From 7148e76bcd93d57be6d0e1938e18329e675695b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Mon, 14 Sep 2020 22:36:58 +0200 Subject: [PATCH] Add Brigadier support. --- checkstyle.xml | 1 - .../commands/CommandManager.java | 34 ++- .../commands/CommandTree.java | 65 ++++- .../components/parser/ComponentParser.java | 10 + .../parser/StandardParserRegistry.java | 43 ++- .../components/standard/BooleanComponent.java | 12 +- .../components/standard/ByteComponent.java | 12 +- .../components/standard/CharComponent.java | 7 +- .../components/standard/DoubleComponent.java | 14 +- .../components/standard/EnumComponent.java | 14 +- .../components/standard/FloatComponent.java | 14 +- .../components/standard/IntegerComponent.java | 4 + .../components/standard/LongComponent.java | 4 + .../components/standard/ShortComponent.java | 14 +- .../components/standard/StringComponent.java | 21 +- .../brigadier/CloudBrigadierManager.java | 263 ++++++++++++++++++ .../commands/brigadier/package-info.java | 28 ++ cloud-minecraft/cloud-bukkit-test/pom.xml | 2 +- .../commands/BukkitTest.java | 68 +++-- .../commands/BukkitCommand.java | 2 + cloud-minecraft/cloud-paper/pom.xml | 95 +++++++ .../commands/PaperBrigadierListener.java | 59 ++++ .../commands/PaperCommandManager.java | 65 +++++ .../commands/package-info.java | 28 ++ pom.xml | 4 +- 25 files changed, 822 insertions(+), 61 deletions(-) create mode 100644 cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java create mode 100644 cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/package-info.java create mode 100644 cloud-minecraft/cloud-paper/pom.xml create mode 100644 cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperBrigadierListener.java create mode 100644 cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperCommandManager.java create mode 100644 cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/package-info.java diff --git a/checkstyle.xml b/checkstyle.xml index dede079b..54a3ed89 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -146,7 +146,6 @@ - diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java index af0fac9d..28c3dcdd 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java @@ -89,6 +89,25 @@ public abstract class CommandManager()); } + /** + * Tokenize an input string + * + * @param input Input string + * @return List of tokens + */ + @Nonnull + public static LinkedList tokenize(@Nonnull final String input) { + final StringTokenizer stringTokenizer = new StringTokenizer(input, " "); + final LinkedList tokens = new LinkedList<>(); + while (stringTokenizer.hasMoreElements()) { + tokens.add(stringTokenizer.nextToken()); + } + if (input.endsWith(" ")) { + tokens.add(""); + } + return tokens; + } + /** * Execute a command and get a future that completes with the result * @@ -134,19 +153,6 @@ public abstract class CommandManager tokenize(@Nonnull final String input) { - final StringTokenizer stringTokenizer = new StringTokenizer(input, " "); - final LinkedList tokens = new LinkedList<>(); - while (stringTokenizer.hasMoreElements()) { - tokens.add(stringTokenizer.nextToken()); - } - if (input.endsWith(" ")) { - tokens.add(""); - } - return tokens; - } - /** * Register a new command * @@ -235,7 +241,7 @@ public abstract class CommandManager getCommandTree() { + public CommandTree getCommandTree() { return this.commandTree; } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java index 7f93679c..17a2daec 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandTree.java @@ -39,6 +39,7 @@ import com.intellectualsites.commands.sender.CommandSender; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -491,13 +492,35 @@ public final class CommandTree { return casted; } + /** + * Get an immutable collection containing all of the root nodes + * in the tree + * + * @return Root nodes + */ + @Nonnull + public Collection>> getRootNodes() { + return this.internalTree.getChildren(); + } + + /** + * Get a named root node, if it exists + * + * @param name Root node name + * @return Root node, or {@code null} + */ + @Nullable + public Node> getNamedNode(@Nullable final String name) { + return this.getRootNodes().stream().filter(node -> node.getValue() != null + && node.getValue().getName().equalsIgnoreCase(name)).findAny().orElse(null); + } /** * Very simple tree structure * * @param Node value type */ - private static final class Node { + public static final class Node { private final Map nodeMeta = new HashMap<>(); private final List> children = new LinkedList<>(); @@ -508,7 +531,13 @@ public final class CommandTree { this.value = value; } - private List> getChildren() { + /** + * Get an immutable copy of the node's child list + * + * @return Children + */ + @Nonnull + public List> getChildren() { return Collections.unmodifiableList(this.children); } @@ -529,10 +558,30 @@ public final class CommandTree { return null; } - private boolean isLeaf() { + /** + * Check if the node is a leaf node + * + * @return {@code true} if the node is a leaf node, else {@code false} + */ + public boolean isLeaf() { return this.children.isEmpty(); } + /** + * Get the node meta instance + * + * @return Node meta + */ + @Nonnull + public Map getNodeMeta() { + return this.nodeMeta; + } + + /** + * Get the node value + * + * @return Node value + */ @Nullable public T getValue() { return this.value; @@ -555,11 +604,21 @@ public final class CommandTree { return Objects.hash(getValue()); } + /** + * Get the parent node + * + * @return Parent node + */ @Nullable public Node getParent() { return this.parent; } + /** + * Set the parent node + * + * @param parent new parent node + */ public void setParent(@Nullable final Node parent) { this.parent = parent; } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/parser/ComponentParser.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/parser/ComponentParser.java index ef8c4458..de4eba20 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/parser/ComponentParser.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/parser/ComponentParser.java @@ -62,4 +62,14 @@ public interface ComponentParser { return Collections.emptyList(); } + /** + * Check whether or not this component parser is context free. A context free + * parser will not use the provided command context, and so supports impromptu parsing + * + * @return {@code true} if the parser is context free, else {@code false} + */ + default boolean isContextFree() { + return false; + } + } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/parser/StandardParserRegistry.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/parser/StandardParserRegistry.java index cbb2997a..e6b31e86 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/parser/StandardParserRegistry.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/parser/StandardParserRegistry.java @@ -26,12 +26,21 @@ package com.intellectualsites.commands.components.parser; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import com.intellectualsites.commands.annotations.specifier.Range; +import com.intellectualsites.commands.components.standard.BooleanComponent; +import com.intellectualsites.commands.components.standard.ByteComponent; +import com.intellectualsites.commands.components.standard.CharComponent; +import com.intellectualsites.commands.components.standard.DoubleComponent; +import com.intellectualsites.commands.components.standard.EnumComponent; +import com.intellectualsites.commands.components.standard.FloatComponent; import com.intellectualsites.commands.components.standard.IntegerComponent; +import com.intellectualsites.commands.components.standard.ShortComponent; +import com.intellectualsites.commands.components.standard.StringComponent; import com.intellectualsites.commands.sender.CommandSender; import javax.annotation.Nonnull; import java.lang.annotation.Annotation; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -69,9 +78,27 @@ public final class StandardParserRegistry implements Pa this.registerAnnotationMapper(Range.class, new RangeMapper<>()); /* Register standard types */ + this.registerParserSupplier(TypeToken.of(Byte.class), options -> + new ByteComponent.ByteParser((byte) options.get(StandardParameters.RANGE_MIN, Byte.MIN_VALUE), + (byte) options.get(StandardParameters.RANGE_MAX, Byte.MAX_VALUE))); + this.registerParserSupplier(TypeToken.of(Short.class), options -> + new ShortComponent.ShortParser((short) options.get(StandardParameters.RANGE_MIN, Short.MIN_VALUE), + (short) options.get(StandardParameters.RANGE_MAX, Short.MAX_VALUE))); this.registerParserSupplier(TypeToken.of(Integer.class), options -> new IntegerComponent.IntegerParser((int) options.get(StandardParameters.RANGE_MIN, Integer.MIN_VALUE), (int) options.get(StandardParameters.RANGE_MAX, Integer.MAX_VALUE))); + this.registerParserSupplier(TypeToken.of(Float.class), options -> + new FloatComponent.FloatParser((float) options.get(StandardParameters.RANGE_MIN, Float.MIN_VALUE), + (float) options.get(StandardParameters.RANGE_MAX, Float.MAX_VALUE))); + this.registerParserSupplier(TypeToken.of(Double.class), options -> + new DoubleComponent.DoubleParser((double) options.get(StandardParameters.RANGE_MIN, Double.MIN_VALUE), + (double) options.get(StandardParameters.RANGE_MAX, Double.MAX_VALUE))); + this.registerParserSupplier(TypeToken.of(Character.class), options -> new CharComponent.CharacterParser()); + /* Make this one less awful */ + this.registerParserSupplier(TypeToken.of(String.class), options -> new StringComponent.StringParser( + StringComponent.StringMode.SINGLE, (context, s) -> Collections.emptyList())); + /* Add options to this */ + this.registerParserSupplier(TypeToken.of(Boolean.class), options -> new BooleanComponent.BooleanParser<>(false)); } @Override @@ -98,8 +125,8 @@ public final class StandardParserRegistry implements Pa if (mapper == null) { return; } - @SuppressWarnings("unchecked") - final ParserParameters parserParametersCasted = (ParserParameters) mapper.apply(annotation, parsingType); + @SuppressWarnings("unchecked") final ParserParameters parserParametersCasted = (ParserParameters) mapper.apply( + annotation, parsingType); parserParameters.merge(parserParametersCasted); }); return parserParameters; @@ -117,10 +144,18 @@ public final class StandardParserRegistry implements Pa } final Function> producer = this.parserSuppliers.get(actualType); if (producer == null) { + /* Give enums special treatment */ + if (actualType.isSubtypeOf(Enum.class)) { + @SuppressWarnings("all") + final EnumComponent.EnumParser enumComponent = new EnumComponent.EnumParser((Class) + actualType.getRawType()); + // noinspection all + return Optional.of(enumComponent); + } return Optional.empty(); } - @SuppressWarnings("unchecked") - final ComponentParser parser = (ComponentParser) producer.apply(parserParameters); + @SuppressWarnings("unchecked") final ComponentParser parser = (ComponentParser) producer.apply( + parserParameters); return Optional.of(parser); } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/BooleanComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/BooleanComponent.java index 7fa83807..14ad5ee6 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/BooleanComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/BooleanComponent.java @@ -146,7 +146,12 @@ public final class BooleanComponent extends CommandComp private final boolean liberal; - private BooleanParser(final boolean liberal) { + /** + * Construct a new boolean parser + * + * @param liberal Whether or not it'll accept boolean-esque strings, or just booleans + */ + public BooleanParser(final boolean liberal) { this.liberal = liberal; } @@ -195,6 +200,11 @@ public final class BooleanComponent extends CommandComp return LIBERAL; } + + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/ByteComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/ByteComponent.java index 6afbd514..f01f0674 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/ByteComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/ByteComponent.java @@ -167,7 +167,13 @@ public final class ByteComponent extends CommandCompone private final byte min; private final byte max; - private ByteParser(final byte min, final byte max) { + /** + * Construct a new byte parser + * + * @param min Minimum value + * @param max Maximum value + */ + public ByteParser(final byte min, final byte max) { this.min = min; this.max = max; } @@ -198,6 +204,10 @@ public final class ByteComponent extends CommandCompone } } + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/CharComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/CharComponent.java index 20b3c6f8..0713a42b 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/CharComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/CharComponent.java @@ -111,7 +111,7 @@ public final class CharComponent extends CommandCompone } - private static final class CharacterParser implements ComponentParser { + public static final class CharacterParser implements ComponentParser { @Nonnull @Override @@ -128,6 +128,11 @@ public final class CharComponent extends CommandCompone return ComponentParseResult.success(input.charAt(0)); } + + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/DoubleComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/DoubleComponent.java index 32f8a1d4..cc474d89 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/DoubleComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/DoubleComponent.java @@ -166,12 +166,18 @@ public final class DoubleComponent extends CommandCompo } - private static final class DoubleParser implements ComponentParser { + public static final class DoubleParser implements ComponentParser { private final double min; private final double max; - private DoubleParser(final double min, final double max) { + /** + * Construct a new double parser + * + * @param min Minimum value + * @param max Maximum value + */ + public DoubleParser(final double min, final double max) { this.min = min; this.max = max; } @@ -197,6 +203,10 @@ public final class DoubleComponent extends CommandCompo } } + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/EnumComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/EnumComponent.java index 2cbdcec4..3f131edf 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/EnumComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/EnumComponent.java @@ -130,12 +130,17 @@ public class EnumComponent> extends C } - private static final class EnumParser> implements ComponentParser { + public static final class EnumParser> implements ComponentParser { private final Class enumClass; private final EnumSet allowedValues; - private EnumParser(@Nonnull final Class enumClass) { + /** + * Construct a new enum parser + * + * @param enumClass Enum class + */ + public EnumParser(@Nonnull final Class enumClass) { this.enumClass = enumClass; this.allowedValues = EnumSet.allOf(enumClass); } @@ -164,6 +169,11 @@ public class EnumComponent> extends C public List suggestions(@Nonnull final CommandContext commandContext, @Nonnull final String input) { return EnumSet.allOf(this.enumClass).stream().map(e -> e.name().toLowerCase()).collect(Collectors.toList()); } + + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/FloatComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/FloatComponent.java index 6936b7f8..2856a4f2 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/FloatComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/FloatComponent.java @@ -166,12 +166,18 @@ public final class FloatComponent extends CommandCompon } - private static final class FloatParser implements ComponentParser { + public static final class FloatParser implements ComponentParser { private final float min; private final float max; - private FloatParser(final float min, final float max) { + /** + * Construct a new float parser + * + * @param min Minimum value + * @param max Maximum value + */ + public FloatParser(final float min, final float max) { this.min = min; this.max = max; } @@ -197,6 +203,10 @@ public final class FloatComponent extends CommandCompon } } + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/IntegerComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/IntegerComponent.java index cc637289..1c5e31f5 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/IntegerComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/IntegerComponent.java @@ -221,6 +221,10 @@ public final class IntegerComponent extends CommandComp return this.max; } + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/LongComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/LongComponent.java index 76325cae..1fb6aaf1 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/LongComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/LongComponent.java @@ -197,6 +197,10 @@ public final class LongComponent extends CommandCompone } } + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/ShortComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/ShortComponent.java index 5e825262..1b48aeb2 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/ShortComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/ShortComponent.java @@ -166,12 +166,18 @@ public final class ShortComponent extends CommandCompon } - private static final class ShortParser implements ComponentParser { + public static final class ShortParser implements ComponentParser { private final short min; private final short max; - private ShortParser(final short min, final short max) { + /** + * Construct a new short parser + * + * @param min Minimum value + * @param max Maximum value + */ + public ShortParser(final short min, final short max) { this.min = min; this.max = max; } @@ -197,6 +203,10 @@ public final class ShortComponent extends CommandCompon } } + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/StringComponent.java b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/StringComponent.java index f65855e6..6a8b091f 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/StringComponent.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/components/standard/StringComponent.java @@ -45,7 +45,7 @@ public final class StringComponent extends CommandCompo @Nonnull final String name, @Nonnull final StringMode stringMode, @Nonnull final String defaultValue, - @Nonnull final BiFunction, String, List> suggestionsProvider) { + @Nonnull final BiFunction, String, List> suggestionsProvider) { super(required, name, new StringParser<>(stringMode, suggestionsProvider), defaultValue); this.stringMode = stringMode; } @@ -121,7 +121,7 @@ public final class StringComponent extends CommandCompo public static final class Builder extends CommandComponent.Builder { private StringMode stringMode = StringMode.SINGLE; - private BiFunction, String, List> suggestionsProvider = (v1, v2) -> Collections.emptyList(); + private BiFunction, String, List> suggestionsProvider = (v1, v2) -> Collections.emptyList(); protected Builder(@Nonnull final String name) { super(name); @@ -188,13 +188,19 @@ public final class StringComponent extends CommandCompo } - private static final class StringParser implements ComponentParser { + public static final class StringParser implements ComponentParser { private final StringMode stringMode; private final BiFunction, String, List> suggestionsProvider; - private StringParser(@Nonnull final StringMode stringMode, - @Nonnull final BiFunction, String, List> suggestionsProvider) { + /** + * Construct a new string parser + * + * @param stringMode String parsing mode + * @param suggestionsProvider Suggestions provider + */ + public StringParser(@Nonnull final StringMode stringMode, + @Nonnull final BiFunction, String, List> suggestionsProvider) { this.stringMode = stringMode; this.suggestionsProvider = suggestionsProvider; } @@ -258,6 +264,11 @@ public final class StringComponent extends CommandCompo public List suggestions(@Nonnull final CommandContext commandContext, @Nonnull final String input) { return this.suggestionsProvider.apply(commandContext, input); } + + @Override + public boolean isContextFree() { + return true; + } } diff --git a/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java b/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java new file mode 100644 index 00000000..92b610b4 --- /dev/null +++ b/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java @@ -0,0 +1,263 @@ +// +// 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.brigadier; + +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; +import com.intellectualsites.commands.CommandTree; +import com.intellectualsites.commands.components.CommandComponent; +import com.intellectualsites.commands.components.StaticComponent; +import com.intellectualsites.commands.components.standard.BooleanComponent; +import com.intellectualsites.commands.components.standard.ByteComponent; +import com.intellectualsites.commands.components.standard.DoubleComponent; +import com.intellectualsites.commands.components.standard.FloatComponent; +import com.intellectualsites.commands.components.standard.IntegerComponent; +import com.intellectualsites.commands.components.standard.ShortComponent; +import com.intellectualsites.commands.components.standard.StringComponent; +import com.intellectualsites.commands.sender.CommandSender; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.arguments.DoubleArgumentType; +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; + +import javax.annotation.Nonnull; +import java.util.Map; +import java.util.function.BiPredicate; +import java.util.function.Function; + +/** + * Manager used to map cloud {@link com.intellectualsites.commands.Command} + *

+ * The structure of this class is largely inspired by + * + * ACFBrigadiermanager in the ACF project, which was originally written by MiniDigger and licensed under the MIT license. + * + * @param Command sender type + * @param Brigadier sender type + */ +public final class CloudBrigadierManager { + + private final Map, Function, + ? extends ArgumentType>> mappers; + + /** + * Create a new cloud brigadier manager + */ + public CloudBrigadierManager() { + this.mappers = Maps.newHashMap(); + this.registerInternalMappings(); + } + + private void registerInternalMappings() { + /* Map byte, short and int to IntegerArgumentType */ + this.registerMapping(new TypeToken>() { + }, component -> { + final boolean hasMin = component.getMin() != Byte.MIN_VALUE; + final boolean hasMax = component.getMax() != Byte.MAX_VALUE; + if (hasMin) { + return IntegerArgumentType.integer(component.getMin(), component.getMax()); + } else if (hasMax) { + return IntegerArgumentType.integer(Byte.MIN_VALUE, component.getMax()); + } else { + return IntegerArgumentType.integer(); + } + }); + this.registerMapping(new TypeToken>() { + }, component -> { + final boolean hasMin = component.getMin() != Short.MIN_VALUE; + final boolean hasMax = component.getMax() != Short.MAX_VALUE; + if (hasMin) { + return IntegerArgumentType.integer(component.getMin(), component.getMax()); + } else if (hasMax) { + return IntegerArgumentType.integer(Short.MIN_VALUE, component.getMax()); + } else { + return IntegerArgumentType.integer(); + } + }); + this.registerMapping(new TypeToken>() { + }, component -> { + final boolean hasMin = component.getMin() != Integer.MIN_VALUE; + final boolean hasMax = component.getMax() != Integer.MAX_VALUE; + + System.out.println("Constructing new IntegerArgumentType with min " + hasMin + " | max " + hasMax); + + if (hasMin) { + return IntegerArgumentType.integer(component.getMin(), component.getMax()); + } else if (hasMax) { + return IntegerArgumentType.integer(Integer.MIN_VALUE, component.getMax()); + } else { + return IntegerArgumentType.integer(); + } + }); + /* Map float to FloatArgumentType */ + this.registerMapping(new TypeToken>() { + }, component -> { + final boolean hasMin = component.getMin() != Float.MIN_VALUE; + final boolean hasMax = component.getMax() != Float.MAX_VALUE; + if (hasMin) { + return FloatArgumentType.floatArg(component.getMin(), component.getMax()); + } else if (hasMax) { + return FloatArgumentType.floatArg(Float.MIN_VALUE, component.getMax()); + } else { + return FloatArgumentType.floatArg(); + } + }); + /* Map double to DoubleArgumentType */ + this.registerMapping(new TypeToken>() { + }, component -> { + final boolean hasMin = component.getMin() != Double.MIN_VALUE; + final boolean hasMax = component.getMax() != Double.MAX_VALUE; + if (hasMin) { + return DoubleArgumentType.doubleArg(component.getMin(), component.getMax()); + } else if (hasMax) { + return DoubleArgumentType.doubleArg(Double.MIN_VALUE, component.getMax()); + } else { + return DoubleArgumentType.doubleArg(); + } + }); + /* Map boolean to BoolArgumentType */ + this.registerMapping(new TypeToken>() { + }, component -> BoolArgumentType.bool()); + /* Map String properly to StringArgumentType */ + this.registerMapping(new TypeToken>() { + }, component -> { + switch (component.getStringMode()) { + case SINGLE: + return StringArgumentType.word(); + case QUOTED: + return StringArgumentType.string(); + case GREEDY: + return StringArgumentType.greedyString(); + default: + return StringArgumentType.word(); + } + }); + } + + /** + * Register a cloud-Brigadier mapping + * + * @param componentType cloud component type + * @param mapper mapper function + * @param cloud component value type + * @param cloud component type + * @param Brigadier argument type value + */ + public , O> void registerMapping(@Nonnull final TypeToken componentType, + @Nonnull final Function> mapper) { + this.mappers.put(componentType.getRawType(), mapper); + } + + /** + * Get a Brigadier {@link ArgumentType} from a cloud {@link CommandComponent} + * + * @param componentType cloud component type + * @param component cloud component + * @param cloud component value type (generic) + * @param cloud component type (generic) + * @return Brigadier argument type + */ + @Nonnull + @SuppressWarnings("all") + public > ArgumentType getArgument(@Nonnull final TypeToken componentType, + @Nonnull final K component) { + final CommandComponent commandComponent = (CommandComponent) component; + final Function function = this.mappers.getOrDefault(componentType.getRawType(), t -> + createDefaultMapper((CommandComponent) component)); + return (ArgumentType) function.apply(commandComponent); + } + + @Nonnull + private > ArgumentType createDefaultMapper(@Nonnull final CommandComponent + component) { + return StringArgumentType.string(); + } + + /** + * Create a literal command from Brigadier command info, and a cloud command instance + * + * @param cloudCommand Cloud root command + * @param root Brigadier root command + * @param suggestionProvider Brigadier suggestions provider + * @param executor Brigadier command executor + * @param permissionChecker Permission checker + * @return Constructed literal command node + */ + @Nonnull + public LiteralCommandNode createLiteralCommandNode(@Nonnull final CommandTree.Node> cloudCommand, + @Nonnull final LiteralCommandNode root, + @Nonnull final SuggestionProvider suggestionProvider, + @Nonnull final com.mojang.brigadier.Command executor, + @Nonnull final BiPredicate permissionChecker) { + final LiteralArgumentBuilder literalArgumentBuilder = LiteralArgumentBuilder.literal(root.getLiteral()) + .requires(sender -> permissionChecker.test(sender, cloudCommand.getNodeMeta().getOrDefault("permission", ""))); + if (cloudCommand.isLeaf() && cloudCommand.getValue() != null && cloudCommand.getValue().getOwningCommand() != null) { + literalArgumentBuilder.executes(executor); + } + final LiteralCommandNode constructedRoot = literalArgumentBuilder.build(); + for (final CommandTree.Node> child : cloudCommand.getChildren()) { + constructedRoot.addChild(this.constructCommandNode(child, permissionChecker, executor, suggestionProvider)); + } + return constructedRoot; + } + + private CommandNode constructCommandNode(@Nonnull final CommandTree.Node> root, + @Nonnull final BiPredicate permissionChecker, + @Nonnull final com.mojang.brigadier.Command executor, + @Nonnull final SuggestionProvider suggestionProvider) { + CommandNode commandNode; + if (root.getValue() instanceof StaticComponent) { + final LiteralArgumentBuilder argumentBuilder = LiteralArgumentBuilder.literal(root.getValue().getName()) + .requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", ""))); + if (root.isLeaf()) { + argumentBuilder.executes(executor); + } + commandNode = argumentBuilder.build(); + } else { + @SuppressWarnings("unchecked") final RequiredArgumentBuilder builder = RequiredArgumentBuilder + .argument(root.getValue().getName(), + (ArgumentType) getArgument(TypeToken.of(root.getValue().getClass()), + root.getValue())) + .suggests(suggestionProvider) + .requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", ""))); + if (root.isLeaf() || !root.getValue().isRequired()) { + builder.executes(executor); + } + commandNode = builder.build(); + } + for (final CommandTree.Node> node : root.getChildren()) { + commandNode.addChild(constructCommandNode(node, permissionChecker, executor, suggestionProvider)); + } + return commandNode; + } + +} diff --git a/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/package-info.java b/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/package-info.java new file mode 100644 index 00000000..b562ef9b --- /dev/null +++ b/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/package-info.java @@ -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. +// + +/** + * Brigadier mappings + */ +package com.intellectualsites.commands.brigadier; diff --git a/cloud-minecraft/cloud-bukkit-test/pom.xml b/cloud-minecraft/cloud-bukkit-test/pom.xml index 51adcac3..a168aab5 100644 --- a/cloud-minecraft/cloud-bukkit-test/pom.xml +++ b/cloud-minecraft/cloud-bukkit-test/pom.xml @@ -62,7 +62,7 @@ com.intellectualsites - cloud-bukkit + cloud-paper 1.0-SNAPSHOT diff --git a/cloud-minecraft/cloud-bukkit-test/src/main/java/com/intellectualsites/commands/BukkitTest.java b/cloud-minecraft/cloud-bukkit-test/src/main/java/com/intellectualsites/commands/BukkitTest.java index 33734310..1bd07378 100644 --- a/cloud-minecraft/cloud-bukkit-test/src/main/java/com/intellectualsites/commands/BukkitTest.java +++ b/cloud-minecraft/cloud-bukkit-test/src/main/java/com/intellectualsites/commands/BukkitTest.java @@ -25,6 +25,7 @@ package com.intellectualsites.commands; import com.intellectualsites.commands.components.StaticComponent; import com.intellectualsites.commands.components.standard.EnumComponent; +import com.intellectualsites.commands.components.standard.IntegerComponent; import com.intellectualsites.commands.components.standard.StringComponent; import com.intellectualsites.commands.execution.CommandExecutionCoordinator; import org.bukkit.Bukkit; @@ -39,32 +40,57 @@ import java.util.stream.Collectors; public final class BukkitTest extends JavaPlugin { + private static final int PERC_MIN = 0; + private static final int PERC_MAX = 100; + @Override - public void onLoad() { + public void onEnable() { try { - final BukkitCommandManager commandManager = new BukkitCommandManager(this, - CommandExecutionCoordinator.simpleCoordinator()); + final PaperCommandManager commandManager = new PaperCommandManager(this, + CommandExecutionCoordinator.simpleCoordinator()); + commandManager.registerBrigadier(); commandManager.registerCommand(commandManager.commandBuilder("gamemode", - Collections.singleton("gajmöde"), - BukkitCommandMetaBuilder.builder() - .withDescription("Your ugli") - .build()) - .withComponent(EnumComponent.required(GameMode.class, "gamemode")) - .withComponent(StringComponent.newBuilder("player") - .withSuggestionsProvider((v1, v2) -> { - final List suggestions = new ArrayList<>(Bukkit.getOnlinePlayers().stream() - .map(Player::getName).collect(Collectors.toList())); - suggestions.add("dog"); - suggestions.add("cat"); - return suggestions; - }).build()) - .withHandler(c -> c.getCommandSender() - .asPlayer() - .setGameMode(c.get("gamemode") - .orElse(GameMode.SURVIVAL))) - .build()) + Collections.singleton("gajmöde"), + BukkitCommandMetaBuilder.builder() + .withDescription("Your ugli") + .build()) + .withComponent(EnumComponent.required(GameMode.class, "gamemode")) + .withComponent(StringComponent.newBuilder("player") + .withSuggestionsProvider((v1, v2) -> { + final List suggestions = + new ArrayList<>( + Bukkit.getOnlinePlayers() + .stream() + .map(Player::getName) + .collect(Collectors.toList())); + suggestions.add("dog"); + suggestions.add("cat"); + return suggestions; + }).build()) + .withHandler(c -> c.getCommandSender() + .asPlayer() + .setGameMode(c.get("gamemode") + .orElse(GameMode.SURVIVAL))) + .build()) .registerCommand(commandManager.commandBuilder("kenny") .withComponent(StaticComponent.required("sux")) + .withComponent(IntegerComponent + .newBuilder("perc") + .withMin(PERC_MIN).withMax(PERC_MAX).build()) + .withHandler(context -> { + context.getCommandSender().asPlayer().sendMessage(String.format( + "Kenny sux %d%%", + context.get("perc").orElse(PERC_MIN) + )); + }) + .build()) + .registerCommand(commandManager.commandBuilder("test") + .withComponent(StaticComponent.required("one")) + .withHandler(c -> c.getCommandSender().sendMessage("One!")) + .build()) + .registerCommand(commandManager.commandBuilder("test") + .withComponent(StaticComponent.required("two")) + .withHandler(c -> c.getCommandSender().sendMessage("Two!")) .build()); } catch (final Exception e) { e.printStackTrace(); diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommand.java b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommand.java index 1de292be..fd9ee28d 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommand.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommand.java @@ -24,6 +24,7 @@ package com.intellectualsites.commands; import com.intellectualsites.commands.components.CommandComponent; +import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.plugin.Plugin; @@ -56,6 +57,7 @@ final class BukkitCommand extends org.bukkit.command.Command implements PluginId this.bukkitCommandManager.executeCommand(BukkitCommandSender.of(commandSender), builder.toString()) .whenComplete(((commandResult, throwable) -> { if (throwable != null) { + commandSender.sendMessage(ChatColor.RED + throwable.getCause().getMessage()); throwable.printStackTrace(); } else { // Do something... diff --git a/cloud-minecraft/cloud-paper/pom.xml b/cloud-minecraft/cloud-paper/pom.xml new file mode 100644 index 00000000..a95c9ed8 --- /dev/null +++ b/cloud-minecraft/cloud-paper/pom.xml @@ -0,0 +1,95 @@ + + + + + + cloud + com.intellectualsites + 1.0-SNAPSHOT + ../../pom.xml + + 4.0.0 + + cloud-paper + + + + papermc + https://papermc.io/repo/repository/maven-public/ + + + + + + com.intellectualsites + cloud-bukkit + 1.0-SNAPSHOT + + + com.intellectualsites + cloud-brigadier + 1.0-SNAPSHOT + + + com.destroystokyo.paper + paper-api + 1.15.2-R0.1-SNAPSHOT + provided + + + com.destroystokyo.paper + paper-mojangapi + 1.15.2-R0.1-SNAPSHOT + provided + + + + + clean package + CommandStuff-${project.artifactId}-${project.version} + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + + diff --git a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperBrigadierListener.java b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperBrigadierListener.java new file mode 100644 index 00000000..dd195908 --- /dev/null +++ b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperBrigadierListener.java @@ -0,0 +1,59 @@ +// +// 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.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; +import com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent; +import com.intellectualsites.commands.brigadier.CloudBrigadierManager; +import com.intellectualsites.commands.components.CommandComponent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import javax.annotation.Nonnull; + +class PaperBrigadierListener implements Listener { + + private final CloudBrigadierManager brigadierManager; + private final PaperCommandManager paperCommandManager; + + PaperBrigadierListener(@Nonnull final PaperCommandManager paperCommandManager) throws Exception { + this.paperCommandManager = paperCommandManager; + this.brigadierManager = new CloudBrigadierManager<>(); + } + + @EventHandler + public void onCommandRegister(@Nonnull final CommandRegisteredEvent event) { + final CommandTree commandTree = this.paperCommandManager.getCommandTree(); + final CommandTree.Node> node = commandTree.getNamedNode(event.getCommandLabel()); + if (node == null) { + return; + } + event.setLiteral(this.brigadierManager.createLiteralCommandNode(node, + event.getLiteral(), + event.getBrigadierCommand(), + event.getBrigadierCommand(), + (s, p) -> s.getBukkitSender().hasPermission(p))); + } + +} diff --git a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperCommandManager.java b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperCommandManager.java new file mode 100644 index 00000000..ae85825f --- /dev/null +++ b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperCommandManager.java @@ -0,0 +1,65 @@ +// +// 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.execution.CommandExecutionCoordinator; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import javax.annotation.Nonnull; +import java.util.function.Function; + +/** + * Paper command manager that extends {@link BukkitCommandManager} + */ +public class PaperCommandManager extends BukkitCommandManager { + + /** + * Construct a new Paper command manager + * + * @param owningPlugin Plugin that is constructing the manager + * @param commandExecutionCoordinator Coordinator provider + * @throws Exception If the construction of the manager fails + */ + public PaperCommandManager(@Nonnull final Plugin owningPlugin, + @Nonnull final Function, + CommandExecutionCoordinator> commandExecutionCoordinator) throws + Exception { + super(owningPlugin, commandExecutionCoordinator); + } + + /** + * Register the brigadier listener + */ + public void registerBrigadier() { + try { + Bukkit.getPluginManager().registerEvents(new PaperBrigadierListener(this), + this.getOwningPlugin()); + } catch (final Exception e) { + this.getOwningPlugin().getLogger().severe("Failed to register Brigadier listener"); + e.printStackTrace(); + } + } + +} diff --git a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/package-info.java b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/package-info.java new file mode 100644 index 00000000..16035530 --- /dev/null +++ b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/package-info.java @@ -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. +// + +/** + * Paper specific implementation that extends the Bukkit implementation + */ +package com.intellectualsites.commands; diff --git a/pom.xml b/pom.xml index f259fdba..89243148 100644 --- a/pom.xml +++ b/pom.xml @@ -9,11 +9,13 @@ 1.0-SNAPSHOT cloud-jline - cloud-minecraft/cloud-bukkit cloud-core cloud-pipeline cloud-minecraft/cloud-bukkit-test + cloud-minecraft/cloud-bukkit + cloud-minecraft/cloud-paper + cloud-minecraft/cloud-brigadier pom 2020