// // MIT License // // Copyright (c) 2020 Alexander Söderberg & Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // package cloud.commandframework; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.StaticArgument; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.Template; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Opinionated extension of {@link CommandHelpHandler} for Minecraft * * @param Command sender type */ public final class MinecraftHelp { /* General help */ public static final String MESSAGE_HELP_HEADER = "help_header"; public static final String MESSAGE_HELP_FOOTER = "help_footer"; /* Query specific */ public static final String MESSAGE_QUERY_QUERY = "help_query_query"; public static final String MESSAGE_QUERY_AVAILABLE_COMMANDS = "help_query_available_comments"; public static final String MESSAGE_QUERY_COMMAND_SYNTAX = "help_query_command_syntax"; public static final String MESSAGE_QUERY_COMMAND_SYNTAX_LAST = "help_query_command_syntax_last"; public static final String MESSAGE_QUERY_LONGEST_PATH = "help_query_longest_path"; public static final String MESSAGE_QUERY_SUGGESTION = "help_query_suggestion"; public static final String MESSAGE_QUERY_VERBOSE_SYNTAX = "help_query_verbose_syntax"; public static final String MESSAGE_QUERY_VERBOSE_DESCRIPTION = "help_query_verbose_description"; public static final String MESSAGE_QUERY_VERBOSE_ARGS = "help_query_verbose_args"; public static final String MESSAGE_QUERY_VERBOSE_OPTIONAL = "help_query_verbose_optional"; public static final String MESSAGE_QUERY_VERBOSE_REQUIRED = "help_query_verbose_required"; public static final String MESSAGE_QUERY_VERBOSE_LITERAL = "help_query_verbose_literal"; private final MiniMessage miniMessage = MiniMessage.builder().build(); private final Map messageMap = new HashMap<>(); private final AudienceProvider audienceProvider; private final CommandManager commandManager; private final String commandPrefix; /** * Construct a new Minecraft help instance * * @param commandPrefix Command that was used to trigger the help menu. Used to help insertion generation * @param audienceProvider Provider that maps the command sender type to {@link Audience} * @param commandManager Command manager instance */ public MinecraftHelp(final @NonNull String commandPrefix, final @NonNull AudienceProvider audienceProvider, final @NonNull CommandManager commandManager) { this.commandPrefix = commandPrefix; this.audienceProvider = audienceProvider; this.commandManager = commandManager; /* Default messages */ this.messageMap.put(MESSAGE_HELP_HEADER, "------------ Help ------------"); this.messageMap.put(MESSAGE_HELP_FOOTER, "----------------------------"); this.messageMap.put(MESSAGE_QUERY_QUERY, "Showing search results for query: \"/\""); this.messageMap.put(MESSAGE_QUERY_AVAILABLE_COMMANDS, "└─ Available Commands:"); this.messageMap.put(MESSAGE_QUERY_COMMAND_SYNTAX, " ├─ " + "\">/"); this.messageMap.put(MESSAGE_QUERY_COMMAND_SYNTAX_LAST, " └─ " + "\">/"); this.messageMap.put(MESSAGE_QUERY_LONGEST_PATH, "└─ /"); this.messageMap.put(MESSAGE_QUERY_SUGGESTION, " >Click to show help for this command\">" + ""); this.messageMap.put(MESSAGE_QUERY_VERBOSE_SYNTAX, "└─ Command:" + " /"); this.messageMap.put(MESSAGE_QUERY_VERBOSE_DESCRIPTION, " ├─" + " Description: "); this.messageMap.put(MESSAGE_QUERY_VERBOSE_ARGS, " └─ Args:"); this.messageMap.put(MESSAGE_QUERY_VERBOSE_OPTIONAL, " " + "(Optional) - "); this.messageMap.put(MESSAGE_QUERY_VERBOSE_REQUIRED, " " + "- "); this.messageMap.put(MESSAGE_QUERY_VERBOSE_LITERAL, ""); } /** * Get the command manager instance * * @return Command manager */ public @NonNull CommandManager getCommandManager() { return this.commandManager; } /** * Get the audience provider that was used to create this instance * * @return Audience provider */ public @NonNull AudienceProvider getAudienceProvider() { return this.audienceProvider; } /** * Map a command sender to an {@link Audience} * * @param sender Sender to map * @return Mapped audience */ public @NonNull Audience getAudience(final @NonNull C sender) { return this.audienceProvider.apply(sender); } /** * Configure a message * * @param key Message key * @param message Message */ public void setMessage(final @NonNull String key, final @NonNull String message) { this.messageMap.put(key, message); } /** * Query commands and send the results to the recipient * * @param query Command query (without leading '/') * @param recipient Recipient */ public void queryCommands(final @NonNull String query, final @NonNull C recipient) { final Audience audience = this.getAudience(recipient); audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_HELP_HEADER))); this.printTopic(recipient, query, this.commandManager.getCommandHelpHandler().queryHelp(recipient, query)); audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_HELP_FOOTER))); } private void printTopic(final @NonNull C sender, final @NonNull String query, final CommandHelpHandler.@NonNull HelpTopic helpTopic) { this.getAudience(sender).sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_QUERY), Template.of("query", query))); if (helpTopic instanceof CommandHelpHandler.IndexHelpTopic) { this.printIndexHelpTopic(sender, (CommandHelpHandler.IndexHelpTopic) helpTopic); } else if (helpTopic instanceof CommandHelpHandler.MultiHelpTopic) { this.printMultiHelpTopic(sender, (CommandHelpHandler.MultiHelpTopic) helpTopic); } else if (helpTopic instanceof CommandHelpHandler.VerboseHelpTopic) { this.printVerboseHelpTopic(sender, (CommandHelpHandler.VerboseHelpTopic) helpTopic); } else { throw new IllegalArgumentException("Unknown help topic type"); } } private void printIndexHelpTopic(final @NonNull C sender, final CommandHelpHandler.@NonNull IndexHelpTopic helpTopic) { final Audience audience = this.getAudience(sender); audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_AVAILABLE_COMMANDS))); final Iterator> iterator = helpTopic.getEntries().iterator(); while (iterator.hasNext()) { final CommandHelpHandler.VerboseHelpEntry entry = iterator.next(); final String description = entry.getDescription().isEmpty() ? "Click to show help for this command" : entry.getDescription(); String message = this.messageMap.get(iterator.hasNext() ? MESSAGE_QUERY_COMMAND_SYNTAX : MESSAGE_QUERY_COMMAND_SYNTAX_LAST); final String suggestedCommand = entry.getSyntaxString() .replace("<", "") .replace(">", "") .replace("[", "") .replace("]", ""); message = message.replace("", entry.getSyntaxString()) .replace("", description) .replace("cmdprefix", this.commandPrefix + ' ' + suggestedCommand); audience.sendMessage(this.miniMessage.parse(message)); } } private void printMultiHelpTopic(final @NonNull C sender, final CommandHelpHandler.@NonNull MultiHelpTopic helpTopic) { final Audience audience = this.getAudience(sender); audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_LONGEST_PATH), Template.of("command", helpTopic.getLongestPath()))); final int headerIndentation = helpTopic.getLongestPath().length(); final Iterator iterator = helpTopic.getChildSuggestions().iterator(); while (iterator.hasNext()) { final String suggestion = iterator.next(); final StringBuilder indentation = new StringBuilder(); for (int i = 0; i < headerIndentation; i++) { indentation.append(" "); } final String prefix; if (iterator.hasNext()) { prefix = "├─"; } else { prefix = "└─"; } final String suggestedCommand = suggestion.replace("<", "") .replace(">", "") .replace("[", "") .replace("]", ""); audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_SUGGESTION), Template.of("indentation", indentation.toString()), Template.of("prefix", prefix), Template.of("suggestion", suggestion), Template.of("cmdprefix", this.commandPrefix + ' ' + suggestedCommand))); } } private void printVerboseHelpTopic(final @NonNull C sender, final CommandHelpHandler.@NonNull VerboseHelpTopic helpTopic) { final Audience audience = this.getAudience(sender); final String command = this.commandManager.getCommandSyntaxFormatter() .apply(helpTopic.getCommand().getArguments(), null); audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_VERBOSE_SYNTAX), Template.of("command", command))); audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_VERBOSE_DESCRIPTION), Template.of("description", helpTopic.getDescription()))); audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_VERBOSE_ARGS))); final Iterator> iterator = helpTopic.getCommand().getArguments().iterator(); /* Skip the first one because it's the command literal */ iterator.next(); boolean hasLast; while (iterator.hasNext()) { final CommandArgument argument = iterator.next(); String description = helpTopic.getCommand().getArgumentDescription(argument); if (description.isEmpty()) { description = "No description"; } String syntax = this.commandManager.getCommandSyntaxFormatter() .apply(Collections.singletonList(argument), null); if (argument instanceof StaticArgument) { syntax = this.messageMap.get(MESSAGE_QUERY_VERBOSE_LITERAL).replace("", syntax); } final String prefix = iterator.hasNext() ? "├─" : "└─"; hasLast = !iterator.hasNext(); final String message; if (argument.isRequired()) { message = this.messageMap.get(MESSAGE_QUERY_VERBOSE_REQUIRED); } else { message = this.messageMap.get(MESSAGE_QUERY_VERBOSE_OPTIONAL); } audience.sendMessage(this.miniMessage.parse(message, Template.of("prefix", prefix), Template.of("syntax", syntax), Template.of("description", description))); } } }