From 366c4f2ce5eada90597991c77cb951e118afddbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Fri, 16 Oct 2020 20:43:54 +0200 Subject: [PATCH] :sparkles: Add an adventure text colour parser --- .../captions/SimpleCaptionRegistry.java | 8 + .../captions/StandardCaptionKeys.java | 4 + .../minecraft/extras/ColorArgument.java | 230 ++++++++++++++++++ .../examples/bukkit/ExamplePlugin.java | 59 +---- 4 files changed, 251 insertions(+), 50 deletions(-) create mode 100644 cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/ColorArgument.java diff --git a/cloud-core/src/main/java/cloud/commandframework/captions/SimpleCaptionRegistry.java b/cloud-core/src/main/java/cloud/commandframework/captions/SimpleCaptionRegistry.java index 40c70135..9d83d2c5 100644 --- a/cloud-core/src/main/java/cloud/commandframework/captions/SimpleCaptionRegistry.java +++ b/cloud-core/src/main/java/cloud/commandframework/captions/SimpleCaptionRegistry.java @@ -76,6 +76,10 @@ public class SimpleCaptionRegistry implements FactoryDelegatingCaptionRegistr * Default caption for {@link StandardCaptionKeys#ARGUMENT_PARSE_FAILURE_FLAG_MISSING_ARGUMENT} */ public static final String ARGUMENT_PARSE_FAILURE_FLAG_MISSING_ARGUMENT = "Missing argument for '{flag}'"; + /** + * Default caption for {@link StandardCaptionKeys#ARGUMENT_PARSE_FAILURE_COLOR} + */ + public static final String ARGUMENT_PARSE_FAILURE_COLOR = "'{input}' is not a valid color"; private final Map> messageFactories = new HashMap<>(); @@ -120,6 +124,10 @@ public class SimpleCaptionRegistry implements FactoryDelegatingCaptionRegistr StandardCaptionKeys.ARGUMENT_PARSE_FAILURE_FLAG_MISSING_ARGUMENT, (caption, sender) -> ARGUMENT_PARSE_FAILURE_FLAG_MISSING_ARGUMENT ); + this.registerMessageFactory( + StandardCaptionKeys.ARGUMENT_PARSE_FAILURE_COLOR, + (caption, sender) -> ARGUMENT_PARSE_FAILURE_COLOR + ); } @Override diff --git a/cloud-core/src/main/java/cloud/commandframework/captions/StandardCaptionKeys.java b/cloud-core/src/main/java/cloud/commandframework/captions/StandardCaptionKeys.java index 90a30b6e..77f91ff4 100644 --- a/cloud-core/src/main/java/cloud/commandframework/captions/StandardCaptionKeys.java +++ b/cloud-core/src/main/java/cloud/commandframework/captions/StandardCaptionKeys.java @@ -80,6 +80,10 @@ public final class StandardCaptionKeys { * Variables: {flag} */ public static final Caption ARGUMENT_PARSE_FAILURE_FLAG_MISSING_ARGUMENT = of("argument.parse.failure.flag.missing_argument"); + /** + * Variables: {input} + */ + public static final Caption ARGUMENT_PARSE_FAILURE_COLOR = of("argument.parse.failure.color"); private StandardCaptionKeys() { } diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/ColorArgument.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/ColorArgument.java new file mode 100644 index 00000000..4b6fa3d7 --- /dev/null +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/ColorArgument.java @@ -0,0 +1,230 @@ +// +// 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.minecraft.extras; + +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.captions.CaptionVariable; +import cloud.commandframework.captions.StandardCaptionKeys; +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.ParserException; +import cloud.commandframework.types.tuples.Pair; +import io.leangen.geantyref.TypeToken; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.regex.Pattern; + +/** + * Parser for color codes + * + * @param Command sender type + */ +public final class ColorArgument extends CommandArgument { + + private static final Pattern LEGACY_PREDICATE = Pattern.compile( + "&[0-9a-fA-F]" + ); + + private static final Pattern HEX_PREDICATE = Pattern.compile( + "#?([a-fA-F0-9]{1,6})" + ); + + private static final Collection> COLORS = Arrays.asList( + Pair.of('0', NamedTextColor.BLACK), + Pair.of('1', NamedTextColor.DARK_BLUE), + Pair.of('2', NamedTextColor.DARK_GREEN), + Pair.of('3', NamedTextColor.DARK_GREEN), + Pair.of('4', NamedTextColor.DARK_AQUA), + Pair.of('5', NamedTextColor.DARK_PURPLE), + Pair.of('6', NamedTextColor.GOLD), + Pair.of('7', NamedTextColor.GRAY), + Pair.of('8', NamedTextColor.DARK_GRAY), + Pair.of('9', NamedTextColor.BLUE), + Pair.of('a', NamedTextColor.GREEN), + Pair.of('b', NamedTextColor.AQUA), + Pair.of('c', NamedTextColor.RED), + Pair.of('d', NamedTextColor.LIGHT_PURPLE), + Pair.of('e', NamedTextColor.YELLOW), + Pair.of('f', NamedTextColor.WHITE) + ); + + private ColorArgument( + final boolean required, + final @NonNull String name, + final @NonNull String defaultValue + ) { + super( + required, + name, + new ColorArgumentParser<>(), + defaultValue, + TypeToken.get(TextColor.class), + null, + new LinkedList<>() + ); + } + + /** + * Create a new required colour argument + * + * @param name Argument name + * @param Command sender type + * @return Created argument + */ + public static @NonNull ColorArgument of(final @NonNull String name) { + return new ColorArgument<>( + true, + name, + "" + ); + } + + /** + * Create a new optional colour argument + * + * @param name Argument name + * @param Command sender type + * @return Created argument + */ + public static @NonNull ColorArgument optional(final @NonNull String name) { + return new ColorArgument<>( + false, + name, + "" + ); + } + + /** + * Create a new optional colour argument + * + * @param name Argument name + * @param defaultValue Default value + * @param Command sender type + * @return Created argument + */ + public static @NonNull ColorArgument optionalWithDefault( + final @NonNull String name, + final @NonNull String defaultValue + ) { + return new ColorArgument<>( + false, + name, + defaultValue + ); + } + + + public static final class ColorArgumentParser implements ArgumentParser { + + @Override + public @NonNull ArgumentParseResult<@NonNull TextColor> parse( + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue + ) { + final String input = inputQueue.peek(); + if (input == null) { + throw new NullPointerException( + "No input was supplied" + ); + } + if (LEGACY_PREDICATE.matcher(input).matches()) { + final char code = input.substring(1).toLowerCase().charAt(0); + for (final Pair pair : COLORS) { + if (pair.getFirst() == code) { + inputQueue.remove(); + return ArgumentParseResult.success( + pair.getSecond() + ); + } + } + } + for (final Pair pair : COLORS) { + if (pair.getSecond().toString().equalsIgnoreCase(input)) { + inputQueue.remove(); + return ArgumentParseResult.success( + pair.getSecond() + ); + } + } + if (HEX_PREDICATE.matcher(input).matches()) { + return ArgumentParseResult.success( + TextColor.color(Integer.parseInt(input.startsWith("#") ? input.substring(1) : input, 16)) + ); + } + return ArgumentParseResult.failure( + new ColorArgumentParseException( + commandContext, + input + ) + ); + } + + @Override + public @NonNull List<@NonNull String> suggestions( + final @NonNull CommandContext commandContext, final @NonNull String input + ) { + final List suggestions = new LinkedList<>(); + if (input.isEmpty() || input.equals("#") || (HEX_PREDICATE.matcher(input).matches() + && input.length() < (input.startsWith("#") ? 7 : 6))) { + for (char c = 'a'; c <= 'f'; c++) { + suggestions.add(String.format("%s%c", input, c)); + suggestions.add(String.format("&%c", c)); + } + for (char c = '0'; c <= '9'; c++) { + suggestions.add(String.format("%s%c", input, c)); + suggestions.add(String.format("&%c", c)); + } + } + suggestions.addAll(NamedTextColor.NAMES.keys()); + return suggestions; + } + + } + + + private static final class ColorArgumentParseException extends ParserException { + + private ColorArgumentParseException( + final @NonNull CommandContext commandContext, + final @NonNull String input + ) { + super( + ColorArgumentParser.class, + commandContext, + StandardCaptionKeys.ARGUMENT_PARSE_FAILURE_COLOR, + CaptionVariable.of("input", input) + ); + } + + } + +} diff --git a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java index cbc6686a..4037ca08 100644 --- a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java +++ b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java @@ -26,8 +26,6 @@ package cloud.commandframework.examples.bukkit; import cloud.commandframework.Command; import cloud.commandframework.CommandTree; import cloud.commandframework.Description; -import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler; -import cloud.commandframework.minecraft.extras.MinecraftHelp; import cloud.commandframework.annotations.AnnotationParser; import cloud.commandframework.annotations.Argument; import cloud.commandframework.annotations.CommandDescription; @@ -38,8 +36,6 @@ import cloud.commandframework.annotations.Flag; import cloud.commandframework.annotations.Regex; import cloud.commandframework.annotations.specifier.Greedy; import cloud.commandframework.arguments.CommandArgument; -import cloud.commandframework.arguments.parser.ArgumentParseResult; -import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.arguments.parser.ParserParameters; import cloud.commandframework.arguments.parser.StandardParameters; import cloud.commandframework.arguments.standard.EnumArgument; @@ -55,20 +51,20 @@ import cloud.commandframework.bukkit.parsers.WorldArgument; import cloud.commandframework.bukkit.parsers.selector.SingleEntitySelectorArgument; import cloud.commandframework.captions.Caption; import cloud.commandframework.captions.SimpleCaptionRegistry; -import cloud.commandframework.context.CommandContext; import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.extra.confirmation.CommandConfirmationManager; import cloud.commandframework.meta.CommandMeta; +import cloud.commandframework.minecraft.extras.ColorArgument; +import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler; +import cloud.commandframework.minecraft.extras.MinecraftHelp; import cloud.commandframework.paper.PaperCommandManager; import cloud.commandframework.types.tuples.Triplet; -import com.google.common.collect.ImmutableList; import io.leangen.geantyref.TypeToken; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -89,7 +85,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; import java.util.function.Function; /** @@ -317,33 +312,6 @@ public final class ExamplePlugin extends JavaPlugin { player.getInventory().getItemInHand().addEnchantment(ctx.get("enchant"), ctx.get("level")); }).execute())); - // - // An Argument Parser for TextColor that accepts NamedTextColor names or RGB colors in the format 'RRGGBB' - // - final ArgumentParser textColorArgumentParser = (context, inputQueue) -> { - final String input = inputQueue.peek(); - if (input == null) { - return ArgumentParseResult.failure(new IllegalArgumentException("No input provided")); - } - if (NamedTextColor.NAMES.keys().contains(input.toLowerCase())) { - inputQueue.remove(); - return ArgumentParseResult.success(NamedTextColor.NAMES.value(input.toLowerCase())); - } - final TextColor hex = TextColor.fromHexString("#" + input); - if (hex != null) { - inputQueue.remove(); - return ArgumentParseResult.success(hex); - } - return ArgumentParseResult.failure(new IllegalArgumentException( - "No such color. Try a NamedTextColor or Hex in the format 'RRGGBB'")); - }; - - // - // A Suggestions Provider which returns the list of NamedTextColors - // - final BiFunction, String, List> textColorSuggestionsProvider = - (context, input) -> ImmutableList.copyOf(NamedTextColor.NAMES.keys()); - // // A command to change the color scheme for the help command // @@ -351,33 +319,23 @@ public final class ExamplePlugin extends JavaPlugin { .meta("description", "Sets the color scheme for '/example help'") .literal("helpcolors") .argument( - manager.argumentBuilder(TextColor.class, "primary") - .withParser(textColorArgumentParser) - .withSuggestionsProvider(textColorSuggestionsProvider), + ColorArgument.of("primary"), Description.of("The primary color for the color scheme") ) .argument( - manager.argumentBuilder(TextColor.class, "highlight") - .withParser(textColorArgumentParser) - .withSuggestionsProvider(textColorSuggestionsProvider), + ColorArgument.of("highlight"), Description.of("The primary color used to highlight commands and queries") ) .argument( - manager.argumentBuilder(TextColor.class, "alternate_highlight") - .withParser(textColorArgumentParser) - .withSuggestionsProvider(textColorSuggestionsProvider), + ColorArgument.of("alternate_highlight"), Description.of("The secondary color used to highlight commands and queries") ) .argument( - manager.argumentBuilder(TextColor.class, "text") - .withParser(textColorArgumentParser) - .withSuggestionsProvider(textColorSuggestionsProvider), + ColorArgument.of("text"), Description.of("The color used for description text") ) .argument( - manager.argumentBuilder(TextColor.class, "accent") - .withParser(textColorArgumentParser) - .withSuggestionsProvider(textColorSuggestionsProvider), + ColorArgument.of("accent"), Description.of("The color used for accents and symbols") ) .handler(c -> minecraftHelp.setHelpColors(MinecraftHelp.HelpColors.of( @@ -388,6 +346,7 @@ public final class ExamplePlugin extends JavaPlugin { c.get("accent") ))) ); + // // Create a Bukkit-like command //