From c2f476644212e01f90d18871d4a5c377a611fbb1 Mon Sep 17 00:00:00 2001 From: willkroboth <46540330+willkroboth@users.noreply.github.com> Date: Mon, 13 May 2024 11:46:54 -0400 Subject: [PATCH] Help topic rewrite Revises github.com/JorelAli/CommandAPI/commit/ac8c06086f2e05015416b939028e1301ff95a87b Notable changes: - `RegisteredCommand` and `RegisteredCommand.Node` now have generic `` parameter - `RegisteredCommand.Node` includes the `CommandPermission permission` and `Predicate requirements`, copied from the argument/command the node represents - `RegisteredCommand#permission` is now acessibile as the permission of the `Node rootNode` - Created `CommandAPIHelpTopic` in `dev.jorel.commandapi.help` package - Replaced `shortDescription`, `fullDescription`, `usageDescription`, and `Object helpTopic` fields in `ExecutableCommand` and `RegisteredCommand` with `CommandAPIHelpTopic helpTopic` - Extended by `EditableHelpTopic` - Help builder methods of `ExecutableCommand` are delegated to its `CommandAPIHelpTopic` if it is editable - More general API created for https://github.com/JorelAli/CommandAPI/issues/528: short description, full description, and usage can be provided separately to take advantage of the formatting the CommandAPI uses for static Strings - Extended by `BukkitHelpTopicWrapper` in `commandapi-bukkit-core` - Wraps Bukkit's `HelpTopic` as a `CommandAPIHelpTopic` so full `HelpTopic` customization can still be used - Created `CustomCommandAPIHelpTopic` in `commandapi-bukkit` - Converts a `CommandAPIHelpTopic` into a Bukkit `HelpTopic` for adding to the help map - Replaces usage of `NMS#generateHelpTopic` - Help formatting code extracted from `CommandAPIBukkit#updateHelpForCommands` - Resolves https://github.com/JorelAli/CommandAPI/issues/470: `CommandSender` permissions and requirements are now checked when generating usage - Changed the treatement of namespaced help topics (https://github.com/JorelAli/CommandAPI/pull/546). Namespaced help topics are now created. In the case where the same command name is registered with different namespaces, this allows the user to see the unmerged help using `/help namespace:commandName` (see `CommandHelpTests#testRegisterMergeNamespaces`). - Updated tests to fully cover changes TODO: Update and write documentation --- .../commandapi/AbstractArgumentTree.java | 6 +- .../commandapi/AbstractCommandAPICommand.java | 15 +- .../jorel/commandapi/AbstractCommandTree.java | 6 +- .../java/dev/jorel/commandapi/CommandAPI.java | 5 +- .../jorel/commandapi/CommandAPIHandler.java | 9 +- .../jorel/commandapi/CommandAPIPlatform.java | 2 +- .../jorel/commandapi/CommandPermission.java | 9 +- .../jorel/commandapi/ExecutableCommand.java | 143 ++-- .../jorel/commandapi/RegisteredCommand.java | 142 ++-- .../arguments/AbstractArgument.java | 27 +- .../arguments/FlagsArgumentCommon.java | 27 +- .../jorel/commandapi/arguments/Literal.java | 21 +- .../commandapi/arguments/MultiLiteral.java | 21 +- .../commandapi/help/CommandAPIHelpTopic.java | 11 + .../commandapi/help/EditableHelpTopic.java | 193 +++++ .../help/FullDescriptionGenerator.java | 24 + .../help/ShortDescriptionGenerator.java | 15 + .../jorel/commandapi/help/UsageGenerator.java | 29 + .../jorel/commandapi/CommandAPIBukkit.java | 165 +--- .../jorel/commandapi/CommandAPICommand.java | 11 +- .../dev/jorel/commandapi/CommandTree.java | 19 +- .../commandapi/arguments/CustomArgument.java | 4 +- .../commandapi/arguments/FlagsArgument.java | 2 +- .../commandapi/arguments/LiteralArgument.java | 2 +- .../arguments/MultiLiteralArgument.java | 2 +- .../help/BukkitHelpTopicWrapper.java | 41 + .../help/CustomCommandAPIHelpTopic.java | 136 ++++ .../java/dev/jorel/commandapi/nms/NMS.java | 2 - .../dev/jorel/commandapi/nms/NMS_1_16_R1.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_16_R2.java | 6 - .../jorel/commandapi/nms/NMS_1_16_4_R3.java | 6 - .../jorel/commandapi/nms/NMS_1_17_Common.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_18_R2.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_18_R1.java | 6 - .../jorel/commandapi/nms/NMS_1_19_Common.java | 6 - .../jorel/commandapi/nms/NMS_1_19_3_R2.java | 6 - .../jorel/commandapi/nms/NMS_1_19_4_R3.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_20_R2.java | 6 - .../dev/jorel/commandapi/nms/NMS_1_20_R3.java | 7 - .../dev/jorel/commandapi/nms/NMS_1_20_R4.java | 7 - .../dev/jorel/commandapi/nms/NMS_1_20_R1.java | 6 - .../dev/jorel/commandapi/nms/NMS_Common.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - .../dev/jorel/commandapi/test/MockNMS.java | 5 - ...mmandAPICommandRegisteredCommandTests.java | 119 ++- .../commandapi/test/CommandHelpTests.java | 703 ++++++++++++++++-- .../test/CommandHelpTestsPlugin.java | 35 + .../CommandTreeRegisteredCommandTests.java | 119 ++- .../jorel/commandapi/test/OnEnableTests.java | 2 +- .../test/RegisteredCommandTestBase.java | 57 +- .../jorel/commandapi/CommandAPIVelocity.java | 2 +- .../commandapi/arguments/LiteralArgument.java | 2 +- .../arguments/MultiLiteralArgument.java | 2 +- 60 files changed, 1670 insertions(+), 590 deletions(-) create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/CommandAPIHelpTopic.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java create mode 100644 commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java create mode 100644 commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java index acaa7bb0af..a510841a98 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java @@ -99,7 +99,7 @@ public void setArguments(List> * @param The Brigadier Source object for running commands. */ public void buildBrigadierNode( - NodeInformation previousNodeInformation, + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames ) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); @@ -115,12 +115,12 @@ public void buildBrigadierNode( executor.hasAnyExecutors() ? args -> handler.generateBrigadierCommand(args, executor) : null); // Collect children into our own list - List childrenNodeInformation = new ArrayList<>(); + List> childrenNodeInformation = new ArrayList<>(); // Add our branches as children to the node for (AbstractArgumentTree child : arguments) { // Collect children into our own list - NodeInformation newPreviousNodeInformation = new NodeInformation<>( + NodeInformation newPreviousNodeInformation = new NodeInformation<>( previousNodeInformation.lastCommandNodes(), children -> childrenNodeInformation.addAll(children) ); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 3fede09760..81db8a8bbe 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -238,14 +238,14 @@ protected boolean isRootExecutable() { } @Override - protected List createArgumentNodes(LiteralCommandNode rootNode) { + protected List> createArgumentNodes(LiteralCommandNode rootNode) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); - List childrenNodes = new ArrayList<>(); + List> childrenNodes = new ArrayList<>(); // Create arguments if (hasAnyArguments()) { - NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); @@ -279,12 +279,12 @@ protected List createArgumentNodes(LiteralComma // Add subcommands for (Impl subCommand : subcommands) { - CommandInformation nodes = subCommand.createCommandInformation(""); + CommandInformation nodes = subCommand.createCommandInformation(""); // Add root node rootNode.addChild(nodes.rootNode()); - RegisteredCommand.Node rootNodeInformation = nodes.command().rootNode(); + RegisteredCommand.Node rootNodeInformation = nodes.command().rootNode(); childrenNodes.add(rootNodeInformation); // Add aliases @@ -294,9 +294,10 @@ protected List createArgumentNodes(LiteralComma // Create node information for the alias String aliasName = aliasNode.getLiteral(); childrenNodes.add( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( aliasName, rootNodeInformation.className(), aliasName, - rootNodeInformation.executable(), rootNodeInformation.children() + rootNodeInformation.executable(), rootNodeInformation.permission(), rootNodeInformation.requirements(), + rootNodeInformation.children() ) ); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java index c2520e39c1..4ba7fb0a09 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java @@ -87,10 +87,10 @@ protected boolean isRootExecutable() { } @Override - protected List createArgumentNodes(LiteralCommandNode rootNode) { + protected List> createArgumentNodes(LiteralCommandNode rootNode) { CommandAPIHandler handler = CommandAPIHandler.getInstance(); - List childrenNodes = new ArrayList<>(); + List> childrenNodes = new ArrayList<>(); // The previous arguments include an unlisted MultiLiteral representing the command name and aliases // This doesn't affect how the command acts, but it helps represent the command path in exceptions @@ -103,7 +103,7 @@ protected List createArgumentNodes(LiteralComma // Build branches for (AbstractArgumentTree argument : arguments) { // We need new previousArguments lists for each branch so they don't interfere - NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); + NodeInformation previousNodeInformation = new NodeInformation<>(List.of(rootNode), children -> childrenNodes.addAll(children)); List previousArguments = new ArrayList<>(); List previousArgumentNames = new ArrayList<>(); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java index 85bd740ebe..70164e0563 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPI.java @@ -288,7 +288,8 @@ public static void registerCommand(Class commandClass) { * @return A list of all {@link RegisteredCommand}{@code s} that have been * registered by the CommandAPI so far. The returned list is immutable. */ - public static List getRegisteredCommands() { - return Collections.unmodifiableList(new ArrayList<>(CommandAPIHandler.getInstance().registeredCommands.values())); + public static List> getRegisteredCommands() { + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + return Collections.unmodifiableList(new ArrayList<>(handler.registeredCommands.values())); } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java index ab939cabd1..5f8ab70478 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java @@ -91,7 +91,7 @@ public class CommandAPIHandler platform; - final Map registeredCommands; // Keep track of what has been registered for type checking + final Map> registeredCommands; // Keep track of what has been registered for type checking static final Pattern NAMESPACE_PATTERN = Pattern.compile("[0-9a-z_.-]+"); private static CommandAPIHandler instance; @@ -171,11 +171,11 @@ public void registerCommand(ExecutableCommand command, String platform.preCommandRegistration(command.getName()); // Generate command information - ExecutableCommand.CommandInformation commandInformation = command.createCommandInformation(namespace); + ExecutableCommand.CommandInformation commandInformation = command.createCommandInformation(namespace); LiteralCommandNode resultantNode = commandInformation.rootNode(); List> aliasNodes = commandInformation.aliasNodes(); - RegisteredCommand registeredCommand = commandInformation.command(); + RegisteredCommand registeredCommand = commandInformation.command(); // Log the commands being registered for (List argsAsStr : registeredCommand.rootNode().argsAsStr()) { @@ -209,7 +209,8 @@ public void registerCommand(ExecutableCommand command, String writeDispatcherToFile(); // Merge RegisteredCommand into map - BiFunction mergeRegisteredCommands = (key, value) -> value == null ? registeredCommand : value.mergeCommandInformation(registeredCommand); + BiFunction, RegisteredCommand> mergeRegisteredCommands = + (key, value) -> value == null ? registeredCommand : value.mergeCommandInformation(registeredCommand); if (registeredCommand.namespace().isEmpty()) { registeredCommands.compute(registeredCommand.commandName(), mergeRegisteredCommands); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java index e7134e3cf5..967c05fc07 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIPlatform.java @@ -108,7 +108,7 @@ public interface CommandAPIPlatform resultantNode, List> aliasNodes); + public abstract void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes); /** * Builds and registers a Brigadier command node. diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java index adcdcb884c..962da1d689 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/CommandPermission.java @@ -147,9 +147,14 @@ PermissionNode getPermissionNode() { return this.permissionNode; } - CommandPermission negate() { + /** + * Sets this permission to be negated. + * You can also achieve this using {@link ExecutableCommand#withoutPermission(CommandPermission)}. + * + * @return This permission object + */ + public CommandPermission negate() { this.negated = true; return this; } - } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java index 34a62566af..04cf16c906 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/ExecutableCommand.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.function.Predicate; import com.mojang.brigadier.builder.LiteralArgumentBuilder; @@ -10,6 +9,11 @@ import dev.jorel.commandapi.commandsenders.AbstractCommandSender; import dev.jorel.commandapi.exceptions.InvalidCommandNameException; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; +import dev.jorel.commandapi.help.EditableHelpTopic; +import dev.jorel.commandapi.help.FullDescriptionGenerator; +import dev.jorel.commandapi.help.ShortDescriptionGenerator; +import dev.jorel.commandapi.help.UsageGenerator; /** * This is a base class for {@link AbstractCommandAPICommand} and {@link AbstractCommandTree} command definitions @@ -43,24 +47,7 @@ public abstract class ExecutableCommand requirements = s -> true; // Command help - /** - * An optional short description for the command - */ - protected String shortDescription = null; - /** - * An optional full description for the command - */ - protected String fullDescription = null; - /** - * An optional usage description for the command - */ - protected String[] usageDescription = null; - // TODO: Bukkit specific fields probably should not be in platform agnostic classes - // Either make HelpTopic platform agnostic or move this field into bukkit-core - /** - * An optional HelpTopic object for the command (for Bukkit) - */ - protected Object helpTopic = null; + protected CommandAPIHelpTopic helpTopic = new EditableHelpTopic<>(); ExecutableCommand(final String name) { if (name == null || name.isEmpty() || name.contains(" ")) { @@ -142,6 +129,26 @@ public Impl withRequirement(Predicate requirement) { return instance(); } + /** + * Sets the {@link CommandAPIHelpTopic} for this command. Using this method will override + * any declared short description, full description or usage description provided + * via the following methods and similar overloads: + *
    + *
  • {@link ExecutableCommand#withShortDescription(String)}
  • + *
  • {@link ExecutableCommand#withFullDescription(String)}
  • + *
  • {@link ExecutableCommand#withUsage(String...)}
  • + *
  • {@link ExecutableCommand#withHelp(String, String)}
  • + *
+ * Further calls to these methods will also be ignored. + * + * @param helpTopic the help topic to use for this command + * @return this command builder + */ + public Impl withHelp(CommandAPIHelpTopic helpTopic) { + this.helpTopic = helpTopic; + return instance(); + } + /** * Sets the short description for this command. This is the help which is * shown in the main /help menu. @@ -150,7 +157,9 @@ public Impl withRequirement(Predicate requirement) { * @return this command builder */ public Impl withShortDescription(String description) { - this.shortDescription = description; + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withShortDescription(description); + } return instance(); } @@ -162,7 +171,9 @@ public Impl withShortDescription(String description) { * @return this command builder */ public Impl withFullDescription(String description) { - this.fullDescription = description; + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withFullDescription(description); + } return instance(); } @@ -176,8 +187,9 @@ public Impl withFullDescription(String description) { * @return this command builder */ public Impl withHelp(String shortDescription, String fullDescription) { - this.shortDescription = shortDescription; - this.fullDescription = fullDescription; + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withHelp(shortDescription, fullDescription); + } return instance(); } @@ -189,7 +201,51 @@ public Impl withHelp(String shortDescription, String fullDescription) { * @return this command builder */ public Impl withUsage(String... usage) { - this.usageDescription = usage; + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withUsage(usage); + } + return instance(); + } + + /** + * Sets the short description of this command to be generated using the given {@link ShortDescriptionGenerator}. + * This is the help which is shown in the main /help menu. + * + * @param description The {@link ShortDescriptionGenerator} to use to generate the short description. + * @return this command builder + */ + public Impl withShortDescription(ShortDescriptionGenerator description) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withShortDescription(description); + } + return instance(); + } + + /** + * Sets the full description of this command to be generated using the given {@link FullDescriptionGenerator}. + * This is the help which is shown in the specific /help page for this command (e.g. /help mycommand). + * + * @param description The {@link FullDescriptionGenerator} to use to generate the full description. + * @return this command builder + */ + public Impl withFullDescription(FullDescriptionGenerator description) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withFullDescription(description); + } + return instance(); + } + + /** + * Sets the usage of this command to be generated using the given {@link UsageGenerator}. + * This is the usage which is shown in the specific /help page for this command (e.g. /help mycommand). + * + * @param usage The {@link UsageGenerator} to use to generate the usage. + * @return this command builder + */ + public Impl withUsage(UsageGenerator usage) { + if (helpTopic instanceof EditableHelpTopic editableHelpTopic) { + helpTopic = editableHelpTopic.withUsage(usage); + } return instance(); } @@ -260,13 +316,22 @@ public void setRequirements(Predicate requirements) { this.requirements = requirements; } + /** + * Returns the {@link CommandAPIHelpTopic} for this command + * + * @return the {@link CommandAPIHelpTopic} for this command + */ + public CommandAPIHelpTopic getHelpTopic() { + return helpTopic; + } + /** * Returns the short description for this command * * @return the short description for this command */ public String getShortDescription() { - return this.shortDescription; + return this.helpTopic.getShortDescription().orElse(null); } /** @@ -275,7 +340,7 @@ public String getShortDescription() { * @return the full description for this command */ public String getFullDescription() { - return this.fullDescription; + return this.helpTopic.getFullDescription(null).orElse(null); } /** @@ -284,16 +349,7 @@ public String getFullDescription() { * @return the usage for this command */ public String[] getUsage() { - return this.usageDescription; - } - - /** - * Returns the {@code HelpTopic} object assigned to this command (For Bukkit) - * - * @return the {@code HelpTopic} object assigned to this command (For Bukkit) - */ - public Object getHelpTopic() { - return helpTopic; + return this.helpTopic.getUsage(null, null).orElse(null); } ////////////////// @@ -326,25 +382,24 @@ public void register(String namespace) { ((CommandAPIHandler) CommandAPIHandler.getInstance()).registerCommand(this, namespace); } - protected static record CommandInformation(LiteralCommandNode rootNode, List> aliasNodes, RegisteredCommand command) { + protected static record CommandInformation(LiteralCommandNode rootNode, List> aliasNodes, RegisteredCommand command) { } - protected CommandInformation createCommandInformation(String namespace) { + protected CommandInformation createCommandInformation(String namespace) { checkPreconditions(); // Create rootNode LiteralCommandNode rootNode = this.createCommandNodeBuilder(name).build(); - List children = createArgumentNodes(rootNode); + List> children = createArgumentNodes(rootNode); // Create aliaseNodes List> aliasNodes = createAliasNodes(rootNode); // Create command information - RegisteredCommand command = new RegisteredCommand( - name, aliases, namespace, permission, - Optional.ofNullable(shortDescription), Optional.ofNullable(fullDescription), Optional.ofNullable(usageDescription), Optional.ofNullable(helpTopic), - new RegisteredCommand.Node(name, getClass().getSimpleName(), name, isRootExecutable(), children) + RegisteredCommand command = new RegisteredCommand<>( + name, aliases, namespace, helpTopic, + new RegisteredCommand.Node<>(name, getClass().getSimpleName(), name, isRootExecutable(), permission, requirements, children) ); return new CommandInformation<>(rootNode, aliasNodes, command); @@ -387,5 +442,5 @@ protected List> createAliasNodes(LiteralComm protected abstract boolean isRootExecutable(); - protected abstract List createArgumentNodes(LiteralCommandNode rootNode); + protected abstract List> createArgumentNodes(LiteralCommandNode rootNode); } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java index 0243d52599..19b6464eb2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/RegisteredCommand.java @@ -6,22 +6,22 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; +import java.util.function.Predicate; + +import dev.jorel.commandapi.help.CommandAPIHelpTopic; /** * Class to store a registered command which has its command name and a tree representing its arguments. * This class also contains the information required to construct a meaningful help topic for a command. * - * @param commandName The name of this command, without any leading {@code /} characters - * @param aliases A {@link String}{@code []} of aliases for this command - * @param namespace The namespace for this command - * @param permission The {@link CommandPermission} required to run this command - * @param shortDescription An {@link Optional} containing this command's help's short description - * @param fullDescription An {@link Optional} containing this command's help's full description - * @param fullDescription An {@link Optional} containing this command's help's usage - * @param rootNode The root {@link Node} in the tree structure that holds the arguments of this command + * @param commandName The name of this command, without any leading {@code /} characters + * @param aliases A {@link String}{@code []} of aliases for this command + * @param namespace The namespace for this command + * @param helpTopic The {@link CommandAPIHelpTopic} that stores the help information for this command + * @param rootNode The root {@link Node} in the tree structure that holds the arguments of this command + * @param The class for running platform commands */ -public record RegisteredCommand( +public record RegisteredCommand( /** * @return The name of this command, without any leading {@code /} characters @@ -39,47 +39,28 @@ public record RegisteredCommand( String namespace, /** - * @return The {@link CommandPermission} required to run this command - */ - CommandPermission permission, - - /** - * @return An {@link Optional} containing this command's help's short description - */ - Optional shortDescription, - - /** - * @return An {@link Optional} containing this command's help's full description - */ - Optional fullDescription, - - /** - * @return An {@link Optional} containing this command's help's usage - */ - Optional usageDescription, - - // TODO: Bukkit specific fields probably should not be in platform agnostic classes - // Either make HelpTopic platform agnostic or move this field into bukkit-core - /** - * @return An {@link Optional} containing this command's help topic (for Bukkit) + * @return The {@link CommandAPIHelpTopic} that stores the help information for this command */ - Optional helpTopic, + CommandAPIHelpTopic helpTopic, /** * @return The root {@link Node} in the tree structure that holds the arguments of this command */ - Node rootNode) { + Node rootNode) { /** * Class to store information about each argument in a command's tree. * - * @param nodeName The name of this argument node - * @param className The name of the CommandAPI object that this node represents - * @param helpString The string that should be used to represent this node when automatically generating help usage. - * @param executable True if this node can be executed, and false otherwise - * @param children A {@link List} of nodes that are children to this node + * @param nodeName The name of this argument node + * @param className The name of the CommandAPI object that this node represents + * @param helpString The string that should be used to represent this node when automatically generating help usage. + * @param permission The {@link CommandPermission} object that determines if someone can see this node + * @param requirements An arbitrary additional check to perform to determine if someone can see this node + * @param executable True if this node can be executed, and false otherwise + * @param children A {@link List} of nodes that are children to this node + * @param The class for running platform commands */ - public static record Node( + public static record Node( /** * @return The name of this argument node @@ -92,19 +73,29 @@ public static record Node( String className, /** - * @return The string that should be used to represent this node when automatically generating help usage. + * @return The string that should be used to represent this node when automatically generating help usage */ String helpString, /** * @return True if this node can be executed, and false otherwise */ - boolean executable, + boolean executable, + + /** + * @return The {@link CommandPermission} object that determines if someone can see this node + */ + CommandPermission permission, + + /** + * @return An arbitrary additional check to perform to determine if someone can see this node + */ + Predicate requirements, /** * @return A {@link List} of nodes that are children to this node */ - List children) { + List> children) { /** * @return A {@link List} of each executable path starting at this node. Each path is represented as a {@link List} of @@ -121,7 +112,7 @@ public List> argsAsStr() { } // Add children nodes - for (Node node : children) { + for (Node node : children) { List> subPaths = node.argsAsStr(); for (List subPath : subPaths) { @@ -134,22 +125,44 @@ public List> argsAsStr() { return paths; } - private Node merge(Node other) { + private Node merge(Node other) { // Merge executable status boolean mergeExecutable = this.executable || other.executable; // Merge children - Map childrenByName = new HashMap<>(); - for (Node child : this.children) { + Map> childrenByName = new HashMap<>(); + for (Node child : this.children) { childrenByName.put(child.nodeName, child); } - for (Node child : other.children) { + for (Node child : other.children) { childrenByName.compute(child.nodeName, (key, value) -> value == null ? child : value.merge(child)); } - List mergeChildren = new ArrayList<>(childrenByName.values()); + List> mergeChildren = new ArrayList<>(childrenByName.values()); // Other information defaults to the node that was registered first (this) - return new Node(nodeName, className, helpString, mergeExecutable, mergeChildren); + return new Node<>(nodeName, className, helpString, mergeExecutable, permission, requirements, mergeChildren); + } + + // There is no good way to check equality between Predicates, it's just a strict `==` + // However, record's impelementation of `.equals` tries to include `Predicate requirement` anyway + // So, overide equals to ignore that (and update hashCode so it matches) + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Node)) { + return false; + } + Node other = (Node) obj; + return Objects.equals(nodeName, other.nodeName) && Objects.equals(className, other.className) && Objects.equals(helpString, other.helpString) + && Objects.equals(executable, other.executable) && Objects.equals(permission, other.permission) + && Objects.equals(children, other.children); + } + + @Override + public final int hashCode() { + return Objects.hash(nodeName, className, helpString, executable, permission, children); } } @@ -162,37 +175,35 @@ private Node merge(Node other) { * @param other The {@link RegisteredCommand} that was registered after this one. * @return The {@link RegisteredCommand} that results from merging this and the other command. */ - public RegisteredCommand mergeCommandInformation(RegisteredCommand other) { + public RegisteredCommand mergeCommandInformation(RegisteredCommand other) { // Merge aliases String[] mergedAliases = new String[this.aliases.length + other.aliases.length]; System.arraycopy(this.aliases, 0, mergedAliases, 0, this.aliases.length); System.arraycopy(other.aliases, 0, mergedAliases, this.aliases.length, other.aliases.length); // Merge arguments - Node mergedRootNode = this.rootNode.merge(other.rootNode); + Node mergedRootNode = this.rootNode.merge(other.rootNode); // Other information defaults to the command that was registered first (this) - return new RegisteredCommand(commandName, mergedAliases, namespace, permission, shortDescription, fullDescription, usageDescription, helpTopic, mergedRootNode); + return new RegisteredCommand<>(commandName, mergedAliases, namespace, helpTopic, mergedRootNode); } /** * @return A copy of this {@link RegisteredCommand}, but with {@link RegisteredCommand#namespace()} as {@code ""}. */ - public RegisteredCommand copyWithEmptyNamespace() { - return new RegisteredCommand(commandName, aliases, "", permission, shortDescription, fullDescription, usageDescription, helpTopic, rootNode); + public RegisteredCommand copyWithEmptyNamespace() { + return new RegisteredCommand<>(commandName, aliases, "", helpTopic, rootNode); } // The default implementation of `hashCode`, `equals`, and `toString` don't work for arrays, // so we need to override and specifically use the Arrays methods for `String[] aliases` - // As https://stackoverflow.com/a/32083420 mentions, the same thing happens when Optionals wrap an array, like `Optional usageDescription` @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(aliases); - result = prime * result + Arrays.hashCode(usageDescription.orElse(null)); - result = prime * result + Objects.hash(commandName, namespace, permission, shortDescription, fullDescription, helpTopic, rootNode); + result = prime * result + Objects.hash(commandName, namespace, helpTopic, rootNode); return result; } @@ -204,19 +215,14 @@ public boolean equals(Object obj) { if (!(obj instanceof RegisteredCommand)) { return false; } - RegisteredCommand other = (RegisteredCommand) obj; - return Arrays.equals(aliases, other.aliases) && Objects.equals(rootNode, other.rootNode) && Objects.equals(commandName, other.commandName) - && Objects.equals(namespace, other.namespace) && Arrays.equals(usageDescription.orElse(null), other.usageDescription.orElse(null)) - && Objects.equals(fullDescription, other.fullDescription) && Objects.equals(permission, other.permission) && Objects.equals(shortDescription, other.shortDescription) - && Objects.equals(helpTopic, other.helpTopic); + RegisteredCommand other = (RegisteredCommand) obj; + return Objects.equals(commandName, other.commandName) && Arrays.equals(aliases, other.aliases) && Objects.equals(namespace, other.namespace) + && Objects.equals(helpTopic, other.helpTopic) && Objects.equals(rootNode, other.rootNode); } @Override public String toString() { - return "RegisteredCommand [commandName=" + commandName + ", aliases=" + Arrays.toString(aliases) - + ", namespace=" + namespace + ", permission=" + permission - + ", shortDescription=" + shortDescription + ", fullDescription=" + fullDescription - + ", usageDescription=" + (usageDescription.isPresent() ? "Optional[" + Arrays.toString(usageDescription.get()) + "]" : "Optional.empty") + return "RegisteredCommand [commandName=" + commandName + ", aliases=" + Arrays.toString(aliases) + ", namespace=" + namespace + ", helpTopic=" + helpTopic + ", rootNode=" + rootNode + "]"; } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 705d317c33..d60ae696b7 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -336,12 +336,12 @@ public String toString() { * @param childrenConsumer A callback that accepts a {@link List} of all the {@link RegisteredCommand.Node}s that represent the nodes * added as children to the previous argument. */ - public static record NodeInformation(List> lastCommandNodes, ChildrenConsumer childrenConsumer) { + public static record NodeInformation(List> lastCommandNodes, ChildrenConsumer childrenConsumer) { } @FunctionalInterface - public static interface ChildrenConsumer { - public void createNodeWithChildren(List children); + public static interface ChildrenConsumer { + public void createNodeWithChildren(List> children); } /** @@ -368,8 +368,8 @@ public static interface ChildrenConsumer { * @param The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier node structure for this argument. */ - public NodeInformation addArgumentNodes( - NodeInformation previousNodeInformation, + public NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -397,7 +397,7 @@ public NodeInformation addArgumentNodes( * @param previousArgumentNames A List of Strings containing the node names that came before this argument. */ public void checkPreconditions( - NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames ) { if(previousNodeInformation.lastCommandNodes().isEmpty()) { throw new GreedyArgumentException(previousArguments, (Argument) this); @@ -500,8 +500,8 @@ public CommandNode finishBuildingNode(ArgumentBuilder NodeInformation linkNode( - NodeInformation previousNodeInformation, CommandNode rootNode, + public NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -511,14 +511,15 @@ public NodeInformation linkNode( } // Create information for this node - NodeInformation nodeInformation = new NodeInformation<>( + NodeInformation nodeInformation = new NodeInformation<>( // A GreedyArgument cannot have arguments after it this instanceof GreedyArgument ? List.of() : List.of(rootNode), // Create registered node information once children are created children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "<" + nodeName + ">", combinedArguments.isEmpty() && terminalExecutorCreator != null, + permission, requirements, children ) )) @@ -540,11 +541,13 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", * appropriate Brigadier {@link Command} which should be applied to the last * stacked argument of the node structure. This parameter can be null to indicate * that the argument stack should not be executable. + * @param The implementation of AbstractArgument being used. + * @param The class for running platform commands. * @param The Brigadier Source object for running commands. * @return The list of last nodes in the Brigadier {@link CommandNode} structure representing the built argument stack. */ - public static , Source> NodeInformation stackArguments( - List argumentsToStack, NodeInformation previousNodeInformation, + public static , CommandSender, Source> NodeInformation stackArguments( + List argumentsToStack, NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java index 2792155117..ef94b0e907 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgumentCommon.java @@ -10,6 +10,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.FlagsArgumentEndingNode; @@ -19,6 +20,7 @@ import java.util.*; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.function.Predicate; public interface FlagsArgumentCommon getRequirements(); + /** * Links to {@link AbstractArgument#getCombinedArguments()}. */ @@ -57,7 +69,7 @@ public interface FlagsArgumentCommon void checkPreconditions( - NodeInformation previousNodes, List previousArguments, List previousArgumentNames + NodeInformation previousNodes, List previousArguments, List previousArgumentNames ); /** @@ -100,8 +112,8 @@ default List parseArgument(CommandContext cmd * A FlagsArgument works completely differently from a typical argument, so we need to completely * override the usual logic. */ - default NodeInformation addArgumentNodes( - NodeInformation previousNodeInformation, + default NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -143,13 +155,14 @@ default NodeInformation addArgumentNodes( } // Create information for this node - NodeInformation nodeInformation = new NodeInformation<>( + NodeInformation nodeInformation = new NodeInformation<>( newNodes, // Create registered node information once children are created children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "<" + nodeName + ">", loopingBranchesExecutable || terminalBranchesExecutable, + getArgumentPermission(), getRequirements(), children ) )) @@ -159,7 +172,7 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", return AbstractArgument.stackArguments(getCombinedArguments(), nodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); } - private static , Source> List> setupBranch( + private static , CommandSender, Source> List> setupBranch( List branchArguments, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator, @@ -170,7 +183,7 @@ nodeName, getClass().getSimpleName(), "<" + nodeName + ">", List branchPreviousArgumentNames = new ArrayList<>(previousArgumentNames); RootCommandNode branchRoot = new RootCommandNode<>(); - NodeInformation branchNodeInformation = new NodeInformation<>(List.of(branchRoot), null); + NodeInformation branchNodeInformation = new NodeInformation<>(List.of(branchRoot), null); // Stack branch nodes branchNodeInformation = AbstractArgument.stackArguments(branchArguments, branchNodeInformation, branchPreviousArguments, branchPreviousArgumentNames, terminalExecutorCreator); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java index f9347166c5..a28973b0e6 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/Literal.java @@ -5,12 +5,14 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; import java.util.List; import java.util.function.Function; +import java.util.function.Predicate; /** * An interface representing literal-based arguments @@ -39,6 +41,16 @@ public interface Literal getRequirements(); + /** * Links to {@link AbstractArgument#isListed()}. */ @@ -79,8 +91,8 @@ public interface Literal * Normally, Arguments use thier node name as their help string. However, a Literal uses its literal as the help string. */ - default NodeInformation linkNode( - NodeInformation previousNodeInformation, CommandNode rootNode, + default NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -90,13 +102,14 @@ default NodeInformation linkNode( } // Create information for this node - NodeInformation nodeInformation = new NodeInformation<>( + NodeInformation nodeInformation = new NodeInformation<>( List.of(rootNode), // Create registered node information once children are created children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( getNodeName(), getClass().getSimpleName(), getLiteral(), getCombinedArguments().isEmpty() && terminalExecutorCreator != null, + getArgumentPermission(), getRequirements(), children ) )) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java index cc4e84c284..a1fdea5c74 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteral.java @@ -5,6 +5,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.arguments.AbstractArgument.NodeInformation; import dev.jorel.commandapi.commandnodes.NamedLiteralArgumentBuilder; @@ -14,6 +15,7 @@ import java.util.Iterator; import java.util.List; import java.util.function.Function; +import java.util.function.Predicate; /** * An interface representing arguments with multiple literal string definitions @@ -44,6 +46,16 @@ public interface MultiLiteral getRequirements(); + /** * Links to {@link AbstractArgument#isListed()}. */ @@ -86,8 +98,8 @@ public interface MultiLiteral NodeInformation linkNode( - NodeInformation previousNodeInformation, CommandNode rootNode, + default NodeInformation linkNode( + NodeInformation previousNodeInformation, CommandNode rootNode, List previousArguments, List previousArgumentNames, Function, Command> terminalExecutorCreator ) { @@ -120,14 +132,15 @@ default NodeInformation linkNode( } // Create information for this node - NodeInformation nodeInformation = new NodeInformation<>( + NodeInformation nodeInformation = new NodeInformation<>( newNodes, // Create registered node information once children are created children -> previousNodeInformation.childrenConsumer().createNodeWithChildren(List.of( - new RegisteredCommand.Node( + new RegisteredCommand.Node<>( nodeName, getClass().getSimpleName(), "(" + String.join("|", getLiterals())+ ")", getCombinedArguments().isEmpty() && terminalExecutorCreator != null, + getArgumentPermission(), getRequirements(), children ) )) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/CommandAPIHelpTopic.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/CommandAPIHelpTopic.java new file mode 100644 index 0000000000..34f402728d --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/CommandAPIHelpTopic.java @@ -0,0 +1,11 @@ +package dev.jorel.commandapi.help; + +/** + * An interface for providing the short description, full description, and usage in a command help. + * This combines the {@link FunctionalInterface}s {@link ShortDescriptionGenerator}, {@link FullDescriptionGenerator}, and {@link UsageGenerator}. + * + * @param The class for running platform commands. + */ +public interface CommandAPIHelpTopic extends ShortDescriptionGenerator, FullDescriptionGenerator, UsageGenerator { + +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java new file mode 100644 index 0000000000..4a47f1c003 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/EditableHelpTopic.java @@ -0,0 +1,193 @@ +package dev.jorel.commandapi.help; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +import javax.annotation.Nullable; + +import dev.jorel.commandapi.RegisteredCommand; + +/** + * An {@link CommandAPIHelpTopic} that can have its short description, full description, and usage edited. + */ +public class EditableHelpTopic implements CommandAPIHelpTopic { + private ShortDescriptionGenerator shortDescription = () -> Optional.empty(); + private FullDescriptionGenerator fullDescription = forWho -> Optional.empty(); + private UsageGenerator usage = (forWho, argumentTree) -> Optional.empty(); + + /** + * Creates a new {@link EditableHelpTopic} that returns empty {@link Optional}s + * by default for its short description, full description, and usage. + */ + public EditableHelpTopic() { + + } + + /** + * Creates a new {@link EditableHelpTopic} that returns the given short description, full description, and usage by default. + * + * @param shortDescription The short description {@link String} for this command help. + * @param fullDescription The full description {@link String} for this command help. + * @param usage The {@link String} array that holds the usage for this command help. + */ + public EditableHelpTopic(@Nullable String shortDescription, @Nullable String fullDescription, @Nullable String[] usage) { + Optional shortDescriptionResult = Optional.ofNullable(shortDescription); + this.shortDescription = () -> shortDescriptionResult; + + Optional fullDescriptionResult = Optional.ofNullable(fullDescription); + this.fullDescription = forWho -> fullDescriptionResult; + + Optional usageResult = Optional.ofNullable(usage); + this.usage = (forWho, argumentTree) -> usageResult; + } + + // Static help results + /** + * Sets the short description for this command help. This is the help which is + * shown in the main /help menu. + * + * @param description the short description for the command help + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withShortDescription(@Nullable String description) { + Optional result = Optional.ofNullable(description); + this.shortDescription = () -> result; + + return this; + } + + /** + * Sets the full description for this command help. This is the help which is + * shown in the specific /help page for the command (e.g. /help mycommand). + * + * @param description the full description for this command help + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withFullDescription(@Nullable String description) { + Optional result = Optional.ofNullable(description); + this.fullDescription = forWho -> result; + + return this; + } + + /** + * Sets the short and full description for this command help. This is a short-hand + * for the {@link #withShortDescription} and {@link #withFullDescription} methods. + * + * @param shortDescription the short description for this command help + * @param fullDescription the full description for this command help + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withHelp(@Nullable String shortDescription, @Nullable String fullDescription) { + this.withShortDescription(shortDescription); + this.withFullDescription(fullDescription); + + return this; + } + + /** + * Sets the usage for this command help. This is the usage which is + * shown in the specific /help page for the command (e.g. /help mycommand). + * + * @param usage the full usage for this command + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withUsage(@Nullable String... usage) { + Optional result = Optional.ofNullable(usage); + this.usage = (forWho, argumentTree) -> result; + + return this; + } + + // Dynamic help results + /** + * Sets the short description of this command help to be generated using the given {@link ShortDescriptionGenerator}. + * This is the help which is shown in the main /help menu. + * + * @param description The {@link ShortDescriptionGenerator} to use to generate the short description. + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withShortDescription(ShortDescriptionGenerator description) { + this.shortDescription = description; + + return this; + } + + /** + * Sets the full description of this command help to be generated using the given {@link FullDescriptionGenerator}. + * This is the help which is shown in the specific /help page for the command (e.g. /help mycommand). + * + * @param description The {@link FullDescriptionGenerator} to use to generate the full description. + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withFullDescription(FullDescriptionGenerator description) { + this.fullDescription = description; + + return this; + } + + /** + * Sets the usage of this command help to be generated using the given {@link UsageGenerator}. + * This is the usage which is shown in the specific /help page for the command (e.g. /help mycommand). + * + * @param usage The {@link UsageGenerator} to use to generate the usage. + * @return this {@link EditableHelpTopic} + */ + public EditableHelpTopic withUsage(UsageGenerator usage) { + this.usage = usage; + + return this; + } + + // Implement CommandAPIHelpTopic methods + @Override + public Optional getShortDescription() { + return shortDescription.getShortDescription(); + } + + @Override + public Optional getFullDescription(@Nullable CommandSender forWho) { + return fullDescription.getFullDescription(forWho); + } + + @Override + public Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree) { + return usage.getUsage(forWho, argumentTree); + } + + // equals, hashCode, toString + // Since our fields are functions, they aren't easily compared + // However, the 'default' values returned by passing null parameters tend to make sense + // Just keep in mind that these return Optionals, and in the case of usage, the String[] should use Arrays methods + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof EditableHelpTopic)) { + return false; + } + EditableHelpTopic other = (EditableHelpTopic) obj; + return Objects.equals(shortDescription.getShortDescription(), other.shortDescription.getShortDescription()) + && Objects.equals(fullDescription.getFullDescription(null), other.fullDescription.getFullDescription(null)) + && Arrays.equals(usage.getUsage(null, null).orElse(null), other.usage.getUsage(null, null).orElse(null)); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(usage.getUsage(null, null).orElse(null)); + result = prime * result + Objects.hash(shortDescription.getShortDescription(), fullDescription.getFullDescription(null)); + return result; + } + + @Override + public final String toString() { + return "EditableHelpTopic [" + + "shortDescription=" + shortDescription.getShortDescription() + + ", fullDescription=" + fullDescription.getFullDescription(null) + + ", usage=" + usage.getUsage(null, null).map(Arrays::toString) + "]"; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java new file mode 100644 index 0000000000..2c4839fe0c --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/FullDescriptionGenerator.java @@ -0,0 +1,24 @@ +package dev.jorel.commandapi.help; + +import java.util.Optional; + +import javax.annotation.Nullable; + +/** + * A {@link FunctionalInterface} for generating command help full descriptions. + * See {@link #getFullDescription(Object)}. + * + * @param The class for running platform commands. + */ +@FunctionalInterface +public interface FullDescriptionGenerator { + /** + * Returns an {@link Optional} containing the {@link String} that is the full description for this command help. + * + * @param forWho The {@code CommandSender} the full description should be generated for. For example, you + * could test if this sender has permission to see a branch in your command. This + * parameter may be null. + * @return An {@link Optional} {@link String} that is the full description for this command help. + */ + public Optional getFullDescription(@Nullable CommandSender forWho); +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java new file mode 100644 index 0000000000..25b17c1d3e --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/ShortDescriptionGenerator.java @@ -0,0 +1,15 @@ +package dev.jorel.commandapi.help; + +import java.util.Optional; + +/** + * A {@link FunctionalInterface} for generating command help short descriptions. + * See {@link #getShortDescription()}. + */ +@FunctionalInterface +public interface ShortDescriptionGenerator { + /** + * @return An {@link Optional} {@link String} that is the short description for this command help. + */ + public Optional getShortDescription(); +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java b/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java new file mode 100644 index 0000000000..5f18333828 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/help/UsageGenerator.java @@ -0,0 +1,29 @@ +package dev.jorel.commandapi.help; + +import java.util.Optional; + +import javax.annotation.Nullable; + +import dev.jorel.commandapi.RegisteredCommand; + +/** + * A {@link FunctionalInterface} for generating command help usages. + * See {@link #getUsage(Object, RegisteredCommand.Node)}. + * + * @param The class for running platform commands. + */ +@FunctionalInterface +public interface UsageGenerator { + /** + * Returns an {@link Optional} containing a {@code String[]}, where each item in the array + * represents a possible way to use the command. + * + * @param forWho The {@code CommandSender} the usage should be generated for. For example, you + * could test if this sender has permission to see a branch in your command. This + * parameter may be null. + * @param argumentTree The {@link RegisteredCommand.Node} that is the root node of the command the usage + * should be generated for. This parameter may be null. + * @return An {@link Optional} {@link String} array with the usage for this command help. + */ + public Optional getUsage(@Nullable CommandSender forWho, @Nullable RegisteredCommand.Node argumentTree); +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index 71e45a9f6b..e3a1d3cafa 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -36,6 +36,9 @@ import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; +import dev.jorel.commandapi.help.BukkitHelpTopicWrapper; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; +import dev.jorel.commandapi.help.CustomCommandAPIHelpTopic; import dev.jorel.commandapi.nms.NMS; import dev.jorel.commandapi.preprocessor.RequireField; import dev.jorel.commandapi.preprocessor.Unimplemented; @@ -43,7 +46,6 @@ import net.kyori.adventure.text.Component; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.Keyed; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.Command; @@ -280,152 +282,51 @@ private String unpackInternalPermissionNodeString(CommandPermission perm) { /* * Generate and register help topics */ - private String generateCommandHelpPrefix(String command) { - return (Bukkit.getPluginCommand(command) == null ? "/" : "/minecraft:") + command; - } - - private String generateCommandHelpPrefix(String command, String namespace) { - return (Bukkit.getPluginCommand(command) == null ? "/" + namespace + ":" : "/minecraft:") + command; - } - - private void generateHelpUsage(StringBuilder sb, RegisteredCommand command) { - // Generate usages - String[] usages = getUsageList(command); - - if (usages.length == 0) { - // Might happen if the developer calls `.withUsage()` with no parameters - // They didn't give any usage, so we won't put any there - return; - } - - sb.append(ChatColor.GOLD).append("Usage: ").append(ChatColor.WHITE); - // If 1 usage, put it on the same line, otherwise format like a list - if (usages.length == 1) { - sb.append(usages[0]); - } else { - for (String usage : usages) { - sb.append("\n- ").append(usage); - } - } - } - - private String[] getUsageList(RegisteredCommand currentCommand) { - // TODO: I don't think the changes I made here are well tested, so this could have changed behavior - final Optional usageDescription = currentCommand.usageDescription(); - if (usageDescription.isPresent()) return usageDescription.get(); // Usage was overriden - - // Generate command usage - // TODO: Should default usage generation be updated? https://github.com/JorelAli/CommandAPI/issues/363 - List usages = new ArrayList<>(); - StringBuilder usageSoFar = new StringBuilder("/"); - addUsageForNode(currentCommand.rootNode(), usages, usageSoFar); - return usages.toArray(String[]::new); - } - - private void addUsageForNode(RegisteredCommand.Node node, List usages, StringBuilder usageSoFar) { - // Add node to usage - usageSoFar.append(node.helpString()); - - // Add usage to the list if this is executable - if (node.executable()) usages.add(usageSoFar.toString()); - - // Add children - usageSoFar.append(" "); - int currentLength = usageSoFar.length(); - for (RegisteredCommand.Node child : node.children()) { - // Reset the string builder to the usage up to and including this node - usageSoFar.delete(currentLength, usageSoFar.length()); - - addUsageForNode(child, usages, usageSoFar); - } - } - - void updateHelpForCommands(List commands) { + void updateHelpForCommands(List> commands) { Map helpTopicsToAdd = new HashMap<>(); - Set namespacedCommandNames = new HashSet<>(); - - for (RegisteredCommand command : commands) { - // Don't override the plugin help topic - String commandPrefix = generateCommandHelpPrefix(command.commandName()); - // Namespaced commands shouldn't have a help topic, we should save the namespaced command name - namespacedCommandNames.add(generateCommandHelpPrefix(command.commandName(), command.namespace())); - - StringBuilder aliasSb = new StringBuilder(); - final String shortDescription; - - // Must be empty string, not null as defined by OBC::CustomHelpTopic - final String permission = command.permission().getPermission().orElse(""); + for (RegisteredCommand command : commands) { + String namespaceAddon = (command.namespace().isEmpty() ? "" : command.namespace() + ":"); + String commandName = namespaceAddon + command.commandName(); + CommandAPIHelpTopic commandAPIHelpTopic = command.helpTopic(); - HelpTopic helpTopic; - if (command.helpTopic().isPresent()) { - helpTopic = (HelpTopic) command.helpTopic().get(); - shortDescription = ""; - } else { - // Generate short description - final Optional shortDescriptionOptional = command.shortDescription(); - final Optional fullDescriptionOptional = command.fullDescription(); - if (shortDescriptionOptional.isPresent()) { - shortDescription = shortDescriptionOptional.get(); - } else if (fullDescriptionOptional.isPresent()) { - shortDescription = fullDescriptionOptional.get(); + // Don't override other plugin's help topics + if(Bukkit.getPluginCommand(commandName) == null) { + final HelpTopic helpTopic; + if (commandAPIHelpTopic instanceof BukkitHelpTopicWrapper bukkitHelpTopic) { + helpTopic = bukkitHelpTopic.helpTopic(); } else { - shortDescription = "A command by the " + config.getPlugin().getName() + " plugin."; - } - - // Generate full description - StringBuilder sb = new StringBuilder(); - if (fullDescriptionOptional.isPresent()) { - sb.append(ChatColor.GOLD).append("Description: ").append(ChatColor.WHITE).append(fullDescriptionOptional.get()).append("\n"); - } - - generateHelpUsage(sb, command); - sb.append("\n"); - - // Generate aliases. We make a copy of the StringBuilder because we - // want to change the output when we register aliases - aliasSb = new StringBuilder(sb.toString()); - if (command.aliases().length > 0) { - sb.append(ChatColor.GOLD).append("Aliases: ").append(ChatColor.WHITE).append(String.join(", ", command.aliases())); + helpTopic = new CustomCommandAPIHelpTopic(commandName, command.aliases(), commandAPIHelpTopic, command.rootNode()); } - - helpTopic = generateHelpTopic(commandPrefix, shortDescription, sb.toString().trim(), permission); + helpTopicsToAdd.put("/" + commandName, helpTopic); } - helpTopicsToAdd.put(commandPrefix, helpTopic); for (String alias : command.aliases()) { - if (command.helpTopic().isPresent()) { - helpTopic = (HelpTopic) command.helpTopic().get(); + String aliasName = namespaceAddon + alias; + + // Don't override other plugin's help topics + if(Bukkit.getPluginCommand(aliasName) != null) { + continue; + } + + final HelpTopic helpTopic; + if (commandAPIHelpTopic instanceof BukkitHelpTopicWrapper bukkitHelpTopic) { + helpTopic = bukkitHelpTopic.helpTopic(); } else { - StringBuilder currentAliasSb = new StringBuilder(aliasSb.toString()); - currentAliasSb.append(ChatColor.GOLD).append("Aliases: ").append(ChatColor.WHITE); - // We want to get all aliases (including the original command name), // except for the current alias List aliases = new ArrayList<>(Arrays.asList(command.aliases())); aliases.add(command.commandName()); aliases.remove(alias); - - currentAliasSb.append(String.join(", ", aliases)); - - // Don't override the plugin help topic - commandPrefix = generateCommandHelpPrefix(alias); - helpTopic = generateHelpTopic(commandPrefix, shortDescription, currentAliasSb.toString().trim(), permission); - // Namespaced commands shouldn't have a help topic, we should save the namespaced alias name - namespacedCommandNames.add(generateCommandHelpPrefix(alias, command.namespace())); + helpTopic = new CustomCommandAPIHelpTopic(aliasName, aliases.toArray(String[]::new), commandAPIHelpTopic, command.rootNode()); } - helpTopicsToAdd.put(commandPrefix, helpTopic); + helpTopicsToAdd.put("/" + aliasName, helpTopic); } } // We have to use helpTopics.put (instead of .addTopic) because we're overwriting an existing help topic, not adding a new help topic getHelpMap().putAll(helpTopicsToAdd); - - // We also have to remove help topics for namespaced command names - for (String namespacedCommandName : namespacedCommandNames) { - getHelpMap().remove(namespacedCommandName); - } } private void fixNamespaces() { @@ -557,9 +458,9 @@ public void preCommandRegistration(String commandName) { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { // Register the command's permission string (if it exists) to Bukkit's manager - CommandPermission permission = registeredCommand.permission(); + CommandPermission permission = registeredCommand.rootNode().permission(); permission.getPermission().ifPresent(this::registerPermission); if(!CommandAPI.canRegister()) { @@ -603,7 +504,13 @@ public void postCommandRegistration(RegisteredCommand registeredCommand, Literal } // Adding the command to the help map usually happens in `CommandAPIBukkit#onEnable` - updateHelpForCommands(List.of(registeredCommand)); + // We'll make sure to retrieve the merged versions from CommandAPIHandler + CommandAPIHandler handler = CommandAPIHandler.getInstance(); + Map> registeredCommands = handler.registeredCommands; + updateHelpForCommands(List.of( + registeredCommands.get(registeredCommand.commandName()), + registeredCommands.get(registeredCommand.namespace() + ":" + registeredCommand.commandName()) + )); // Sending command dispatcher packets usually happens when Players join the server for(Player p: Bukkit.getOnlinePlayers()) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java index 3a8cd57426..171aed7c3c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPICommand.java @@ -1,8 +1,8 @@ package dev.jorel.commandapi; import dev.jorel.commandapi.arguments.Argument; - -import java.util.Optional; +import dev.jorel.commandapi.help.BukkitHelpTopicWrapper; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; import org.bukkit.command.CommandSender; import org.bukkit.help.HelpTopic; @@ -26,18 +26,21 @@ public CommandAPICommand instance() { /** * Sets the {@link HelpTopic} for this command. Using this method will override * any declared short description, full description or usage description provided - * via the following methods: + * via the following methods and similar overloads: *
    *
  • {@link CommandAPICommand#withShortDescription(String)}
  • *
  • {@link CommandAPICommand#withFullDescription(String)}
  • *
  • {@link CommandAPICommand#withUsage(String...)}
  • *
  • {@link CommandAPICommand#withHelp(String, String)}
  • *
+ * Further calls to these methods will also be ignored. + * See also {@link ExecutableCommand#withHelp(CommandAPIHelpTopic)}. + * * @param helpTopic the help topic to use for this command * @return this command builder */ public CommandAPICommand withHelp(HelpTopic helpTopic) { - this.helpTopic = Optional.of(helpTopic); + this.helpTopic = new BukkitHelpTopicWrapper(helpTopic); return instance(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java index 5cd69a3789..659d14d91b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandTree.java @@ -1,8 +1,8 @@ package dev.jorel.commandapi; import dev.jorel.commandapi.arguments.Argument; - -import java.util.Optional; +import dev.jorel.commandapi.help.BukkitHelpTopicWrapper; +import dev.jorel.commandapi.help.CommandAPIHelpTopic; import org.bukkit.command.CommandSender; import org.bukkit.help.HelpTopic; @@ -26,18 +26,21 @@ public CommandTree instance() { /** * Sets the {@link HelpTopic} for this command. Using this method will override * any declared short description, full description or usage description provided - * via the following methods: + * via the following methods and similar overloads: *
    - *
  • {@link CommandAPICommand#withShortDescription(String)}
  • - *
  • {@link CommandAPICommand#withFullDescription(String)}
  • - *
  • {@link CommandAPICommand#withUsage(String...)}
  • - *
  • {@link CommandAPICommand#withHelp(String, String)}
  • + *
  • {@link CommandTree#withShortDescription(String)}
  • + *
  • {@link CommandTree#withFullDescription(String)}
  • + *
  • {@link CommandTree#withUsage(String...)}
  • + *
  • {@link CommandTree#withHelp(String, String)}
  • *
+ * Further calls to these methods will also be ignored. + * See also {@link ExecutableCommand#withHelp(CommandAPIHelpTopic)}. + * * @param helpTopic the help topic to use for this command * @return this command builder */ public CommandTree withHelp(HelpTopic helpTopic) { - this.helpTopic = Optional.of(helpTopic); + this.helpTopic = new BukkitHelpTopicWrapper(helpTopic); return instance(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java index 5e5d73539b..3d5a273170 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/CustomArgument.java @@ -199,8 +199,8 @@ public Argument combineWith(List> combinedArguments) { } @Override - public NodeInformation addArgumentNodes( - NodeInformation previousNodeInformation, + public NodeInformation addArgumentNodes( + NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator ) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java index b1cb0155bc..30e8765ca9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/FlagsArgument.java @@ -73,7 +73,7 @@ public List parseArgument(CommandContext cmdC } @Override - public NodeInformation addArgumentNodes(NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation addArgumentNodes(NodeInformation previousNodeInformation, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return FlagsArgumentCommon.super.addArgumentNodes(previousNodeInformation, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index 45c3dac01c..1e96c986db 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -157,7 +157,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index b3d95b1e60..f3b1b799d9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -112,7 +112,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java new file mode 100644 index 0000000000..6947e4f957 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/BukkitHelpTopicWrapper.java @@ -0,0 +1,41 @@ +package dev.jorel.commandapi.help; + +import java.util.Optional; + +import javax.annotation.Nullable; + +import org.bukkit.command.CommandSender; +import org.bukkit.help.HelpTopic; + +import dev.jorel.commandapi.RegisteredCommand.Node; + +/** + * A {@link CommandAPIHelpTopic} that wraps Bukkit's {@link HelpTopic}. + * + * @param helpTopic The Bukkit {@link HelpTopic} being wrapped + */ +public record BukkitHelpTopicWrapper( + + /** + * @return The Bukkit {@link HelpTopic} being wrapped + */ + HelpTopic helpTopic) implements CommandAPIHelpTopic { + + @Override + public Optional getShortDescription() { + return Optional.of(helpTopic.getShortText()); + } + + @Override + public Optional getFullDescription(@Nullable CommandSender forWho) { + if (forWho == null) return Optional.empty(); + + return Optional.of(helpTopic.getFullText(forWho)); + } + + @Override + public Optional getUsage(@Nullable CommandSender forWho, @Nullable Node argumentTree) { + return Optional.empty(); + } + +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java new file mode 100644 index 0000000000..de4ec4dec1 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/help/CustomCommandAPIHelpTopic.java @@ -0,0 +1,136 @@ +package dev.jorel.commandapi.help; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.help.HelpTopic; +import org.jetbrains.annotations.NotNull; + +import dev.jorel.commandapi.CommandAPIBukkit; +import dev.jorel.commandapi.CommandPermission; +import dev.jorel.commandapi.RegisteredCommand; + +public class CustomCommandAPIHelpTopic extends HelpTopic { + private final String aliasString; + private final CommandAPIHelpTopic helpTopic; + private final RegisteredCommand.Node argumentTree; + + public CustomCommandAPIHelpTopic(String name, String[] aliases, CommandAPIHelpTopic helpTopic, RegisteredCommand.Node argumentTree) { + this.name = name; + + // Pre-generate alias help text since it doesn't depend on sender + if (aliases.length > 0) { + this.aliasString = "\n" + ChatColor.GOLD + "Aliases: " + ChatColor.WHITE + String.join(", ", aliases); + } else { + this.aliasString = ""; + } + + this.helpTopic = helpTopic; + this.argumentTree = argumentTree; + } + + @Override + public boolean canSee(@NotNull CommandSender sender) { + // Check if sender can see root node + return canSeeNode(sender, argumentTree); + } + + private boolean canSeeNode(CommandSender sender, RegisteredCommand.Node node) { + final boolean hasPermission; + + // Evaluate the CommandPermission + CommandPermission permission = node.permission(); + if (permission.equals(CommandPermission.NONE)) { + hasPermission = true; + } else if(permission.equals(CommandPermission.OP)) { + hasPermission = sender.isOp(); + } else { + Optional optionalStringPermission = permission.getPermission(); + if (optionalStringPermission.isPresent()) { + hasPermission = sender.hasPermission(optionalStringPermission.get()); + } else { + hasPermission = true; + } + } + + // If sender doesn't have permission (when negated if needed), they can't see this help + if (!hasPermission ^ permission.isNegated()) return false; + + // Check requirements + return node.requirements().test(sender); + } + + @Override + public @NotNull String getShortText() { + Optional shortDescriptionOptional = helpTopic.getShortDescription(); + if (shortDescriptionOptional.isPresent()) { + return shortDescriptionOptional.get(); + } else { + return helpTopic.getFullDescription(null) + .orElse("A command by the " + CommandAPIBukkit.getConfiguration().getPlugin().getName() + " plugin."); + } + } + + @Override + public @NotNull String getFullText(@NotNull CommandSender forWho) { + // Generate full text for the given sender + StringBuilder sb = new StringBuilder(this.getShortText()); + + // Add fullDescription if present + Optional fullDescriptionOptional = this.helpTopic.getFullDescription(forWho); + if (fullDescriptionOptional.isPresent()) { + sb.append("\n").append(ChatColor.GOLD).append("Description: ").append(ChatColor.WHITE).append(fullDescriptionOptional.get()); + } + + // Add usage if present, and otherwise generate default usage + String[] usages = this.helpTopic.getUsage(forWho, this.argumentTree).orElseGet(() -> generateDefaultUsage(forWho)); + + if (usages.length > 0) { + sb.append("\n").append(ChatColor.GOLD).append("Usage: ").append(ChatColor.WHITE); + // If 1 usage, put it on the same line, otherwise format like a list + if (usages.length == 1) { + sb.append(usages[0]); + } else { + for (String usage : usages) { + sb.append("\n- ").append(usage); + } + } + } + + // Add aliases + sb.append(this.aliasString); + + return sb.toString(); + } + + private String[] generateDefaultUsage(CommandSender forWho) { + List usages = new ArrayList<>(); + StringBuilder usageSoFar = new StringBuilder("/"); + addUsageForNode(this.argumentTree, usages, usageSoFar, forWho); + return usages.toArray(String[]::new); + } + + private void addUsageForNode(RegisteredCommand.Node node, List usages, StringBuilder usageSoFar, CommandSender forWho) { + // If sender can't see the node, don't include it in the usage + if(!canSeeNode(forWho, node)) return; + + // Add node to usage + usageSoFar.append(node.helpString()); + + // Add usage to the list if this is executable + if (node.executable()) usages.add(usageSoFar.toString()); + + // Add children + usageSoFar.append(" "); + int currentLength = usageSoFar.length(); + for (RegisteredCommand.Node child : node.children()) { + // Reset the string builder to the usage up to and including this node + usageSoFar.delete(currentLength, usageSoFar.length()); + + addUsageForNode(child, usages, usageSoFar, forWho); + } + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java index bc2c31286b..974ef287c8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/nms/NMS.java @@ -450,8 +450,6 @@ String getScoreHolderSingle(CommandContext cmdCtx, Strin */ void reloadDataPacks(); - HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission); - Map getHelpMap(); Message generateMessageFromJson(String json); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.1/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.1/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R1.java index 16ea05666b..5585899e5c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.1/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.1/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R1.java @@ -49,7 +49,6 @@ import org.bukkit.craftbukkit.v1_16_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_16_R1.enchantments.CraftEnchantment; import org.bukkit.craftbukkit.v1_16_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_16_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_16_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_16_R1.potion.CraftPotionEffectType; @@ -453,11 +452,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ArgumentMinecraftKeyRegistered.a(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R2.java index 84b44a96e4..3a54544580 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_R2.java @@ -48,7 +48,6 @@ import org.bukkit.craftbukkit.v1_16_R2.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_16_R2.enchantments.CraftEnchantment; import org.bukkit.craftbukkit.v1_16_R2.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_16_R2.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_16_R2.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_16_R2.potion.CraftPotionEffectType; @@ -452,11 +451,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ArgumentMinecraftKeyRegistered.a(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_4_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_4_R3.java index c025401754..583cf33a35 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_4_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.16.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_16_4_R3.java @@ -68,7 +68,6 @@ import org.bukkit.craftbukkit.v1_16_R3.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_16_R3.enchantments.CraftEnchantment; import org.bukkit.craftbukkit.v1_16_R3.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_16_R3.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_16_R3.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_16_R3.potion.CraftPotionEffectType; @@ -402,11 +401,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ArgumentMinecraftKeyRegistered.a(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java index a7b50440b4..a0f086ba4e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.17-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_17_Common.java @@ -65,7 +65,6 @@ import org.bukkit.craftbukkit.v1_17_R1.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_17_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_17_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_17_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_17_R1.potion.CraftPotionEffectType; @@ -264,11 +263,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java index 823f8293ef..04c877f62a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R2.java @@ -68,7 +68,6 @@ import org.bukkit.craftbukkit.v1_18_R2.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_18_R2.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_18_R2.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_18_R2.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_18_R2.potion.CraftPotionEffectType; @@ -286,11 +285,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java index 30e87940fc..83adacef2c 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.18/src/main/java/dev/jorel/commandapi/nms/NMS_1_18_R1.java @@ -67,7 +67,6 @@ import org.bukkit.craftbukkit.v1_18_R1.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_18_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_18_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_18_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_18_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_18_R1.potion.CraftPotionEffectType; @@ -272,11 +271,6 @@ public void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java index f8aec9ee4c..753efc5bc5 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19-common/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_Common.java @@ -121,7 +121,6 @@ import org.bukkit.craftbukkit.v1_19_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity; import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_19_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_19_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_19_R1.potion.CraftPotionEffectType; @@ -356,11 +355,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java index ad155b5fa3..4e8198850b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_3_R2.java @@ -118,7 +118,6 @@ import org.bukkit.craftbukkit.v1_19_R2.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_19_R2.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_19_R2.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_19_R2.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_19_R2.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_19_R2.potion.CraftPotionEffectType; @@ -266,11 +265,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java index deeb5fe56d..20ca5fc191 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.19.4/src/main/java/dev/jorel/commandapi/nms/NMS_1_19_4_R3.java @@ -118,7 +118,6 @@ import org.bukkit.craftbukkit.v1_19_R3.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_19_R3.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_19_R3.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_19_R3.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_19_R3.potion.CraftPotionEffectType; @@ -265,11 +264,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java index ef54da9413..309079078e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.2/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R2.java @@ -66,7 +66,6 @@ import org.bukkit.craftbukkit.v1_20_R2.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_20_R2.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_20_R2.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R2.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_20_R2.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R2.potion.CraftPotionEffectType; @@ -287,11 +286,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).toBukkit(); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java index 8127b10475..fd0609fec9 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.3/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R3.java @@ -66,7 +66,6 @@ import org.bukkit.craftbukkit.v1_20_R3.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_20_R3.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_20_R3.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R3.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_20_R3.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R3.potion.CraftPotionEffectType; @@ -356,12 +355,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java index 3d8741e06e..fe6349f5fb 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20.5/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java @@ -66,7 +66,6 @@ import org.bukkit.craftbukkit.v1_20_R4.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_20_R4.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R4.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_20_R4.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R4.potion.CraftPotionEffectType; @@ -395,12 +394,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java index 6c51dfc906..5b46ef046a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-1.20/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R1.java @@ -119,7 +119,6 @@ import org.bukkit.craftbukkit.v1_20_R1.command.BukkitCommandWrapper; import org.bukkit.craftbukkit.v1_20_R1.command.VanillaCommandWrapper; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity; -import org.bukkit.craftbukkit.v1_20_R1.help.CustomHelpTopic; import org.bukkit.craftbukkit.v1_20_R1.help.SimpleHelpMap; import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R1.potion.CraftPotionEffectType; @@ -264,11 +263,6 @@ public final void createDispatcherFile(File file, CommandDispatcher cmdCtx, String key) throws CommandSyntaxException { return ResourceLocationArgument.getAdvancement(cmdCtx, key).bukkit; diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java index c062af30c5..67dbc73a53 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-nms/commandapi-bukkit-nms-common/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java @@ -314,11 +314,6 @@ public final String convert(Sound sound) { @Unimplemented(because = VERSION_SPECIFIC_IMPLEMENTATION, introducedIn = "1.19") public abstract void createDispatcherFile(File file, CommandDispatcher dispatcher) throws IOException; - @Override - @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "CustomHelpTopic") - public abstract HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, - String permission); - @Override @Unimplemented(because = VERSION_SPECIFIC_IMPLEMENTATION, introducedIn = "1.20.2") public abstract org.bukkit.advancement.Advancement getAdvancement(CommandContext cmdCtx, String key) diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 0ec388a63c..c8dbda5e3a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.16.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -598,11 +598,6 @@ public BukkitCommandSender getCommandSenderFromCommandS return null; } } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } @Override public Map getHelpMap() { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java index f22b3cfa05..2ba6bbd578 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.17/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -590,11 +590,6 @@ public BukkitCommandSender getCommandSenderFromCommandS return null; } } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } @Override public Map getHelpMap() { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 374c2c33bb..d73a0f586f 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.18/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -625,11 +625,6 @@ public BukkitCommandSender getCommandSenderFromCommandS return null; } } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } @Override public Map getHelpMap() { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index bfedc317e0..8c9ee3525e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -806,11 +806,6 @@ public BukkitCommandSender getCommandSenderFromCommandS // } // } // } - - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } @Override public Map getHelpMap() { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java index d9b3d39dd6..12731bc821 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.19.4/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -817,11 +817,6 @@ public BukkitCommandSender getCommandSenderFromCommandS // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 829c1f101b..aa647f5e23 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.2/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -841,11 +841,6 @@ public BukkitCommandSender getCommandSenderFromCommandS // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 8e0567abb5..a4f209f8ad 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.3/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -859,11 +859,6 @@ public int popFunctionCallbackResult() { // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 13965712b7..0f41dea65a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20.5/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -993,11 +993,6 @@ public int popFunctionCallbackResult() { // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java index 5697685b5b..20a584207a 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-impl-1.20/src/main/java/dev/jorel/commandapi/test/MockNMS.java @@ -815,11 +815,6 @@ public BukkitCommandSender getCommandSenderFromCommandS // } // } - @Override - public HelpTopic generateHelpTopic(String commandName, String shortDescription, String fullDescription, String permission) { - return baseNMS.generateHelpTopic(commandName, shortDescription, fullDescription, permission); - } - @Override public Map getHelpMap() { return helpMapTopics.get((HelpMapMock) Bukkit.getHelpMap()); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java index ac053a096c..0c4cd060ff 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandAPICommandRegisteredCommandTests.java @@ -8,12 +8,16 @@ import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.help.EditableHelpTopic; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.List; -import java.util.Optional; +import java.util.function.Predicate; import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.node; import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.children; @@ -70,9 +74,9 @@ void testRegisterHelpInformation() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.NONE, - Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>("short description", "full description", new String[]{"usage 1", "usage 2", "usage 3"}), commandNode("command", true).build() ); @@ -86,10 +90,10 @@ void testRegisterOpPermission() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.OP, - Optional.empty(), Optional.empty(), Optional.empty(), - commandNode("command", true).build() + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.OP).build() ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -102,10 +106,28 @@ void testRegisterStringPermission() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.fromString("permission"), - Optional.empty(), Optional.empty(), Optional.empty(), - commandNode("command", true).build() + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.fromString("permission")).build() + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterRequirement() { + Predicate requirement = sender -> sender instanceof Player; + + new CommandAPICommand("command") + .withRequirement(requirement) + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).requirements(requirement).build() ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -118,7 +140,7 @@ void testRegisterOneAlias() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -130,7 +152,7 @@ void testRegisterTwoAliases() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -141,7 +163,7 @@ void testRegisterNamespace() { .executesPlayer(P_EXEC) .register("custom"); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -181,6 +203,46 @@ void testRegisterTwoArguments() { ); } + @Test + void testRegisterArgumentPermissions() { + new CommandAPICommand("command") + .withArguments( + new StringArgument("noPermission"), + new StringArgument("opPermission").withPermission(CommandPermission.OP), + new StringArgument("stringPermission").withPermission(CommandPermission.fromString("permission")) + ) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("noPermission", StringArgument.class, false).withChildren( + node("opPermission", StringArgument.class, false).permission(CommandPermission.OP).withChildren( + node("stringPermission", StringArgument.class, true).permission(CommandPermission.fromString("permission")) + ))), + List.of("command:CommandAPICommand", "noPermission:StringArgument", "opPermission:StringArgument", "stringPermission:StringArgument") + ); + } + + @Test + void testRegisterArgumentRequirement() { + Predicate requirement = sender -> sender instanceof Player; + + new CommandAPICommand("command") + .withArguments(new StringArgument("string").withRequirement(requirement)) + .executesPlayer(P_EXEC) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("string", StringArgument.class, true).requirements(requirement) + ), + List.of("command:CommandAPICommand", "string:StringArgument") + ); + } + @Test void testRegisterMultiLiteralArguments() { new CommandAPICommand("command") @@ -442,15 +504,15 @@ void testRegisterSubcommandWithAliasesAndMultiLiteralArgument() { ) .register(); - List literal2 = children( + List> literal2 = children( node("literal2", MultiLiteralArgument.class, true).helpString("(c|d)") ); - List literal1 = children( + List> literal1 = children( node("literal1", MultiLiteralArgument.class, false).helpString("(a|b)").withChildren(literal2) ); - List subcommands = children( + List> subcommands = children( commandNode("subcommand", false).withChildren(literal1), commandNode("alias1", false).withChildren(literal1), commandNode("alias2", false).withChildren(literal1) @@ -571,8 +633,8 @@ void testRegisterTwoSeparateCommands() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); - RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); + RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); + RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); assertCreatedRegisteredCommands( command1.copyWithEmptyNamespace(), command1, @@ -608,33 +670,38 @@ void testRegisterMergeNamespaces() { new CommandAPICommand("command") .withArguments(new LiteralArgument("first")) .executesPlayer(P_EXEC) + .withAliases("first") .register("first"); new CommandAPICommand("command") .withArguments(new LiteralArgument("second")) .executesPlayer(P_EXEC) + .withAliases("second") .register("second"); - RegisteredCommand first = simpleRegisteredCommand( + RegisteredCommand first = simpleRegisteredCommand( "command", "first", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first") - ) + ), + "first" ); - RegisteredCommand second = simpleRegisteredCommand( + RegisteredCommand second = simpleRegisteredCommand( "command", "second", commandNode("command", false).withChildren( node("second", LiteralArgument.class, true).helpString("second") - ) + ), + "second" ); - RegisteredCommand merged = simpleRegisteredCommand( + RegisteredCommand merged = simpleRegisteredCommand( "command", "", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first"), node("second", LiteralArgument.class, true).helpString("second") - ) + ), + "first", "second" ); assertCreatedRegisteredCommands(merged, first, second); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTests.java index 16be82cfb9..11652686b8 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTests.java @@ -1,17 +1,27 @@ package dev.jorel.commandapi.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.help.HelpTopic; +import org.bukkit.permissions.PermissionAttachment; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import be.seeseemelk.mockbukkit.MockBukkit; import dev.jorel.commandapi.CommandAPICommand; +import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.CommandTree; import dev.jorel.commandapi.arguments.IntegerArgument; import dev.jorel.commandapi.arguments.LiteralArgument; @@ -38,9 +48,16 @@ public void tearDown() { } private void assertHelpTopicCreated(String name, String shortDescription, String fullDescription, CommandSender forWho) { + assertHelpTopicCreated("/" + name, name, shortDescription, fullDescription, forWho); + } + + private void assertHelpTopicCreated(String entryName, String topicName, String shortDescription, String fullDescription, CommandSender forWho) { // Check the help topic was added - HelpTopic helpTopic = server.getHelpMap().getHelpTopic(name); - assertNotNull(helpTopic, "Expected to find help topic called <" + name + ">, but null was found."); + HelpTopic helpTopic = server.getHelpMap().getHelpTopic(entryName); + assertNotNull(helpTopic, "Expected to find help topic called <" + entryName + ">, but null was found."); + + // Check the help topic name + assertEquals(topicName, helpTopic.getName()); // Check the short description assertEquals(shortDescription, helpTopic.getShortText()); @@ -66,7 +83,7 @@ void testRegisterCommandWithHelp() { // Short and full description are inserted assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -89,7 +106,7 @@ void testRegisterCommandWithShortDescription() { // Short description appears at the start of full text because that's how `CustomHelpTopic` works assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -111,7 +128,7 @@ void testRegisterCommandFullDescription() { // Full description replaces short description when not specified assertHelpTopicCreated( - "/test", + "test", "full description", """ full description @@ -133,7 +150,7 @@ void testRegisterCommandNoDescription() { // Check default message assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. @@ -154,15 +171,11 @@ void testRegisterWithRemovedUsage() { Player player = server.addPlayer("APlayer"); // Usage can be removed - // Note that the result returned by `getFullText` has a trailing \n because `CustomHelpTopic` uses - // `shortText + "\n" + fullText`. In this situation, we have no full description, no usage, and no - // aliases, so the `fullText` generated by the CommandAPI is acutally an empty string "". assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ - A command by the CommandAPITest plugin. - """, + A command by the CommandAPITest plugin.""", player ); } @@ -180,7 +193,7 @@ void testRegisterWithOneUsage() { // Usage generation can be overriden with one line assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. @@ -206,7 +219,7 @@ void testRegisterWithMultipleUsage() { // Usage generation can be overriden with multiple lines assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. @@ -218,6 +231,240 @@ void testRegisterWithMultipleUsage() { ); } + @Test + void testRegisterDynamicShortDescription() { + String[] shortDescription = new String[]{""}; + + new CommandAPICommand("test") + .withShortDescription(() -> Optional.of(shortDescription[0])) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player = server.addPlayer(); + + // Check changing shortDescription + shortDescription[0] = "First description"; + assertHelpTopicCreated( + "test", + "First description", + """ + First description + &6Usage: &f/test""", + player + ); + + shortDescription[0] = "Second description"; + assertHelpTopicCreated( + "test", + "Second description", + """ + Second description + &6Usage: &f/test""", + player + ); + } + + @Test + void testRegisterDynamicFullDescription() { + new CommandAPICommand("test") + .withFullDescription(forWho -> { + if (forWho == null) return Optional.empty(); + + return Optional.of("Special full text just for " + (forWho instanceof Player player ? player.getName() : forWho)); + }) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player1 = server.addPlayer("Player1"); + Player player2 = server.addPlayer("Player2"); + + // Check changing text for different CommandSenders + assertHelpTopicCreated( + "test", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Description: &fSpecial full text just for Player1 + &6Usage: &f/test""", + player1 + ); + assertHelpTopicCreated( + "test", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Description: &fSpecial full text just for Player2 + &6Usage: &f/test""", + player2 + ); + } + + @Test + void testRegisterDynamicUsage() { + new CommandAPICommand("test") + .withUsage((forWho, argumentTree) -> { + if (forWho == null) return Optional.empty(); + + return Optional.of(new String[]{ + "/test " + (forWho instanceof Player player ? player.getName() : forWho) + }); + }) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player1 = server.addPlayer("Player1"); + Player player2 = server.addPlayer("Player2"); + + // Check changing text for different CommandSenders + assertHelpTopicCreated( + "test", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/test Player1""", + player1 + ); + assertHelpTopicCreated( + "test", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/test Player2""", + player2 + ); + } + + @Test + void testRegisterCustomHelpTopic() { + HelpTopic helpTopic = new HelpTopic() { + { + this.name = "custom name"; + this.shortText = "short description"; + } + + @Override + public String getFullText(CommandSender forWho) { + return "Special full text just for " + (forWho instanceof Player player ? player.getName() : forWho); + } + + @Override + public boolean canSee(CommandSender sender) { + return true; + } + }; + + // `CommandAPICommand#withHelp(HelpTopic)` and `CommandTree#withHelp(HelpTopic)` are separate methods, so check both for coverage + new CommandAPICommand("commandAPICommand") + // Generally, calls to these methods before `withHelp(HelpTopic)` will be overridden + .withShortDescription("Overridden") + .withFullDescription("Overridden") + .withHelp("Overridden", "Overridden") + .withUsage("Overridden") + .withShortDescription(() -> Optional.of("Overridden")) + .withFullDescription(forWho -> Optional.of("Overidden")) + .withUsage((forWho, argumentTree) -> Optional.of(new String[]{"Overidden"})) + // Add the custom help + .withHelp(helpTopic) + // Generally, calls to these methods after `withHelp(HelpTopic)` should be ignored + .withShortDescription("Overridden") + .withFullDescription("Overridden") + .withHelp("Overridden", "Overridden") + .withUsage("Overridden") + .withShortDescription(() -> Optional.of("Overridden")) + .withFullDescription(forWho -> Optional.of("Overidden")) + .withUsage((forWho, argumentTree) -> Optional.of(new String[]{"Overidden"})) + // Finish building + .withAliases("alias") // Also check alias + .executesPlayer(P_EXEC) + .register(); + + new CommandTree("commandTree") + // Generally, calls to these methods before `withHelp(HelpTopic)` will be overridden + .withShortDescription("Overridden") + .withFullDescription("Overridden") + .withHelp("Overridden", "Overridden") + .withUsage("Overridden") + .withShortDescription(() -> Optional.of("Overridden")) + .withFullDescription(forWho -> Optional.of("Overidden")) + .withUsage((forWho, argumentTree) -> Optional.of(new String[]{"Overidden"})) + // Add the custom help + .withHelp(helpTopic) + // Generally, calls to these methods after `withHelp(HelpTopic)` should be ignored + .withShortDescription("Overridden") + .withFullDescription("Overridden") + .withHelp("Overridden", "Overridden") + .withUsage("Overridden") + .withShortDescription(() -> Optional.of("Overridden")) + .withFullDescription(forWho -> Optional.of("Overidden")) + .withUsage((forWho, argumentTree) -> Optional.of(new String[]{"Overidden"})) + // Finish building + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player1 = server.addPlayer("Player1"); + Player player2 = server.addPlayer("Player2"); + + // Custom HelpTopic allows changing text for different CommandSenders + assertHelpTopicCreated( + "/commandAPICommand", + "custom name", + "short description", + """ + Special full text just for Player1""", + player1 + ); + assertHelpTopicCreated( + "/commandAPICommand", + "custom name", + "short description", + """ + Special full text just for Player2""", + player2 + ); + assertHelpTopicCreated( + "/commandTree", + "custom name", + "short description", + """ + Special full text just for Player1""", + player1 + ); + assertHelpTopicCreated( + "/commandTree", + "custom name", + "short description", + """ + Special full text just for Player2""", + player2 + ); + + // Aliases should also use the custom HelpTopic + assertHelpTopicCreated( + "/alias", + "custom name", + "short description", + """ + Special full text just for Player1""", + player1 + ); + assertHelpTopicCreated( + "/alias", + "custom name", + "short description", + """ + Special full text just for Player2""", + player2 + ); + } + @Test void testRegisterCommandWithHelpWithAliases() { new CommandAPICommand("test") @@ -232,7 +479,7 @@ void testRegisterCommandWithHelpWithAliases() { // Check the main help topic was added assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -246,7 +493,7 @@ void testRegisterCommandWithHelpWithAliases() { // The alias section of each alias does not include itself, but does include the main name // Otherwise everything is the same as the main command assertHelpTopicCreated( - "/othertest", + "othertest", "short description", """ short description @@ -256,7 +503,7 @@ void testRegisterCommandWithHelpWithAliases() { player ); assertHelpTopicCreated( - "/othercommand", + "othercommand", "short description", """ short description @@ -282,7 +529,7 @@ void testRegisterCommandWithMultipleArguments() { // Multiple arguments are stacked in the usage assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -295,13 +542,13 @@ void testRegisterCommandWithMultipleArguments() { @Test void testRegisterMultipleCommands() { new CommandAPICommand("test") - .withHelp("short description", "full description") + .withHelp("short description 1", "full description 1") .withArguments(new StringArgument("arg1")) .executesPlayer(P_EXEC) .register(); new CommandAPICommand("test") - .withHelp("short description", "full description") + .withHelp("short description 2", "full description 2") .withArguments(new StringArgument("arg1")) .withArguments(new IntegerArgument("arg2")) .executesPlayer(P_EXEC) @@ -311,13 +558,13 @@ void testRegisterMultipleCommands() { enableServer(); Player player = server.addPlayer("APlayer"); - // Usage should be merged + // The first command registered gets priority, but usage should be merged assertHelpTopicCreated( - "/test", - "short description", + "test", + "short description 1", """ - short description - &6Description: &ffull description + short description 1 + &6Description: &ffull description 1 &6Usage: &f - /test - /test """, @@ -349,7 +596,7 @@ void testRegisterDeepBranches() { // Each executable node should appear in the usage assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. @@ -378,64 +625,17 @@ void testRegisterLiteralArguments() { enableServer(); Player player = server.addPlayer("APlayer"); - // MultiLiteralArgument unpacks to multiple LiteralArguments - // LiteralArgument has a differnt help string, using its literal rather than its node name + // Literal and MultiLiteral arguments have different help strings, using thier literals rather than thier node name assertHelpTopicCreated( - "/test", + "test", "A command by the CommandAPITest plugin.", """ A command by the CommandAPITest plugin. - &6Usage: &f - - /test a d - - /test b d - - /test c d """, + &6Usage: &f/test (a|b|c) d """, player ); } - @Test - void testRegisterCustomHelpTopic() { - new CommandAPICommand("test") - .withHelp(new HelpTopic() { - { - this.shortText = "short description"; - } - - @Override - public String getFullText(CommandSender forWho) { - return "Special full text just for " + (forWho instanceof Player player ? player.getName() : forWho); - } - - @Override - public boolean canSee(CommandSender sender) { - return true; - } - }) - .executesPlayer(P_EXEC) - .register(); - - // Enable server to register help topics - enableServer(); - Player player1 = server.addPlayer("Player1"); - Player player2 = server.addPlayer("Player2"); - - // Custom HelpTopic allows changing text for different CommandSenders - assertHelpTopicCreated( - "/test", - "short description", - """ - Special full text just for Player1""", - player1 - ); - assertHelpTopicCreated( - "/test", - "short description", - """ - Special full text just for Player2""", - player2 - ); - } - @Test void testRegisterAfterServerEnabled() { // Enable server early @@ -456,7 +656,7 @@ void testRegisterAfterServerEnabled() { // Ensure main and alias help topics exist assertHelpTopicCreated( - "/test", + "test", "short description", """ short description @@ -466,7 +666,7 @@ void testRegisterAfterServerEnabled() { player ); assertHelpTopicCreated( - "/alias", + "alias", "short description", """ short description @@ -476,4 +676,351 @@ void testRegisterAfterServerEnabled() { player ); } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + // Test enabling before and after registering since these take different code paths + void testRegisterMergeNamespaces(boolean enableBeforeRegistering) { + if (enableBeforeRegistering) enableServer(); + + new CommandAPICommand("test") + .withHelp("first short", "first full") + .withArguments(new LiteralArgument("first")) + .executesPlayer(P_EXEC) + .withAliases("first") + .register("first"); + + new CommandAPICommand("test") + .withHelp("second short", "second full") + .withArguments(new LiteralArgument("second")) + .executesPlayer(P_EXEC) + .withAliases("second") + .register("second"); + + if (!enableBeforeRegistering) enableServer(); + + Player player = server.addPlayer("APlayer"); + + // Unnamespaced help should merge usage and aliases together, which is how the command appears for execution + // The command registered first determines the descriptions + assertHelpTopicCreated( + "test", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f + - /test first + - /test second + &6Aliases: &ffirst, second""", + player + ); + assertHelpTopicCreated( + "first", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f + - /test first + - /test second + &6Aliases: &fsecond, test""", + player + ); + assertHelpTopicCreated( + "second", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f + - /test first + - /test second + &6Aliases: &ffirst, test""", + player + ); + + // Namespaced version of help should remain separated + assertHelpTopicCreated( + "first:test", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f/test first + &6Aliases: &ffirst""", + player + ); + assertHelpTopicCreated( + "first:first", + "first short", + """ + first short + &6Description: &ffirst full + &6Usage: &f/test first + &6Aliases: &ftest""", + player + ); + assertNull(server.getHelpMap().getHelpTopic("/first:second")); + + assertHelpTopicCreated( + "second:test", + "second short", + """ + second short + &6Description: &fsecond full + &6Usage: &f/test second + &6Aliases: &fsecond""", + player + ); + assertHelpTopicCreated( + "second:second", + "second short", + """ + second short + &6Description: &fsecond full + &6Usage: &f/test second + &6Aliases: &ftest""", + player + ); + assertNull(server.getHelpMap().getHelpTopic("/second:first")); + } + + @ParameterizedTest + @ValueSource(strings = {"minecraft", "custom"}) + // Test with the default ("minecraft") and a custom namespace + void testRegisterConflictWithPluginCommand(String namespace) { + // Register plugin command + // MockBukkit does not simulate `org.bukkit.craftbukkit.help.SimpleHelpMap#initializeCommands`, + // which would usually create a help topic for our plugin command. + // This is acutally kinda useful, because we can make sure we wouldn't override the help topic + // by making sure a help entry is still not there. + MockBukkit.loadWith(CommandHelpTestsPlugin.class, CommandHelpTestsPlugin.pluginYaml()); + + // Register conflicting CommandAPI commands + new CommandAPICommand("registeredCommand") + .withAliases("unregisteredAlias") + .executesPlayer(P_EXEC) + .register(namespace); + + new CommandAPICommand("unregisteredCommand") + .withAliases("registeredAlias") + .executesPlayer(P_EXEC) + .register(namespace); + + // Enable server to register help topics + enableServer(); + Player player = server.addPlayer("APlayer"); + + // Unnamespaced help topics are taken by the plugin command by default + assertNull(server.getHelpMap().getHelpTopic("/registeredCommand")); + assertNull(server.getHelpMap().getHelpTopic("/registeredAlias")); + + // Commands that don't conflict still show up unnamespaced + assertHelpTopicCreated( + "unregisteredCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/unregisteredCommand + &6Aliases: &fregisteredAlias""", + player + ); + assertHelpTopicCreated( + "unregisteredAlias", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/registeredCommand + &6Aliases: &fregisteredCommand""", + player + ); + + // Our help topics defer to the namespaced version so they're at least accessible somewhere + assertHelpTopicCreated( + namespace + ":registeredCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/registeredCommand + &6Aliases: &funregisteredAlias""", + player + ); + assertHelpTopicCreated( + namespace + ":registeredAlias", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/unregisteredCommand + &6Aliases: &funregisteredCommand""", + player + ); + + // Namespaced versions of commands that don't conflict also show up as usual + assertHelpTopicCreated( + namespace + ":unregisteredCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/unregisteredCommand + &6Aliases: &fregisteredAlias""", + player + ); + assertHelpTopicCreated( + namespace + ":unregisteredAlias", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/registeredCommand + &6Aliases: &fregisteredCommand""", + player + ); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + // Test with permission normal and negated + void testOpPermissionsHidingHelp(boolean needsOP) { + CommandPermission permission = CommandPermission.OP; + permission = needsOP ? permission : permission.negate(); + + new CommandAPICommand("opCommand") + .withPermission(permission) + .executesPlayer(P_EXEC) + .register(); + + new CommandTree("opArgument") + .then(new StringArgument("string").withPermission(permission).executesPlayer(P_EXEC)) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player = server.addPlayer("APlayer"); + + // Player dose not have permission + player.setOp(!needsOP); + + // Without permission, usage is hidden + assertHelpTopicCreated( + "opCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin.""", + player + ); + assertHelpTopicCreated( + "opArgument", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/opArgument""", + player + ); + + // `canSee` just checks the root node + assertFalse(server.getHelpMap().getHelpTopic("/opCommand").canSee(player)); + assertTrue(server.getHelpMap().getHelpTopic("/opArgument").canSee(player)); + + // Player has permission + player.setOp(needsOP); + + // With permission, usage is visible + assertHelpTopicCreated( + "opCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/opCommand""", + player + ); + assertHelpTopicCreated( + "opArgument", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f + - /opArgument + - /opArgument """, + player + ); + + assertTrue(server.getHelpMap().getHelpTopic("/opCommand").canSee(player)); + assertTrue(server.getHelpMap().getHelpTopic("/opArgument").canSee(player)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + // Test with permission normal and negated + void testStringPermissionsHidingHelp(boolean needsPermission) { + CommandPermission permission = CommandPermission.fromString("permission"); + permission = needsPermission ? permission : permission.negate(); + + new CommandAPICommand("permissionCommand") + .withPermission(permission) + .executesPlayer(P_EXEC) + .register(); + + new CommandTree("permissionArgument") + .then(new StringArgument("string").withPermission(permission).executesPlayer(P_EXEC)) + .executesPlayer(P_EXEC) + .register(); + + // Enable server to register help topics + enableServer(); + Player player = server.addPlayer("APlayer"); + PermissionAttachment permissionAttachment = player.addAttachment(super.plugin); + + // Player dose not have permission + permissionAttachment.setPermission("permission", !needsPermission); + player.recalculatePermissions(); + + // Without permission, usage is hidden + assertHelpTopicCreated( + "permissionCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin.""", + player + ); + assertHelpTopicCreated( + "permissionArgument", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/permissionArgument""", + player + ); + + // `canSee` just checks the root node + assertFalse(server.getHelpMap().getHelpTopic("/permissionCommand").canSee(player)); + assertTrue(server.getHelpMap().getHelpTopic("/permissionArgument").canSee(player)); + + // Player has permission + permissionAttachment.setPermission("permission", needsPermission); + player.recalculatePermissions(); + + // With permission, usage is visible + assertHelpTopicCreated( + "permissionCommand", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f/permissionCommand""", + player + ); + assertHelpTopicCreated( + "permissionArgument", + "A command by the CommandAPITest plugin.", + """ + A command by the CommandAPITest plugin. + &6Usage: &f + - /permissionArgument + - /permissionArgument """, + player + ); + + assertTrue(server.getHelpMap().getHelpTopic("/permissionCommand").canSee(player)); + assertTrue(server.getHelpMap().getHelpTopic("/permissionArgument").canSee(player)); + } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java new file mode 100644 index 0000000000..083a296a97 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandHelpTestsPlugin.java @@ -0,0 +1,35 @@ +package dev.jorel.commandapi.test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; + +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.java.JavaPluginLoader; + +public class CommandHelpTestsPlugin extends JavaPlugin { + // Additional constructors required for MockBukkit + public CommandHelpTestsPlugin() { + super(); + } + + public CommandHelpTestsPlugin(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { + super(loader, description, dataFolder, file); + } + + public static InputStream pluginYaml() { + return new ByteArrayInputStream(""" + name: CommandHelpTestsPlugin + main: dev.jorel.commandapi.test.CommandHelpTestsPlugin + version: 0.0.1 + description: A mock Bukkit plugin for CommandAPI testing + author: Will Kroboth + website: https://www.jorel.dev/CommandAPI/ + api-version: 1.13 + commands: + registeredCommand: + registeredAlias: + """.getBytes()); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java index 9e772b2003..07fd216ac3 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/CommandTreeRegisteredCommandTests.java @@ -7,12 +7,17 @@ import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.StringArgument; +import dev.jorel.commandapi.help.EditableHelpTopic; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.google.common.base.Predicate; + import java.util.List; -import java.util.Optional; import static dev.jorel.commandapi.test.RegisteredCommandTestBase.NodeBuilder.node; @@ -68,9 +73,9 @@ void testRegisterHelpInformation() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.NONE, - Optional.of("short description"), Optional.of("full description"), Optional.of(new String[]{"usage 1", "usage 2", "usage 3"}), + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>("short description", "full description", new String[]{"usage 1", "usage 2", "usage 3"}), commandNode("command", true).build() ); @@ -84,10 +89,10 @@ void testRegisterOpPermission() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.OP, - Optional.empty(), Optional.empty(), Optional.empty(), - commandNode("command", true).build() + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.OP).build() ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -100,10 +105,28 @@ void testRegisterStringPermission() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = new RegisteredCommand( - "command", new String[0], "minecraft", CommandPermission.fromString("permission"), - Optional.empty(), Optional.empty(), Optional.empty(), - commandNode("command", true).build() + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).permission(CommandPermission.fromString("permission")).build() + ); + + assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); + } + + @Test + void testRegisterRequirement() { + Predicate requirement = sender -> sender instanceof Player; + + new CommandTree("command") + .withRequirement(requirement) + .executesPlayer(P_EXEC) + .register(); + + RegisteredCommand expectedCommand = new RegisteredCommand<>( + "command", new String[0], "minecraft", + new EditableHelpTopic<>(), + commandNode("command", true).requirements(requirement).build() ); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -116,7 +139,7 @@ void testRegisterOneAlias() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1"); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -128,7 +151,7 @@ void testRegisterTwoAliases() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "minecraft", commandNode("command", true), "alias1", "alias2"); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -139,7 +162,7 @@ void testRegisterNamespace() { .executesPlayer(P_EXEC) .register("custom"); - RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); + RegisteredCommand expectedCommand = simpleRegisteredCommand("command", "custom", commandNode("command", true)); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); } @@ -176,6 +199,51 @@ void testRegisterTwoBranches() { ); } + @Test + void testRegisterArgumentPermissions() { + new CommandTree("command") + .then( + new StringArgument("noPermission") + .then( + new StringArgument("opPermission") + .withPermission(CommandPermission.OP) + .then( + new StringArgument("stringPermission") + .withPermission(CommandPermission.fromString("permission")) + .executesPlayer(P_EXEC) + ) + ) + ) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("noPermission", StringArgument.class, false).withChildren( + node("opPermission", StringArgument.class, false).permission(CommandPermission.OP).withChildren( + node("stringPermission", StringArgument.class, true).permission(CommandPermission.fromString("permission")) + ))), + List.of("command:CommandTree", "noPermission:StringArgument", "opPermission:StringArgument", "stringPermission:StringArgument") + ); + } + + @Test + void testRegisterArgumentRequirement() { + Predicate requirement = sender -> sender instanceof Player; + + new CommandTree("command") + .then(new StringArgument("string").withRequirement(requirement).executesPlayer(P_EXEC)) + .register(); + + assertCreatedSimpleRegisteredCommand( + "command", + commandNode("command", false).withChildren( + node("string", StringArgument.class, true).requirements(requirement) + ), + List.of("command:CommandTree", "string:StringArgument") + ); + } + @Test void testRegisterMultiLiteralArguments() { new CommandTree("command") @@ -368,8 +436,8 @@ void testRegisterTwoSeparateCommands() { .executesPlayer(P_EXEC) .register(); - RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); - RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); + RegisteredCommand command1 = simpleRegisteredCommand("command1", "minecraft", commandNode("command1", true)); + RegisteredCommand command2 = simpleRegisteredCommand("command2", "minecraft", commandNode("command2", true)); assertCreatedRegisteredCommands( command1.copyWithEmptyNamespace(), command1, @@ -443,32 +511,37 @@ void testRegisterMergeDifferentLengthBranches() { void testRegisterMergeNamespaces() { new CommandTree("command") .then(new LiteralArgument("first").executesPlayer(P_EXEC)) + .withAliases("first") .register("first"); new CommandTree("command") .then(new LiteralArgument("second").executesPlayer(P_EXEC)) + .withAliases("second") .register("second"); - RegisteredCommand first = simpleRegisteredCommand( + RegisteredCommand first = simpleRegisteredCommand( "command", "first", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first") - ) + ), + "first" ); - RegisteredCommand second = simpleRegisteredCommand( + RegisteredCommand second = simpleRegisteredCommand( "command", "second", commandNode("command", false).withChildren( node("second", LiteralArgument.class, true).helpString("second") - ) + ), + "second" ); - RegisteredCommand merged = simpleRegisteredCommand( + RegisteredCommand merged = simpleRegisteredCommand( "command", "", commandNode("command", false).withChildren( node("first", LiteralArgument.class, true).helpString("first"), node("second", LiteralArgument.class, true).helpString("second") - ) + ), + "first", "second" ); assertCreatedRegisteredCommands(merged, first, second); diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java index dc6ecb5eb7..c25cff4add 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/OnEnableTests.java @@ -178,7 +178,7 @@ void testOnEnableRegisterAndUnregisterCommand() { short description &6Description: &ffull description &6Usage: &f/command - &6Aliases: &falias1, alias2"""), topic.getFullText(null)); + &6Aliases: &falias1, alias2"""), topic.getFullText(runCommandsPlayer)); // Make sure commands run correctly diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java index a0a1bc5105..cb0c316e78 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/src/test/java/dev/jorel/commandapi/test/RegisteredCommandTestBase.java @@ -7,8 +7,9 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.Optional; +import java.util.function.Predicate; +import org.bukkit.command.CommandSender; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -16,6 +17,7 @@ import dev.jorel.commandapi.CommandPermission; import dev.jorel.commandapi.RegisteredCommand; import dev.jorel.commandapi.RegisteredCommand.Node; +import dev.jorel.commandapi.help.EditableHelpTopic; public abstract class RegisteredCommandTestBase extends TestBase { @@ -39,7 +41,7 @@ public void tearDown() { @SafeVarargs public final void assertCreatedSimpleRegisteredCommand(String name, NodeBuilder args, List... argsAsStr) { - RegisteredCommand expectedCommand = simpleRegisteredCommand(name, "minecraft", args); + RegisteredCommand expectedCommand = simpleRegisteredCommand(name, "minecraft", args); assertCreatedRegisteredCommands(expectedCommand.copyWithEmptyNamespace(), expectedCommand); @@ -47,10 +49,10 @@ public final void assertCreatedSimpleRegisteredCommand(String name, NodeBuilder assertEquals(Arrays.asList(argsAsStr), CommandAPI.getRegisteredCommands().get(0).rootNode().argsAsStr()); } - public RegisteredCommand simpleRegisteredCommand(String name, String namespace, NodeBuilder args, String... aliases) { - return new RegisteredCommand( - name, aliases, namespace, CommandPermission.NONE, - Optional.empty(), Optional.empty(), Optional.empty(), + public RegisteredCommand simpleRegisteredCommand(String name, String namespace, NodeBuilder args, String... aliases) { + return new RegisteredCommand<>( + name, aliases, namespace, + new EditableHelpTopic<>(), args.build() ); } @@ -60,8 +62,8 @@ public static NodeBuilder node(String name, Class clazz, boolean executable) return new NodeBuilder(name, clazz, executable); } - public static List children(NodeBuilder... children) { - List result = new ArrayList<>(children.length); + public static List> children(NodeBuilder... children) { + List> result = new ArrayList<>(children.length); for (NodeBuilder child : children) { result.add(child.build()); } @@ -73,7 +75,10 @@ public static List children(NodeBuilder... children) { private final boolean executable; private String helpString; - private final List children; + private CommandPermission permission = CommandPermission.NONE; + private Predicate requirements = sender -> true; + + private final List> children; public NodeBuilder(String nodeName, Class clazz, boolean executable) { this.nodeName = nodeName; @@ -89,6 +94,16 @@ public NodeBuilder helpString(String helpString) { return this; } + public NodeBuilder permission(CommandPermission permission) { + this.permission = permission; + return this; + } + + public NodeBuilder requirements(Predicate requirements) { + this.requirements = requirements; + return this; + } + public NodeBuilder withChildren(NodeBuilder... children) { for (NodeBuilder child : children) { this.children.add(child.build()); @@ -96,23 +111,25 @@ public NodeBuilder withChildren(NodeBuilder... children) { return this; } - public NodeBuilder withChildren(Node... children) { + @SafeVarargs + public final NodeBuilder withChildren(Node... children) { return withChildren(Arrays.asList(children)); } - public NodeBuilder withChildren(List children) { + public NodeBuilder withChildren(List> children) { this.children.addAll(children); return this; } - public Node build() { - return new Node(nodeName, className, helpString, executable, children); + public Node build() { + return new Node(nodeName, className, helpString, executable, permission, requirements, children); } } - public void assertCreatedRegisteredCommands(RegisteredCommand... commands) { - List expectedCommands = Arrays.asList(commands); - List actualCommands = CommandAPI.getRegisteredCommands(); + @SafeVarargs + public final void assertCreatedRegisteredCommands(RegisteredCommand... commands) { + List> expectedCommands = Arrays.asList(commands); + List> actualCommands = CommandAPI.getRegisteredCommands(); if (expectedCommands.size() != actualCommands.size()) { StringBuilder builder = new StringBuilder(); @@ -127,8 +144,8 @@ public void assertCreatedRegisteredCommands(RegisteredCommand... commands) { } for (int i = 0; i < expectedCommands.size(); i++) { - RegisteredCommand expectedCommand = expectedCommands.get(i); - RegisteredCommand actualCommand = actualCommands.get(i); + RegisteredCommand expectedCommand = expectedCommands.get(i); + RegisteredCommand actualCommand = actualCommands.get(i); if (!Objects.equals(expectedCommand, actualCommand)) { StringBuilder builder = new StringBuilder(); @@ -147,14 +164,14 @@ public void assertCreatedRegisteredCommands(RegisteredCommand... commands) { } } - private void addRegisteredCommandList(StringBuilder builder, List commands) { + private void addRegisteredCommandList(StringBuilder builder, List> commands) { if (commands.isEmpty()) { builder.append("[]"); return; } builder.append("[\n"); - for (RegisteredCommand command : commands) { + for (RegisteredCommand command : commands) { builder.append("\t"); builder.append(command); builder.append("\n"); diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index 8da4c35299..fedbaaf446 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -219,7 +219,7 @@ public void preCommandRegistration(String commandName) { } @Override - public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { + public void postCommandRegistration(RegisteredCommand registeredCommand, LiteralCommandNode resultantNode, List> aliasNodes) { // Nothing to do } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java index a49151f55e..42e40ac687 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/LiteralArgument.java @@ -153,7 +153,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return Literal.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames,terminalExecutorCreator); } } diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java index cef66a147b..766e5188ae 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/MultiLiteralArgument.java @@ -110,7 +110,7 @@ public String parseArgument(CommandContext cmdCtx, String key, } @Override - public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { + public NodeInformation linkNode(NodeInformation previousNodeInformation, CommandNode rootNode, List> previousArguments, List previousArgumentNames, Function>, Command> terminalExecutorCreator) { return MultiLiteral.super.linkNode(previousNodeInformation, rootNode, previousArguments, previousArgumentNames, terminalExecutorCreator); } }