From fcae5b863765174105f0cfcc1a24d349fb4eb7ec Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 9 Jan 2021 00:16:17 -0800 Subject: [PATCH] Add Location2DArgument, mapped to NMS Vec2I (#201) --- .../bukkit/BukkitBrigadierMapper.java | 21 +- .../bukkit/BukkitCommandManager.java | 4 + .../bukkit/parsers/location/Location2D.java | 54 ++++ .../parsers/location/Location2DArgument.java | 248 ++++++++++++++++++ .../parsers/location/LocationArgument.java | 58 ++-- 5 files changed, 363 insertions(+), 22 deletions(-) create mode 100644 cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2D.java create mode 100644 cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DArgument.java 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 ed15eed5..3b4353a4 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 @@ -28,6 +28,7 @@ import cloud.commandframework.bukkit.arguments.selector.MultipleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.MultiplePlayerSelector; import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.SinglePlayerSelector; +import cloud.commandframework.bukkit.parsers.location.Location2D; import com.mojang.brigadier.arguments.ArgumentType; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -88,6 +89,8 @@ public final class BukkitBrigadierMapper { this.mapComplexNMS(MultiplePlayerSelector.class, this.getEntitySelectorArgument(false, true)); /* Map Vec3 */ this.mapComplexNMS(Location.class, this.getArgumentVec3()); + /* Map Vec2I */ + this.mapComplexNMS(Location2D.class, this.getArgumentVec2I()); } catch (final Exception e) { this.commandManager.getOwningPlugin() .getLogger() @@ -100,7 +103,7 @@ public final class BukkitBrigadierMapper { * @param playersOnly Whether the selector is for players only (true), or for all entities (false) * @return The NMS ArgumentType */ - private Supplier> getEntitySelectorArgument( + private @NonNull Supplier> getEntitySelectorArgument( final boolean single, final boolean playersOnly ) { @@ -117,13 +120,25 @@ public final class BukkitBrigadierMapper { } @SuppressWarnings("UnnecessaryLambda") - private Supplier> getArgumentVec3() { + private @NonNull 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); + this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Vec3D argument", e); + return null; + } + }; + } + + @SuppressWarnings("UnnecessaryLambda") + private @NonNull Supplier> getArgumentVec2I() { + return () -> { + try { + return (ArgumentType) this.getNMSArgument("Vec2I").getDeclaredConstructor().newInstance(); + } catch (final Exception e) { + this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Vec2I argument", e); return null; } }; 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 d1273574..c0a11681 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 @@ -36,7 +36,9 @@ 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.Location2D; import cloud.commandframework.bukkit.parsers.location.LocationArgument; +import cloud.commandframework.bukkit.parsers.location.Location2DArgument; import cloud.commandframework.bukkit.parsers.selector.MultipleEntitySelectorArgument; import cloud.commandframework.bukkit.parsers.selector.MultiplePlayerSelectorArgument; import cloud.commandframework.bukkit.parsers.selector.SingleEntitySelectorArgument; @@ -172,6 +174,8 @@ public class BukkitCommandManager extends CommandManager implements Brigad new EnchantmentArgument.EnchantmentParser<>()); this.getParserRegistry().registerParserSupplier(TypeToken.get(Location.class), parserParameters -> new LocationArgument.LocationParser<>()); + this.getParserRegistry().registerParserSupplier(TypeToken.get(Location2D.class), parserParameters -> + new Location2DArgument.Location2DParser<>()); /* 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/Location2D.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2D.java new file mode 100644 index 00000000..33155624 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2D.java @@ -0,0 +1,54 @@ +// +// 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.bukkit.Location; +import org.bukkit.World; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * {@link Location} projected onto the XZ-plane + * + * @since 1.4.0 + */ +public class Location2D extends Location { + + protected Location2D(final @Nullable World world, final double x, final double z) { + super(world, x, 0, z); + } + + /** + * Get a new Location2D + * + * @param world World this location is in + * @param x X position for this location + * @param z Z position for this location + * @return Location2D + */ + public static @NonNull Location2D from(final @Nullable World world, final double x, final double z) { + return new Location2D(world, x, z); + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DArgument.java new file mode 100644 index 00000000..6aebfe7d --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DArgument.java @@ -0,0 +1,248 @@ +// +// 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.bukkit.parsers.location.LocationArgument.LocationParseException; +import cloud.commandframework.context.CommandContext; +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; + +/** + * Argument parser that parses {@link Location2D} from two 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.4.0 + */ +public final class Location2DArgument extends CommandArgument { + + private Location2DArgument( + 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 Location2DParser<>(), + defaultValue, + TypeToken.get(Location2D.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 Location2DArgument.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 Location2DArgument.newBuilder( + name + ).asOptional().build(); + } + + + public static final class Builder extends CommandArgument.Builder { + + private Builder( + final @NonNull String name + ) { + super( + TypeToken.get(Location2D.class), + name + ); + } + + @Override + public @NonNull CommandArgument<@NonNull C, @NonNull Location2D> build() { + return new Location2DArgument<>( + this.isRequired(), + this.getName(), + this.getDefaultValue(), + this.getSuggestionsProvider(), + new LinkedList<>() + ); + } + + } + + + public static final class Location2DParser implements ArgumentParser { + + private static final int EXPECTED_PARAMETER_COUNT = 2; + + private final LocationCoordinateParser locationCoordinateParser = new LocationCoordinateParser<>(); + + @Override + public @NonNull ArgumentParseResult<@NonNull Location2D> parse( + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue + ) { + if (inputQueue.size() < 2) { + final StringBuilder input = new StringBuilder(); + for (int i = 0; i < inputQueue.size(); i++) { + input.append(((LinkedList) inputQueue).get(i)); + } + return ArgumentParseResult.failure( + new LocationParseException( + commandContext, + LocationParseException.FailureReason.WRONG_FORMAT, + input.toString() + ) + ); + } + final LocationCoordinate[] coordinates = new LocationCoordinate[2]; + for (int i = 0; i < 2; 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) { + return ArgumentParseResult.failure( + new LocationParseException( + commandContext, + LocationParseException.FailureReason.MIXED_LOCAL_ABSOLUTE, + "" + ) + ); + } + + 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.setZ(coordinates[1].getCoordinate()); + } else if (coordinates[1].getType() == LocationCoordinateType.RELATIVE) { + originalLocation.add(0, 0, coordinates[1].getCoordinate()); + } else { + final Vector declaredPos = new Vector( + coordinates[0].getCoordinate(), + 0, + coordinates[1].getCoordinate() + ); + final Location local = LocationArgument.LocationParser.toLocalSpace(originalLocation, declaredPos); + return ArgumentParseResult.success(Location2D.from( + originalLocation.getWorld(), + local.getX(), + local.getZ() + )); + } + + return ArgumentParseResult.success(Location2D.from( + originalLocation.getWorld(), + originalLocation.getX(), + originalLocation.getZ() + )); + } + + @Override + public @NonNull List<@NonNull String> suggestions( + final @NonNull CommandContext commandContext, + final @NonNull String input + ) { + return LocationArgument.LocationParser.getSuggestions(commandContext, input); + } + + @Override + public int getRequestedArgumentCount() { + return EXPECTED_PARAMETER_COUNT; + } + + } + +} 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 index 21537302..148d3e43 100644 --- 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 @@ -228,25 +228,14 @@ public final class LocationArgument extends CommandArgument { } 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() + final Vector declaredPos = new Vector( + coordinates[0].getCoordinate(), + coordinates[1].getCoordinate(), + coordinates[2].getCoordinate() + ); + return ArgumentParseResult.success( + toLocalSpace(originalLocation, declaredPos) ); - originalLocation.add(vec4); } return ArgumentParseResult.success( @@ -254,10 +243,41 @@ public final class LocationArgument extends CommandArgument { ); } + static @NonNull Location toLocalSpace(final @NonNull Location originalLocation, final @NonNull Vector declaredPos) { + final double cosYaw = Math.cos(toRadians(originalLocation.getYaw() + 90.0F)); + final double sinYaw = Math.sin(toRadians(originalLocation.getYaw() + 90.0F)); + final double cosPitch = Math.cos(toRadians(-originalLocation.getPitch())); + final double sinPitch = Math.sin(toRadians(-originalLocation.getPitch())); + final double cosNegYaw = Math.cos(toRadians(-originalLocation.getPitch() + 90.0F)); + final double sinNegYaw = Math.sin(toRadians(-originalLocation.getPitch() + 90.0F)); + final Vector zModifier = new Vector(cosYaw * cosPitch, sinPitch, sinYaw * cosPitch); + final Vector yModifier = new Vector(cosYaw * cosNegYaw, sinNegYaw, sinYaw * cosNegYaw); + final Vector xModifier = zModifier.crossProduct(yModifier).multiply(-1); + final double xOffset = dotProduct(declaredPos, xModifier.getX(), yModifier.getX(), zModifier.getX()); + final double yOffset = dotProduct(declaredPos, xModifier.getY(), yModifier.getY(), zModifier.getY()); + final double zOffset = dotProduct(declaredPos, xModifier.getZ(), yModifier.getZ(), zModifier.getZ()); + return originalLocation.add(xOffset, yOffset, zOffset); + } + + private static double dotProduct(final Vector location, final double x, final double y, final double z) { + return location.getX() * x + location.getY() * y + location.getZ() * z; + } + + private static float toRadians(final float degrees) { + return degrees * (float) Math.PI / 180f; + } + @Override public @NonNull List<@NonNull String> suggestions( final @NonNull CommandContext commandContext, final @NonNull String input + ) { + return LocationArgument.LocationParser.getSuggestions(commandContext, input); + } + + static @NonNull List<@NonNull String> getSuggestions( + final @NonNull CommandContext commandContext, + final @NonNull String input ) { final String workingInput; final String prefix; @@ -283,7 +303,7 @@ public final class LocationArgument extends CommandArgument { } - private static class LocationParseException extends ParserException { + static class LocationParseException extends ParserException { private static final long serialVersionUID = -3261835227265878218L;