From 30e04bfa2f7d2dfc6059bed52627411a787fc4c0 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 20 Mar 2022 10:42:28 -0700
Subject: [PATCH] Add Position

---
 .../io/papermc/paper/math/BlockPosition.java  | 100 +++++++++
 .../papermc/paper/math/BlockPositionImpl.java |   4 +
 .../io/papermc/paper/math/FinePosition.java   |  57 ++++++
 .../papermc/paper/math/FinePositionImpl.java  |   4 +
 .../java/io/papermc/paper/math/Position.java  | 192 ++++++++++++++++++
 .../src/main/java/org/bukkit/Location.java    |  29 ++-
 6 files changed, 385 insertions(+), 1 deletion(-)
 create mode 100644 paper-api/src/main/java/io/papermc/paper/math/BlockPosition.java
 create mode 100644 paper-api/src/main/java/io/papermc/paper/math/BlockPositionImpl.java
 create mode 100644 paper-api/src/main/java/io/papermc/paper/math/FinePosition.java
 create mode 100644 paper-api/src/main/java/io/papermc/paper/math/FinePositionImpl.java
 create mode 100644 paper-api/src/main/java/io/papermc/paper/math/Position.java

diff --git a/paper-api/src/main/java/io/papermc/paper/math/BlockPosition.java b/paper-api/src/main/java/io/papermc/paper/math/BlockPosition.java
new file mode 100644
index 0000000000..c358bfdefc
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/math/BlockPosition.java
@@ -0,0 +1,100 @@
+package io.papermc.paper.math;
+
+import org.bukkit.Axis;
+import org.bukkit.block.BlockFace;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * A position represented with integers.
+ * <p>
+ * <b>May see breaking changes until Experimental annotation is removed.</b>
+ *
+ * @see FinePosition
+ */
+@ApiStatus.Experimental
+@NullMarked
+public interface BlockPosition extends Position {
+
+    @Override
+    default double x() {
+        return this.blockX();
+    }
+
+    @Override
+    default double y() {
+        return this.blockY();
+    }
+
+    @Override
+    default double z() {
+        return this.blockZ();
+    }
+
+    @Override
+    default boolean isBlock() {
+        return true;
+    }
+
+    @Override
+    default boolean isFine() {
+        return false;
+    }
+
+    @Override
+    default BlockPosition toBlock() {
+        return this;
+    }
+
+    @Override
+    default BlockPosition offset(final int x, final int y, final int z) {
+        return x == 0 && y == 0 && z == 0 ? this : new BlockPositionImpl(this.blockX() + x, this.blockY() + y, this.blockZ() + z);
+    }
+
+    @Override
+    default FinePosition offset(final double x, final double y, final double z) {
+        return new FinePositionImpl(this.blockX() + x, this.blockY() + y, this.blockZ() + z);
+    }
+
+    /**
+     * Returns a block position offset by 1 in the direction specified.
+     *
+     * @param blockFace the block face to offset towards
+     * @return the offset block position
+     */
+    @Contract(value = "_ -> new", pure = true)
+    default BlockPosition offset(final BlockFace blockFace) {
+        return this.offset(blockFace, 1);
+    }
+
+    /**
+     * Returns a block position offset in the direction specified
+     * multiplied by the amount.
+     *
+     * @param blockFace the block face to offset towards
+     * @param amount the number of times to move in that direction
+     * @return the offset block position
+     */
+    @Contract(pure = true)
+    default BlockPosition offset(final BlockFace blockFace, final int amount) {
+        return amount == 0 ? this : new BlockPositionImpl(this.blockX() + (blockFace.getModX() * amount), this.blockY() + (blockFace.getModY() * amount), this.blockZ() + (blockFace.getModZ() * amount));
+    }
+
+    /**
+     * Returns a block position offset by the amount along
+     * the specified axis.
+     *
+     * @param axis the axis to offset along
+     * @param amount the amount to offset along that axis
+     * @return the offset block position
+     */
+    @Contract(pure = true)
+    default BlockPosition offset(final Axis axis, final int amount) {
+        return amount == 0 ? this : switch (axis) {
+            case X -> new BlockPositionImpl(this.blockX() + amount, this.blockY(), this.blockZ());
+            case Y -> new BlockPositionImpl(this.blockX(), this.blockY() + amount, this.blockZ());
+            case Z -> new BlockPositionImpl(this.blockX(), this.blockY(), this.blockZ() + amount);
+        };
+    }
+}
diff --git a/paper-api/src/main/java/io/papermc/paper/math/BlockPositionImpl.java b/paper-api/src/main/java/io/papermc/paper/math/BlockPositionImpl.java
new file mode 100644
index 0000000000..eb5a3f26c7
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/math/BlockPositionImpl.java
@@ -0,0 +1,4 @@
+package io.papermc.paper.math;
+
+record BlockPositionImpl(int blockX, int blockY, int blockZ) implements BlockPosition {
+}
diff --git a/paper-api/src/main/java/io/papermc/paper/math/FinePosition.java b/paper-api/src/main/java/io/papermc/paper/math/FinePosition.java
new file mode 100644
index 0000000000..b9c0065d8a
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/math/FinePosition.java
@@ -0,0 +1,57 @@
+package io.papermc.paper.math;
+
+import org.bukkit.util.NumberConversions;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * A position represented with doubles.
+ * <p>
+ * <b>May see breaking changes until Experimental annotation is removed.</b>
+ *
+ * @see BlockPosition
+ */
+@ApiStatus.Experimental
+@NullMarked
+public interface FinePosition extends Position {
+
+    @Override
+    default int blockX() {
+        return NumberConversions.floor(this.x());
+    }
+
+    @Override
+    default int blockY() {
+        return NumberConversions.floor(this.y());
+    }
+
+    @Override
+    default int blockZ() {
+        return NumberConversions.floor(this.z());
+    }
+
+    @Override
+    default boolean isBlock() {
+        return false;
+    }
+
+    @Override
+    default boolean isFine() {
+        return true;
+    }
+
+    @Override
+    default BlockPosition toBlock() {
+        return new BlockPositionImpl(this.blockX(), this.blockY(), this.blockZ());
+    }
+
+    @Override
+    default FinePosition offset(final int x, final int y, final int z) {
+        return this.offset((double) x, y, z);
+    }
+
+    @Override
+    default FinePosition offset(final double x, final double y, final double z) {
+        return x == 0.0 && y == 0.0 && z == 0.0 ? this : new FinePositionImpl(this.x() + x, this.y() + y, this.z() + z);
+    }
+}
diff --git a/paper-api/src/main/java/io/papermc/paper/math/FinePositionImpl.java b/paper-api/src/main/java/io/papermc/paper/math/FinePositionImpl.java
new file mode 100644
index 0000000000..93476aaf8d
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/math/FinePositionImpl.java
@@ -0,0 +1,4 @@
+package io.papermc.paper.math;
+
+record FinePositionImpl(double x, double y, double z) implements FinePosition {
+}
diff --git a/paper-api/src/main/java/io/papermc/paper/math/Position.java b/paper-api/src/main/java/io/papermc/paper/math/Position.java
new file mode 100644
index 0000000000..0e6a6a6738
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/math/Position.java
@@ -0,0 +1,192 @@
+package io.papermc.paper.math;
+
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * Common interface for {@link FinePosition} and {@link BlockPosition}.
+ * <p>
+ * <b>May see breaking changes until Experimental annotation is removed.</b>
+ */
+@ApiStatus.Experimental
+@NullMarked
+public interface Position {
+
+    FinePosition FINE_ZERO = new FinePositionImpl(0, 0, 0);
+    BlockPosition BLOCK_ZERO = new BlockPositionImpl(0, 0, 0);
+
+    /**
+     * Gets the block x value for this position
+     *
+     * @return the block x value
+     */
+    int blockX();
+
+    /**
+     * Gets the block x value for this position
+     *
+     * @return the block x value
+     */
+    int blockY();
+
+    /**
+     * Gets the block x value for this position
+     *
+     * @return the block x value
+     */
+    int blockZ();
+
+    /**
+     * Gets the x value for this position
+     *
+     * @return the x value
+     */
+    double x();
+
+    /**
+     * Gets the y value for this position
+     *
+     * @return the y value
+     */
+    double y();
+
+    /**
+     * Gets the z value for this position
+     *
+     * @return the z value
+     */
+    double z();
+
+    /**
+     * Checks of this position represents a {@link BlockPosition}
+     *
+     * @return true if block
+     */
+    boolean isBlock();
+
+    /**
+     * Checks if this position represents a {@link FinePosition}
+     *
+     * @return true if fine
+     */
+    boolean isFine();
+
+    /**
+     * Checks if each component of this position is finite.
+     */
+    default boolean isFinite() {
+        return Double.isFinite(this.x()) && Double.isFinite(this.y()) && Double.isFinite(this.z());
+    }
+
+    /**
+     * Returns a position offset by the specified amounts.
+     *
+     * @param x x value to offset
+     * @param y y value to offset
+     * @param z z value to offset
+     * @return the offset position
+     */
+    Position offset(int x, int y, int z);
+
+    /**
+     * Returns a position offset by the specified amounts.
+     *
+     * @param x x value to offset
+     * @param y y value to offset
+     * @param z z value to offset
+     * @return the offset position
+     */
+    FinePosition offset(double x, double y, double z);
+
+    /**
+     * Returns a new position at the center of the block position this represents
+     *
+     * @return a new center position
+     */
+    @Contract(value = "-> new", pure = true)
+    default FinePosition toCenter() {
+        return new FinePositionImpl(this.blockX() + 0.5, this.blockY() + 0.5, this.blockZ() + 0.5);
+    }
+
+    /**
+     * Returns the block position of this position
+     * or itself if it already is a block position
+     *
+     * @return the block position
+     */
+    @Contract(pure = true)
+    BlockPosition toBlock();
+
+    /**
+     * Converts this position to a vector
+     *
+     * @return a new vector
+     */
+    @Contract(value = "-> new", pure = true)
+    default Vector toVector() {
+        return new Vector(this.x(), this.y(), this.z());
+    }
+
+    /**
+     * Creates a new location object at this position with the specified world
+     *
+     * @param world the world for the location object
+     * @return a new location
+     */
+    @Contract(value = "_ -> new", pure = true)
+    default Location toLocation(final World world) {
+        return new Location(world, this.x(), this.y(), this.z());
+    }
+
+    /**
+     * Creates a position at the coordinates
+     *
+     * @param x x coord
+     * @param y y coord
+     * @param z z coord
+     * @return a position with those coords
+     */
+    @Contract(value = "_, _, _ -> new", pure = true)
+    static BlockPosition block(final int x, final int y, final int z) {
+        return new BlockPositionImpl(x, y, z);
+    }
+
+    /**
+     * Creates a position from the location.
+     *
+     * @param location the location to copy the position of
+     * @return a new position at that location
+     */
+    @Contract(value = "_ -> new", pure = true)
+    static BlockPosition block(final Location location) {
+        return new BlockPositionImpl(location.getBlockX(), location.getBlockY(), location.getBlockZ());
+    }
+
+    /**
+     * Creates a position at the coordinates
+     *
+     * @param x x coord
+     * @param y y coord
+     * @param z z coord
+     * @return a position with those coords
+     */
+    @Contract(value = "_, _, _ -> new", pure = true)
+    static FinePosition fine(final double x, final double y, final double z) {
+        return new FinePositionImpl(x, y, z);
+    }
+
+    /**
+     * Creates a position from the location.
+     *
+     * @param location the location to copy the position of
+     * @return a new position at that location
+     */
+    @Contract(value = "_ -> new", pure = true)
+    static FinePosition fine(final Location location) {
+        return new FinePositionImpl(location.getX(), location.getY(), location.getZ());
+    }
+}
diff --git a/paper-api/src/main/java/org/bukkit/Location.java b/paper-api/src/main/java/org/bukkit/Location.java
index 734054f1e8..bc8a64d54e 100644
--- a/paper-api/src/main/java/org/bukkit/Location.java
+++ b/paper-api/src/main/java/org/bukkit/Location.java
@@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable;
  * magnitude than 360 are valid, but may be normalized to any other equivalent
  * representation by the implementation.
  */
-public class Location implements Cloneable, ConfigurationSerializable {
+public class Location implements Cloneable, ConfigurationSerializable, io.papermc.paper.math.FinePosition { // Paper
     private Reference<World> world;
     private double x;
     private double y;
@@ -706,4 +706,31 @@ public class Location implements Cloneable, ConfigurationSerializable {
         }
         return pitch;
     }
+
+    // Paper - add Position
+    @Override
+    public double x() {
+        return this.getX();
+    }
+
+    @Override
+    public double y() {
+        return this.getY();
+    }
+
+    @Override
+    public double z() {
+        return this.getZ();
+    }
+
+    @Override
+    public boolean isFinite() {
+        return io.papermc.paper.math.FinePosition.super.isFinite() && Float.isFinite(this.getYaw()) && Float.isFinite(this.getPitch());
+    }
+
+    @Override
+    public @NotNull Location toLocation(@NotNull World world) {
+        return new Location(world, this.x(), this.y(), this.z(), this.getYaw(), this.getPitch());
+    }
+    // Paper end
 }