Add adventure based help menu

This commit is contained in:
Alexander Söderberg 2020-09-21 19:11:48 +02:00
parent bad944e1a5
commit 3f96837cf1
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
8 changed files with 480 additions and 16 deletions

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~
~ MIT License
~
~ Copyright (c) 2020 Alexander Söderberg
~
~ 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud</artifactId>
<groupId>com.intellectualsites</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-minecraft-extras</artifactId>
<version>0.1.0-SNAPSHOT</version>
<repositories>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-api</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.intellectualsites</groupId>
<artifactId>cloud-core</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-minimessage</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,49 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg
//
// 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 com.intellectualsites.commands;
import net.kyori.adventure.audience.Audience;
import javax.annotation.Nonnull;
import java.util.function.Function;
/**
* Function that maps the command sender type to an adventure {@link net.kyori.adventure.audience.Audience}
*
* @param <C> Command sender type
*/
@FunctionalInterface
public interface AudienceProvider<C> extends Function<C, Audience> {
/**
* Convert a command sender to an {@link Audience}
*
* @param sender Command sender
* @return Mapped audience
*/
@Override
@Nonnull
Audience apply(@Nonnull C sender);
}

View file

@ -0,0 +1,266 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg
//
// 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 com.intellectualsites.commands;
import com.intellectualsites.commands.arguments.CommandArgument;
import com.intellectualsites.commands.arguments.StaticArgument;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.Template;
import javax.annotation.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 <C> Command sender type
*/
public final class MinecraftHelp<C> {
/* 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<String, String> messageMap = new HashMap<>();
private final AudienceProvider<C> audienceProvider;
private final CommandManager<C> commandManager;
/**
* Construct a new Minecraft help instance
*
* @param audienceProvider Provider that maps the command sender type to {@link Audience}
* @param commandManager Command manager instance
*/
public MinecraftHelp(@Nonnull final AudienceProvider<C> audienceProvider,
@Nonnull final CommandManager<C> commandManager) {
this.audienceProvider = audienceProvider;
this.commandManager = commandManager;
/* Default messages */
this.messageMap.put(MESSAGE_HELP_HEADER, "<gold><bold>------------ Help ------------</bold></gold>");
this.messageMap.put(MESSAGE_HELP_FOOTER, "<gold><bold>----------------------------</bold></gold>");
this.messageMap.put(MESSAGE_QUERY_QUERY, "<gray>Showing search results for query: \"<green>/<query></green>\"</gray>");
this.messageMap.put(MESSAGE_QUERY_AVAILABLE_COMMANDS, "<dark_gray>└─</dark_gray><gray> Available Commands:</gray>");
this.messageMap.put(MESSAGE_QUERY_COMMAND_SYNTAX, "<dark_gray> ├─</dark_gray> <green>"
+ "<hover:show_text:\"<gray><description></gray>\">/<command></hover></green>");
this.messageMap.put(MESSAGE_QUERY_COMMAND_SYNTAX_LAST, "<dark_gray> └─</dark_gray> <green>"
+ "<hover:show_text:\"<gray><description></gray>\">/<command></hover></green>");
this.messageMap.put(MESSAGE_QUERY_LONGEST_PATH, "<dark_gray>└─</dark_gray> <green>/<command></green>");
this.messageMap.put(MESSAGE_QUERY_SUGGESTION, "<dark_gray><indentation><prefix></dark_gray> <green><suggestion></green>");
this.messageMap.put(MESSAGE_QUERY_VERBOSE_SYNTAX, "<dark_gray>└─</dark_gray> <gold>Command:"
+ " </gold><green>/<command></green>");
this.messageMap.put(MESSAGE_QUERY_VERBOSE_DESCRIPTION, "<dark_gray> ├─</dark_gray>"
+ " <gold>Description:</gold> <gray><description></gray>");
this.messageMap.put(MESSAGE_QUERY_VERBOSE_ARGS, "<dark_gray> └─</dark_gray> <gold>Args:</gold>");
this.messageMap.put(MESSAGE_QUERY_VERBOSE_OPTIONAL, "<dark_gray> <prefix></dark_gray> <white><syntax><white> "
+ "<yellow>(Optional)</yellow> <dark_gray>-</dark_gray> <gray><description></gray>");
this.messageMap.put(MESSAGE_QUERY_VERBOSE_REQUIRED, "<dark_gray> <prefix></dark_gray> <white><syntax><white> "
+ "<dark_gray>-</dark_gray> <gray><description></gray>");
this.messageMap.put(MESSAGE_QUERY_VERBOSE_LITERAL, "<gray><syntax></gray>");
}
/**
* Get the command manager instance
*
* @return Command manager
*/
@Nonnull
public CommandManager<C> getCommandManager() {
return this.commandManager;
}
/**
* Get the audience provider that was used to create this instance
*
* @return Audience provider
*/
@Nonnull
public AudienceProvider<C> getAudienceProvider() {
return this.audienceProvider;
}
/**
* Map a command sender to an {@link Audience}
*
* @param sender Sender to map
* @return Mapped audience
*/
@Nonnull
public Audience getAudience(@Nonnull final C sender) {
return this.audienceProvider.apply(sender);
}
/**
* Configure a message
*
* @param key Message key
* @param message Message
*/
public void setMessage(@Nonnull final String key, @Nonnull final 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(@Nonnull final String query,
@Nonnull final 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(query));
audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_HELP_FOOTER)));
}
private void printTopic(@Nonnull final C sender,
@Nonnull final String query,
@Nonnull final CommandHelpHandler.HelpTopic<C> 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<C>) helpTopic);
} else if (helpTopic instanceof CommandHelpHandler.MultiHelpTopic) {
this.printMultiHelpTopic(sender, (CommandHelpHandler.MultiHelpTopic<C>) helpTopic);
} else if (helpTopic instanceof CommandHelpHandler.VerboseHelpTopic) {
this.printVerboseHelpTopic(sender, (CommandHelpHandler.VerboseHelpTopic<C>) helpTopic);
} else {
throw new IllegalArgumentException("Unknown help topic type");
}
}
private void printIndexHelpTopic(@Nonnull final C sender, @Nonnull final CommandHelpHandler.IndexHelpTopic<C> helpTopic) {
final Audience audience = this.getAudience(sender);
audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_AVAILABLE_COMMANDS)));
final Iterator<CommandHelpHandler.VerboseHelpEntry<C>> iterator = helpTopic.getEntries().iterator();
while (iterator.hasNext()) {
final CommandHelpHandler.VerboseHelpEntry<C> entry = iterator.next();
if (iterator.hasNext()) {
audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_COMMAND_SYNTAX),
Template.of("command", entry.getSyntaxString()),
Template.of("description", entry.getDescription())));
} else {
audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_COMMAND_SYNTAX_LAST),
Template.of("command", entry.getSyntaxString()),
Template.of("description", entry.getDescription())));
}
}
}
private void printMultiHelpTopic(@Nonnull final C sender, @Nonnull final CommandHelpHandler.MultiHelpTopic<C> 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<String> 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 = "└─";
}
audience.sendMessage(this.miniMessage.parse(this.messageMap.get(MESSAGE_QUERY_SUGGESTION),
Template.of("indentation", indentation.toString()),
Template.of("prefix", prefix),
Template.of("suggestion", suggestion)));
}
}
private void printVerboseHelpTopic(@Nonnull final C sender, @Nonnull final CommandHelpHandler.VerboseHelpTopic<C> 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<CommandArgument<C, ?>> iterator = helpTopic.getCommand().getArguments().iterator();
/* Skip the first one because it's the command literal */
iterator.next();
boolean hasLast;
while (iterator.hasNext()) {
final CommandArgument<C, ?> 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>", 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)));
}
}
}

View file

@ -0,0 +1,28 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg
//
// 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.
//
/**
* Minecraft extras
*/
package com.intellectualsites.commands;