diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index d3a91b2..9ab55dd 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -8,10 +8,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '25' distribution: temurin cache: maven - name: Set up Node.js diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 5b24081..3e7ad7f 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -18,10 +18,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '25' distribution: 'temurin' cache: maven - name: Build with Maven diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1f1eab7..8dc7ac1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - name: Set up JDK 21 + - name: Set up JDK 25 uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '25' distribution: temurin cache: maven server-id: twme-repo-releases diff --git a/README.md b/README.md index c3c60ae..c79f291 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,21 @@ Version 3.0.0 replaces the packet module's EntityLib implementation with Virtual ## Requirements -- Java 17+ -- Minecraft 1.19.4+ for Text Display entities -- PacketEvents 2.13.0+ initialized by the host platform when using the packet module +- Minecraft 26.3 is the compiled and verified target. The modules keep Java 17 bytecode and their API usage is unchanged by this upgrade, so the existing Text Display floor (Minecraft 1.19.4+) is unaffected. +- Java 17+ at runtime: the published modules keep Java 17 bytecode +- Java 25 to build from source, because the 26.3 APIs are Java 25 class files +- PacketEvents 2.14.0+ initialized by the host platform when using the packet module - VirtualEntities v0.9.0 is pulled transitively by the packet module +| Component | Version | +| --- | --- | +| Minecraft | 26.3 | +| Paper API | `26.3.build.41-alpha` | +| Spigot API | `26.3-R0.1-SNAPSHOT` | +| PacketEvents | 2.14.0 | +| Build JDK | 25 | +| Runtime bytecode | Java 17 | + ## Installation ### Official TWME Maven repository (recommended) @@ -128,13 +138,13 @@ Builders provide color, brightness, see-through, view range, double-sided, root- ## Migrating From 2.x - Remove EntityLib initialization and dependencies. -- Use PacketEvents 2.13.0+ and let VirtualEntities manage packet-only entities. +- Use PacketEvents 2.14.0+ and let VirtualEntities manage packet-only entities. - Prefer `new PacketShapeFactory(VirtualEntities.create())` at plugin scope; the no-argument constructor remains available for small standalone uses and closes its manager when the factory is closed. - `PacketLine`, `PacketPolyline`, `PacketTriangle`, and `PacketParallelogram` now expose `List` from `getEntities()` instead of EntityLib wrappers. ## Verification -`integration/mineflayer/run-e2e.sh` launches Paper 1.21.11 with PacketEvents, creates a packet-only root-anchored line, and verifies from Mineflayer that Text Display spawn, metadata rebase, root movement, and packet bundle ordering all work. The same test can run from the manual GitHub Actions E2E workflow. +`integration/mineflayer/run-e2e.sh` launches Paper 1.21.11 with PacketEvents, creates a packet-only root-anchored line, and verifies from Mineflayer that Text Display spawn, metadata rebase, root movement, and packet bundle ordering all work. The same test can run from the manual GitHub Actions E2E workflow. Mineflayer still only speaks up to Minecraft 26.1, so that harness keeps its 1.21.11 server even though the modules target 26.3. ## Credits diff --git a/api/src/main/java/dev/twme/textdisplayshape/util/TextDisplayUtil.java b/api/src/main/java/dev/twme/textdisplayshape/util/TextDisplayUtil.java index 3cc3a70..17be651 100644 --- a/api/src/main/java/dev/twme/textdisplayshape/util/TextDisplayUtil.java +++ b/api/src/main/java/dev/twme/textdisplayshape/util/TextDisplayUtil.java @@ -7,6 +7,7 @@ import org.joml.Matrix4f; import org.joml.Matrix4fc; +import org.joml.Matrix3f; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -69,6 +70,18 @@ private static Matrix4f shear(Matrix4fc mat, float yx, float xy) { return new Matrix4f(mat).mul(shearMatrix); } + private static Quaternionf orientation(Vector3f zAxis, Vector3f yAxis) { + // Construct the basis explicitly. Quaternionf.lookAlong has produced + // different handedness for some axes across JOML versions used by + // Minecraft servers; the supplied vertices must determine local +X. + Vector3f xAxis = new Vector3f(yAxis).cross(zAxis).normalize(); + Matrix3f basis = new Matrix3f() + .setColumn(0, xAxis) + .setColumn(1, yAxis) + .setColumn(2, zAxis); + return new Quaternionf().setFromNormalized(basis).normalize(); + } + /** * Gets the transformation matrix for a unit square. * @@ -116,7 +129,7 @@ public static TextDisplayTriangleResult textDisplayTriangle( float height = p3.dot(yAxis); float p3Width = p3.dot(xAxis); - Quaternionf rotation = new Quaternionf().lookAlong(new Vector3f(zAxis).mul(-1f), yAxis).conjugate(); + Quaternionf rotation = orientation(zAxis, yAxis); float shear = (width > 0.001f) ? p3Width / width : 0.0f; @@ -174,7 +187,7 @@ public static Matrix4f textDisplayLine(Vector3f point1, Vector3f point2, float t Vector3f xAxis = new Vector3f(direction).normalize(); Vector3f yAxis = new Vector3f(zAxis).cross(xAxis).normalize(); - Quaternionf rotation = new Quaternionf().lookAlong(new Vector3f(zAxis).mul(-1f), yAxis).conjugate(); + Quaternionf rotation = orientation(zAxis, yAxis); // Apply roll rotation around the line axis (X axis in local space) Quaternionf rollRotation = new Quaternionf().rotateX(roll); @@ -216,7 +229,7 @@ public static Matrix4f textDisplayParallelogram(Vector3f point1, Vector3f point2 float height = p3.dot(yAxis); float p3Width = p3.dot(xAxis); - Quaternionf rotation = new Quaternionf().lookAlong(new Vector3f(zAxis).mul(-1f), yAxis).conjugate(); + Quaternionf rotation = orientation(zAxis, yAxis); // Calculate shear amount (transforms rectangle into parallelogram) float shear = (width > 0.001f) ? p3Width / width : 0.0f; @@ -337,7 +350,7 @@ public static TRSResult computeParallelogramTRS(Vector3f point1, Vector3f point2 float height = p3vec.dot(yAxis); float p3Width = p3vec.dot(xAxis); - Quaternionf rotation = new Quaternionf().lookAlong(new Vector3f(zAxis).mul(-1f), yAxis).conjugate(); + Quaternionf rotation = orientation(zAxis, yAxis); float shear = (width > 0.001f) ? p3Width / width : 0.0f; // Inner 2x2 = scale(w,h) * shear(s,0) * unitSquare @@ -374,7 +387,7 @@ public static List computeTriangleTRS(Vector3f point1, Vector3f point float height = p3vec.dot(yAxis); float p3Width = p3vec.dot(xAxis); - Quaternionf rotation = new Quaternionf().lookAlong(new Vector3f(zAxis).mul(-1f), yAxis).conjugate(); + Quaternionf rotation = orientation(zAxis, yAxis); float shear = (width > 0.001f) ? p3Width / width : 0.0f; double w = width; diff --git a/api/src/test/java/dev/twme/textdisplayshape/util/TextDisplayUtilTest.java b/api/src/test/java/dev/twme/textdisplayshape/util/TextDisplayUtilTest.java index 7df7c85..022c549 100644 --- a/api/src/test/java/dev/twme/textdisplayshape/util/TextDisplayUtilTest.java +++ b/api/src/test/java/dev/twme/textdisplayshape/util/TextDisplayUtilTest.java @@ -13,6 +13,65 @@ class TextDisplayUtilTest { private static final float EPSILON = 1.0E-4F; + // A single-space Text Display background has width 4 and height 9. After the + // vanilla renderer's 180-degree Y rotation, -0.025 scale, and offset, these + // are the local coordinates of its corners before the shape transformation. + private static final Vector3f BOTTOM_LEFT = new Vector3f(-0.05f, 0f, 0f); + private static final Vector3f BOTTOM_RIGHT = new Vector3f(0.075f, 0f, 0f); + private static final Vector3f TOP_LEFT = new Vector3f(-0.05f, 0.25f, 0f); + + @Test + void lineBackgroundRunsBetweenItsEndpoints() { + List cases = List.of( + new Vector3f[] {new Vector3f(0f, 0f, 0f), new Vector3f(5f, 0f, 0f)}, + new Vector3f[] {new Vector3f(5f, 0f, 0f), new Vector3f(0f, 0f, 0f)}, + new Vector3f[] {new Vector3f(0f, 0f, 0f), new Vector3f(0f, 5f, 0f)}, + new Vector3f[] {new Vector3f(0f, 5f, 0f), new Vector3f(0f, 0f, 0f)}, + new Vector3f[] {new Vector3f(0f, 0f, 0f), new Vector3f(0f, 0f, 5f)}, + new Vector3f[] {new Vector3f(0f, 0f, 5f), new Vector3f(0f, 0f, 0f)}, + new Vector3f[] {new Vector3f(2f, 3f, 4f), new Vector3f(7f, 5f, 1f)}); + + for (Vector3f[] endpoints : cases) { + Matrix4f transform = TextDisplayUtil.textDisplayLine(endpoints[0], endpoints[1], 0.05f); + Vector3f start = new Vector3f(BOTTOM_LEFT).add(0f, 0.125f, 0f); + Vector3f end = new Vector3f(BOTTOM_RIGHT).add(0f, 0.125f, 0f); + assertVectorEquals(endpoints[0], transform.transformPosition(start)); + assertVectorEquals(endpoints[1], transform.transformPosition(end)); + } + } + + @Test + void parallelogramBackgroundCornersMatchTheirVertices() { + for (Vector3f[] vertices : List.of( + points(2f, 3f, 4f, 7f, 3f, 4f, 2f, 3f, 8f), + points(2f, 3f, 4f, 2f, 3f, 8f, 7f, 3f, 4f), + points(2f, 3f, 8f, 2f, 3f, 4f, 2f, 7f, 8f))) { + Matrix4f expected = TextDisplayUtil.textDisplayParallelogram( + vertices[0], vertices[1], vertices[2]); + Matrix4f packetTransform = reconstruct(TextDisplayUtil.computeParallelogramTRS( + vertices[0], vertices[1], vertices[2])); + + for (Matrix4f transform : List.of(expected, packetTransform)) { + assertVectorEquals(vertices[0], transform.transformPosition(new Vector3f(BOTTOM_LEFT))); + assertVectorEquals(vertices[1], transform.transformPosition(new Vector3f(BOTTOM_RIGHT))); + assertVectorEquals(vertices[2], transform.transformPosition(new Vector3f(TOP_LEFT))); + } + } + } + + @Test + void triangleBackgroundPiecesReachTheRequestedVertices() { + Vector3f[] vertices = points(0f, 0f, 0f, 4f, 0f, 0f, 0f, 3f, 0f); + List pieces = TextDisplayUtil.computeTriangleTRS( + vertices[0], vertices[1], vertices[2]); + + assertVectorEquals(vertices[0], reconstruct(pieces.get(0)) + .transformPosition(new Vector3f(BOTTOM_LEFT))); + assertVectorEquals(vertices[1], reconstruct(pieces.get(1)) + .transformPosition(new Vector3f(BOTTOM_RIGHT))); + assertVectorEquals(vertices[2], reconstruct(pieces.get(2)) + .transformPosition(new Vector3f(TOP_LEFT))); + } @Test void parallelogramTrsReconstructsTransformationMatrix() { @@ -107,4 +166,9 @@ private static Vector3f[] points(float... coordinates) { private static void assertMatrixEquals(Matrix4f expected, Matrix4f actual) { assertTrue(expected.equals(actual, EPSILON), () -> "expected:\n" + expected + "\nbut was:\n" + actual); } + + private static void assertVectorEquals(Vector3f expected, Vector3f actual) { + assertTrue(expected.distance(actual) < EPSILON, + () -> "expected " + expected + " but was " + actual); + } } diff --git a/integration/mineflayer/run-e2e.sh b/integration/mineflayer/run-e2e.sh index 0ffecf3..6a01624 100755 --- a/integration/mineflayer/run-e2e.sh +++ b/integration/mineflayer/run-e2e.sh @@ -3,14 +3,14 @@ set -euo pipefail readonly PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" readonly PAPER_API="https://fill.papermc.io/v3/projects/paper/versions/1.21.11/builds/latest" -readonly PACKETEVENTS_URL="https://github.com/retrooper/packetevents/releases/download/v2.13.0/packetevents-spigot-2.13.0.jar" -readonly PACKETEVENTS_SHA256="6d9ece0d87ee727a79a20b7ffbd432021609c6f52bafcb654fc2d3e9b6f064c5" +readonly PACKETEVENTS_URL="https://github.com/retrooper/packetevents/releases/download/v2.14.0/packetevents-spigot-2.14.0.jar" +readonly PACKETEVENTS_SHA256="060087c58ec268eae7dd0eb1f17ac3bea1eaadda1693414141ff64e506091bd7" readonly PORT="${TDS_E2E_PORT:-$(node -e 'const net=require("net");const server=net.createServer();server.listen(0,"127.0.0.1",()=>{console.log(server.address().port);server.close()})')}" readonly TEMP_DIR="$(mktemp -d)" readonly SERVER_DIR="${TEMP_DIR}/server" readonly CACHE_DIR="${PROJECT_DIR}/target/mineflayer-cache" readonly PAPER_JAR="${CACHE_DIR}/paper-1.21.11.jar" -readonly PACKETEVENTS_JAR="${CACHE_DIR}/packetevents-spigot-2.13.0.jar" +readonly PACKETEVENTS_JAR="${CACHE_DIR}/packetevents-spigot-2.14.0.jar" readonly PLUGIN_JAR="${PROJECT_DIR}/integration/paper-plugin/target/TextDisplayShapesIntegration.jar" SERVER_PID="" diff --git a/integration/paper-plugin/pom.xml b/integration/paper-plugin/pom.xml index 12c3f68..506ab12 100644 --- a/integration/paper-plugin/pom.xml +++ b/integration/paper-plugin/pom.xml @@ -9,7 +9,7 @@ 1.0.0 - 21 + 25 UTF-8 @@ -37,13 +37,36 @@ io.papermc.paper paper-api - 1.21.11-R0.1-SNAPSHOT + 26.3.build.41-alpha provided com.github.retrooper packetevents-spigot - 2.13.0 + 2.14.0 + provided + + + + net.kyori + adventure-api + 5.2.0 + provided + + + net.kyori + adventure-text-minimessage + 5.2.0 + provided + + + net.kyori + adventure-text-serializer-legacy + 5.2.0 provided diff --git a/jitpack.yml b/jitpack.yml index 255e0f4..33caed7 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,5 +1,5 @@ jdk: - - openjdk21 + - openjdk25 before_install: - - sdk install java 21-open - - sdk use java 21-open + - sdk install java 25-open + - sdk use java 25-open diff --git a/packet/pom.xml b/packet/pom.xml index 06cc2cb..300e654 100644 --- a/packet/pom.xml +++ b/packet/pom.xml @@ -53,13 +53,37 @@ io.papermc.paper paper-api - 1.21.11-R0.1-SNAPSHOT + 26.3.build.41-alpha provided com.github.retrooper packetevents-spigot - 2.13.0 + 2.14.0 + provided + + + + net.kyori + adventure-api + 5.2.0 + provided + + + net.kyori + adventure-text-minimessage + 5.2.0 + provided + + + net.kyori + adventure-text-serializer-legacy + 5.2.0 provided diff --git a/paper/pom.xml b/paper/pom.xml index 1c7ddb1..2c5c4fc 100644 --- a/paper/pom.xml +++ b/paper/pom.xml @@ -44,7 +44,7 @@ io.papermc.paper paper-api - 1.21.11-R0.1-SNAPSHOT + 26.3.build.41-alpha provided diff --git a/spigot/pom.xml b/spigot/pom.xml index ce2ed88..961d553 100644 --- a/spigot/pom.xml +++ b/spigot/pom.xml @@ -51,7 +51,7 @@ org.spigotmc spigot-api - 1.21.4-R0.1-SNAPSHOT + 26.3-R0.1-SNAPSHOT provided