diff --git a/CHANGELOG.md b/CHANGELOG.md index 5efa104f..477f90ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added CloudInjectionModule to cloud-velocity - Added PlayerArgument to cloud-velocity - Added TextColorArgument to minecraft-extras + - Added LocationArgument to cloud-bukkit - Added ServerArgument to cloud-velocity ## [1.0.2] - 2020-10-18 diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java index 6985f116..f18d0509 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandTree.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandTree.java @@ -464,30 +464,28 @@ public final class CommandTree { commandContext.store("__parsing_argument__", i + 2); } } - } - // END: Compound arguments - - // START: Flags - if (child.getValue() instanceof FlagArgument) { + } else if (child.getValue() instanceof FlagArgument) { /* Remove all but last */ while (commandQueue.size() > 1) { commandContext.store(FlagArgument.FLAG_META, commandQueue.remove()); } - } - // END: Flags - - // START: Array arguments - if (child.getValue() != null && GenericTypeReflector.erase(child.getValue().getValueType().getType()).isArray()) { + } else if (child.getValue() != null + && GenericTypeReflector.erase(child.getValue().getValueType().getType()).isArray()) { while (commandQueue.size() > 1) { commandQueue.remove(); } + } else if (child.getValue() != null) { + for (int i = 0; i < child.getValue().getParser().getRequestedArgumentCount() - 1 && commandQueue.size()> 1; i++) { + commandContext.store( + String.format("%s_%d", child.getValue().getName(), i), + commandQueue.remove() + ); + } } - // END: Array arguments if (child.getValue() != null) { if (commandQueue.isEmpty()) { return Collections.emptyList(); - // return child.getValue().getParser().suggestions(commandContext, ""); } else if (child.isLeaf() && commandQueue.size() < 2) { return child.getValue().getSuggestionsProvider().apply(commandContext, commandQueue.peek()); } else if (child.isLeaf()) { diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParser.java index 05a13b79..a5ddced0 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParser.java @@ -39,6 +39,11 @@ import java.util.Queue; @FunctionalInterface public interface ArgumentParser { + /** + * Default amount of arguments that the parser expects to consume + */ + int DEFAULT_ARGUMENT_COUNT = 1; + /** * Parse command input into a command result * @@ -75,4 +80,14 @@ public interface ArgumentParser { return false; } + /** + * Get the amount of arguments that this parsers seeks to + * consume + * + * @return The number of arguments tha the parser expects + */ + default int getRequestedArgumentCount() { + return DEFAULT_ARGUMENT_COUNT; + } + } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerArgument.java index 7d2e9bc1..cd9465a9 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerArgument.java @@ -190,7 +190,18 @@ public final class IntegerArgument extends CommandArgument { this.max = max; } - static @NonNull List<@NonNull String> getSuggestions(final long min, final long max, final @NonNull String input) { + /** + * Get integer suggestions. This supports both positive and negative numbers + * + * @param min Minimum value + * @param max Maximum value + * @param input Input + * @return List of suggestions + */ + public static @NonNull List<@NonNull String> getSuggestions( + final long min, + final long max, + final @NonNull String input) { final Set numbers = new TreeSet<>(); try { diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java index 9b25ab2f..7b8d8796 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java @@ -30,6 +30,7 @@ import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.SinglePlayerSelector; import com.mojang.brigadier.arguments.ArgumentType; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.enchantments.Enchantment; import org.checkerframework.checker.nullness.qual.NonNull; @@ -84,6 +85,8 @@ public final class BukkitBrigadierMapper { this.mapComplexNMS(SinglePlayerSelector.class, this.getEntitySelectorArgument(true, true)); this.mapComplexNMS(MultipleEntitySelector.class, this.getEntitySelectorArgument(false, false)); this.mapComplexNMS(MultiplePlayerSelector.class, this.getEntitySelectorArgument(false, true)); + /* Map Vec3 */ + this.mapComplexNMS(Location.class, this.getArgumentVec3()); } catch (final Exception e) { this.commandManager.getOwningPlugin() .getLogger() @@ -112,6 +115,18 @@ public final class BukkitBrigadierMapper { }; } + private Supplier> getArgumentVec3() { + return () -> { + try { + return (ArgumentType) this.getNMSArgument("Vec3").getDeclaredConstructor(boolean.class) + .newInstance(true); + } catch (final Exception e) { + this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve vector argument", e); + return null; + } + }; + } + /** * Attempt to retrieve an NMS argument type * diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionKeys.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionKeys.java index 9eeff4ca..5bacb8dd 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionKeys.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionKeys.java @@ -80,6 +80,10 @@ public final class BukkitCaptionKeys { */ public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER = of( "argument.parse.failure.selector.non_player_in_player_selector"); + /** + * Variables: {input} + */ + public static final Caption ARGUMENT_PARSE_FAILURE_LOCATION = of("argument.parse.failure.location"); private BukkitCaptionKeys() { } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionRegistry.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionRegistry.java index 1148b9b5..b7f6131a 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionRegistry.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionRegistry.java @@ -73,7 +73,11 @@ public class BukkitCaptionRegistry extends SimpleCaptionRegistry { * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER} */ public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER = "Non-player(s) selected in player selector."; - + /** + * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_LOCATION} + */ + public static final String ARGUMENT_PARSE_FAILURE_LOCATION = + "'{input}' is not a valid location. Required format is ' '"; protected BukkitCaptionRegistry() { super(); @@ -117,6 +121,10 @@ public class BukkitCaptionRegistry extends SimpleCaptionRegistry { BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER, (caption, sender) -> ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER ); + this.registerMessageFactory( + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_LOCATION, + (caption, sender) -> ARGUMENT_PARSE_FAILURE_LOCATION + ); } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java index bb615383..464e68b8 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java @@ -34,6 +34,7 @@ import cloud.commandframework.bukkit.parsers.MaterialArgument; import cloud.commandframework.bukkit.parsers.OfflinePlayerArgument; import cloud.commandframework.bukkit.parsers.PlayerArgument; import cloud.commandframework.bukkit.parsers.WorldArgument; +import cloud.commandframework.bukkit.parsers.location.LocationArgument; import cloud.commandframework.bukkit.parsers.selector.MultipleEntitySelectorArgument; import cloud.commandframework.bukkit.parsers.selector.MultiplePlayerSelectorArgument; import cloud.commandframework.bukkit.parsers.selector.SingleEntitySelectorArgument; @@ -43,6 +44,7 @@ import cloud.commandframework.tasks.TaskFactory; import cloud.commandframework.tasks.TaskRecipe; import io.leangen.geantyref.TypeToken; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.World; @@ -163,6 +165,8 @@ public class BukkitCommandManager extends CommandManager { new OfflinePlayerArgument.OfflinePlayerParser<>()); this.getParserRegistry().registerParserSupplier(TypeToken.get(Enchantment.class), parserParameters -> new EnchantmentArgument.EnchantmentParser<>()); + this.getParserRegistry().registerParserSupplier(TypeToken.get(Location.class), parserParameters -> + new LocationArgument.LocationParser<>()); /* Register Entity Selector Parsers */ this.getParserRegistry().registerParserSupplier(TypeToken.get(SingleEntitySelector.class), parserParameters -> new SingleEntitySelectorArgument.SingleEntitySelectorParser<>()); diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationArgument.java new file mode 100644 index 00000000..2902eb09 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationArgument.java @@ -0,0 +1,305 @@ +// +// 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.bukkit.parsers.location; + +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.arguments.standard.IntegerArgument; +import cloud.commandframework.bukkit.BukkitCaptionKeys; +import cloud.commandframework.captions.CaptionVariable; +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.ParserException; +import io.leangen.geantyref.TypeToken; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.util.Vector; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +/** + * Argument parser that parses {@link Location} from three doubles. This will use the command + * senders world when it exists, or else it'll use the first loaded Bukkit world + * + * @param Command sender type + * @since 1.1.0 + */ +public final class LocationArgument extends CommandArgument { + + private LocationArgument( + final boolean required, + final @NonNull String name, + final @NonNull String defaultValue, + final @Nullable BiFunction, String, List> suggestionsProvider, + final @NonNull Collection<@NonNull BiFunction<@NonNull CommandContext, + @NonNull Queue<@NonNull String>, @NonNull ArgumentParseResult>> argumentPreprocessors + ) { + super( + required, + name, + new LocationParser<>(), + defaultValue, + TypeToken.get(Location.class), + suggestionsProvider, + argumentPreprocessors + ); + } + + /** + * Create a new argument builder + * + * @param name Argument name + * @param Command sender type + * @return Builder instance + */ + public static @NonNull Builder newBuilder( + final @NonNull String name + ) { + return new Builder<>(name); + } + + /** + * Create a new required argument + * + * @param name Argument name + * @param Command sender type + * @return Constructed argument + */ + public static @NonNull CommandArgument of( + final @NonNull String name + ) { + return LocationArgument.newBuilder( + name + ).asRequired().build(); + } + + /** + * Create a new optional argument + * + * @param name Argument name + * @param Command sender type + * @return Constructed argument + */ + public static @NonNull CommandArgument optional( + final @NonNull String name + ) { + return LocationArgument.newBuilder( + name + ).asOptional().build(); + } + + + public static final class Builder extends CommandArgument.Builder { + + private Builder( + final @NonNull String name + ) { + super( + TypeToken.get(Location.class), + name + ); + } + + @Override + public @NonNull CommandArgument<@NonNull C, @NonNull Location> build() { + return new LocationArgument<>( + this.isRequired(), + this.getName(), + this.getDefaultValue(), + this.getSuggestionsProvider(), + new LinkedList<>() + ); + } + + } + + public static final class LocationParser implements ArgumentParser { + + private static final int EXPECTED_PARAMETER_COUNT = 3; + + private final LocationCoordinateParser locationCoordinateParser = new LocationCoordinateParser<>(); + + @Override + public @NonNull ArgumentParseResult<@NonNull Location> parse( + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue + ) { + if (inputQueue.size() < 3) { + final StringBuilder input = new StringBuilder(); + for (int i = 0; i < inputQueue.size(); i++) { + input.append(((LinkedList) inputQueue).get(i)); + if ((i + 1) < inputQueue.size()) { + input.append(" "); + } + } + return ArgumentParseResult.failure( + new LocationParseException( + commandContext, + input.toString() + ) + ); + } + final LocationCoordinate[] coordinates = new LocationCoordinate[3]; + for (int i = 0; i < 3; i++) { + final ArgumentParseResult<@NonNull LocationCoordinate> coordinate = this.locationCoordinateParser.parse( + commandContext, + inputQueue + ); + if (coordinate.getFailure().isPresent()) { + return ArgumentParseResult.failure( + coordinate.getFailure().get() + ); + } + coordinates[i] = coordinate.getParsedValue().orElseThrow(NullPointerException::new); + } + final Location originalLocation; + final CommandSender bukkitSender = commandContext.get("BukkitCommandSender"); + + if (bukkitSender instanceof BlockCommandSender) { + originalLocation = ((BlockCommandSender) bukkitSender).getBlock().getLocation(); + } else if (bukkitSender instanceof Entity) { + originalLocation = ((Entity) bukkitSender).getLocation(); + } else { + originalLocation = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + } + + if (((coordinates[0].getType() == LocationCoordinateType.LOCAL) + != (coordinates[1].getType() == LocationCoordinateType.LOCAL)) + || ((coordinates[0].getType() == LocationCoordinateType.LOCAL) + != (coordinates[2].getType() == LocationCoordinateType.LOCAL)) + ) { + final StringBuilder input = new StringBuilder(); + for (int i = 0; i < inputQueue.size(); i++) { + input.append(((LinkedList) inputQueue).get(i)); + if ((i + 1) < inputQueue.size()) { + input.append(" "); + } + } + return ArgumentParseResult.failure( + new LocationParseException( + commandContext, + input.toString() + ) + ); + } + + if (coordinates[0].getType() == LocationCoordinateType.ABSOLUTE) { + originalLocation.setX(coordinates[0].getCoordinate()); + } else if (coordinates[0].getType() == LocationCoordinateType.RELATIVE) { + originalLocation.add(coordinates[0].getCoordinate(), 0, 0); + } + + if (coordinates[1].getType() == LocationCoordinateType.ABSOLUTE) { + originalLocation.setY(coordinates[1].getCoordinate()); + } else if (coordinates[1].getType() == LocationCoordinateType.RELATIVE) { + originalLocation.add(0, coordinates[1].getCoordinate(), 0); + } + + if (coordinates[2].getType() == LocationCoordinateType.ABSOLUTE) { + originalLocation.setZ(coordinates[2].getCoordinate()); + } else if (coordinates[2].getType() == LocationCoordinateType.RELATIVE) { + originalLocation.add(0, 0, coordinates[2].getCoordinate()); + } else { + final double multiplier = 0.017453292D; + final double f = Math.cos((originalLocation.getYaw() + 90.0F) * multiplier); + final double f1 = Math.sin((originalLocation.getY() + 90.0F) * multiplier); + final double f2 = Math.cos(-originalLocation.getPitch() * multiplier); + final double f3 = Math.sin(-originalLocation.getPitch() * multiplier); + final double f4 = Math.cos((-originalLocation.getPitch() + 90.0F) * multiplier); + final double f5 = Math.sin((-originalLocation.getPitch() + 90.0F) * multiplier); + final Vector vec1 = new Vector(f * f2, f3, f1 * f2); + final Vector vec2 = new Vector(f * f4, f5, f1 * f4); + final Vector vec3 = vec1.crossProduct(vec2).multiply(-1); + final Vector vec4 = new Vector( + vec1.getX() * coordinates[2].getCoordinate() + vec2.getX() * coordinates[1].getCoordinate() + + vec3.getX() * coordinates[0].getCoordinate(), + vec1.getY() * coordinates[2].getCoordinate() + vec2.getY() * coordinates[1].getCoordinate() + + vec3.getY() * coordinates[0].getCoordinate(), + vec1.getZ() * coordinates[1].getCoordinate() + vec2.getZ() * coordinates[1].getCoordinate() + + vec3.getZ() * coordinates[0].getCoordinate() + ); + originalLocation.add(vec4); + } + + return ArgumentParseResult.success( + originalLocation + ); + } + + @Override + public @NonNull List<@NonNull String> suggestions( + final @NonNull CommandContext commandContext, + final @NonNull String input + ) { + final String workingInput; + final String prefix; + if (input.startsWith("~") || input.startsWith("^")) { + prefix = Character.toString(input.charAt(0)); + workingInput = input.substring(1); + } else { + prefix = ""; + workingInput = input; + } + return IntegerArgument.IntegerParser.getSuggestions( + Integer.MIN_VALUE, + Integer.MAX_VALUE, + workingInput + ).stream().map(string -> prefix + string).collect(Collectors.toList()); + } + + @Override + public int getRequestedArgumentCount() { + return EXPECTED_PARAMETER_COUNT; + } + + } + + + private static class LocationParseException extends ParserException { + + protected LocationParseException( + final @NonNull CommandContext context, + final @NonNull String input + ) { + super( + LocationParser.class, + context, + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_LOCATION, + CaptionVariable.of("input", input) + ); + } + + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinate.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinate.java new file mode 100644 index 00000000..88ef01ae --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinate.java @@ -0,0 +1,103 @@ +// +// 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.bukkit.parsers.location; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Objects; + +/** + * Single coordinate with a type + * + * @since 1.1.0 + */ +public final class LocationCoordinate { + + private final LocationCoordinateType type; + private final double coordinate; + + private LocationCoordinate( + final @NonNull LocationCoordinateType type, + final double coordinate + ) { + this.type = type; + this.coordinate = coordinate; + } + + /** + * Create a new location coordinate + * + * @param type Coordinate type + * @param coordinate Coordinate + * @return Created coordinate instance + */ + public static @NonNull LocationCoordinate of( + final @NonNull LocationCoordinateType type, + final double coordinate + ) { + return new LocationCoordinate(type, coordinate); + } + + /** + * Get the coordinate type + * + * @return Coordinate type + */ + public @NonNull LocationCoordinateType getType() { + return this.type; + } + + /** + * Get the coordinate + * + * @return Coordinate + */ + public double getCoordinate() { + return this.coordinate; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final LocationCoordinate that = (LocationCoordinate) o; + return Double.compare(that.coordinate, coordinate) == 0 + && type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(type, coordinate); + } + + @Override + public String toString() { + return String.format("LocationCoordinate{type=%s, coordinate=%f}", this.type.name().toLowerCase(), this.coordinate); + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinateParser.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinateParser.java new file mode 100644 index 00000000..9d413b69 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinateParser.java @@ -0,0 +1,93 @@ +// +// 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.bukkit.parsers.location; + +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.arguments.standard.DoubleArgument; +import cloud.commandframework.bukkit.parsers.PlayerArgument; +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.NoInputProvidedException; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Queue; + +/** + * A single coordinate, meant to be used as an element in a position vector + * + * @param Command sender type + * @since 1.1.0 + */ +public final class LocationCoordinateParser implements ArgumentParser { + + @Override + public @NonNull ArgumentParseResult<@NonNull LocationCoordinate> parse( + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue + ) { + String input = inputQueue.peek(); + + if (input == null) { + return ArgumentParseResult.failure(new NoInputProvidedException( + PlayerArgument.PlayerParser.class, + commandContext + )); + } + + /* Determine the type */ + final LocationCoordinateType locationCoordinateType; + if (input.startsWith("^")) { + locationCoordinateType = LocationCoordinateType.LOCAL; + input = input.substring(1); + } else if (input.startsWith("~")) { + locationCoordinateType = LocationCoordinateType.RELATIVE; + input = input.substring(1); + } else { + locationCoordinateType = LocationCoordinateType.ABSOLUTE; + } + + final double coordinate; + try { + coordinate = input.isEmpty() ? 0 : Double.parseDouble(input); + } catch (final Exception e) { + return ArgumentParseResult.failure( + new DoubleArgument.DoubleParseException( + input, + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, + commandContext + ) + ); + } + + inputQueue.remove(); + return ArgumentParseResult.success( + LocationCoordinate.of( + locationCoordinateType, + coordinate + ) + ); + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinateType.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinateType.java new file mode 100644 index 00000000..5ba71e79 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationCoordinateType.java @@ -0,0 +1,44 @@ +// +// 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.bukkit.parsers.location; + +/** + * Type of location coordinates + * + * @since 1.1.0 + */ +public enum LocationCoordinateType { + /** + * Absolute coordinate + */ + ABSOLUTE, + /** + * Coordinate relative to some origin + */ + RELATIVE, + /** + * Coordinates relative to a local coordinate space + */ + LOCAL +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/package-info.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/package-info.java new file mode 100644 index 00000000..7bfc220e --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/package-info.java @@ -0,0 +1,28 @@ +// +// 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. +// + +/** + * Vanilla-like location arguments + */ +package cloud.commandframework.bukkit.parsers.location; 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 960172ba..87e77fbf 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 @@ -59,6 +59,7 @@ import cloud.commandframework.extra.confirmation.CommandConfirmationManager; import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.minecraft.extras.TextColorArgument; import cloud.commandframework.paper.PaperCommandManager; +import cloud.commandframework.tasks.TaskConsumer; import cloud.commandframework.types.tuples.Triplet; import io.leangen.geantyref.TypeToken; import net.kyori.adventure.identity.Identity; @@ -447,4 +448,13 @@ public final class ExamplePlugin extends JavaPlugin { ); } + @CommandMethod("example teleport complex ") + private void teleportComplex( + final @NonNull Player sender, + final @NonNull @Argument("location") Location location + ) { + this.manager.taskRecipe().begin(location).synchronous((@NonNull TaskConsumer) sender::teleport) + .execute(() -> sender.sendMessage("You have been teleported!")); + } + }