From a4eecb36d6ebc67cd2c5d5c465112fddaea70e9d Mon Sep 17 00:00:00 2001 From: XxFran10xX <318299142+XxFran10xX@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:58:30 +0200 Subject: [PATCH] fix: keep restored vehicle heading when steering Turning steps from the rotator's cached driveYaw. Restoring a saved rotation (rawSet) and reset() wrote the bone quaternion without updating that cache, so it stayed at the model's rest yaw. The first A/D press after a vehicle was loaded from the database snapped it back to its spawn heading, which players saw as ships flipping 180 degrees when they started sailing. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../vehicleframework/bones/BoneRotator.java | 7 ++ .../bones/BoneRotatorDriveYawTest.java | 72 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/test/java/net/tfminecraft/vehicleframework/bones/BoneRotatorDriveYawTest.java diff --git a/src/main/java/net/tfminecraft/vehicleframework/bones/BoneRotator.java b/src/main/java/net/tfminecraft/vehicleframework/bones/BoneRotator.java index e481c5f..4366155 100644 --- a/src/main/java/net/tfminecraft/vehicleframework/bones/BoneRotator.java +++ b/src/main/java/net/tfminecraft/vehicleframework/bones/BoneRotator.java @@ -70,8 +70,14 @@ public SimpleManualAnimator getAnimator() { public void rawSet(float x, float y, float z, float w) { animator.getRotation().set(x, y, z, w); + syncDriveYaw(); //animator.animate(bone); } + + // Turning steps from driveYaw, so a stale value snaps the bone back to it. + private void syncDriveYaw() { + driveYaw = new ConvertedAngle(animator.getRotation()).getYaw(); + } public void rotateEntity(float yaw, float pitch) { Location loc = e.getLocation().clone(); @@ -110,6 +116,7 @@ public void rotate(double targetX, double targetY, double targetZ) { public void reset() { Quaternionf q = new Quaternionf(); animator.getRotation().set(q.x, q.y, q.z, q.w); + syncDriveYaw(); //animator.animate(bone); } diff --git a/src/test/java/net/tfminecraft/vehicleframework/bones/BoneRotatorDriveYawTest.java b/src/test/java/net/tfminecraft/vehicleframework/bones/BoneRotatorDriveYawTest.java new file mode 100644 index 0000000..189897d --- /dev/null +++ b/src/test/java/net/tfminecraft/vehicleframework/bones/BoneRotatorDriveYawTest.java @@ -0,0 +1,72 @@ +package net.tfminecraft.vehicleframework.bones; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.joml.Quaternionf; +import org.junit.jupiter.api.Test; + +import com.ticxo.modelengine.api.model.bone.ModelBone; + +import net.tfminecraft.vehicleframework.enums.SeatType; +import net.tfminecraft.vehicleframework.vehicles.ActiveVehicle; +import net.tfminecraft.vehicleframework.vehicles.controller.RotateController; +import net.tfminecraft.vehicleframework.vehicles.seat.Seat; +import net.tfminecraft.vehicleframework.vehicles.util.AccessPanel; + +class BoneRotatorDriveYawTest { + + @Test + void restoredHeadingIsWhereTurningStarts() { + Fixture f = new Fixture(); + Quaternionf saved = yaw(170f); + f.rotator.rawSet(saved.x, saved.y, saved.z, saved.w); + + new RotateController().turnLeft(f.rotator, f.vehicle, f.captain); + + assertEquals(171.2f, boneYaw(f.rotator), 0.01f); + assertEquals(171.2f, f.rotator.getDriveYaw(), 0.01f); + } + + @Test + void resetHeadingIsWhereTurningStarts() { + Fixture f = new Fixture(); + f.rotator.setRotation(120, 0, 0, true, false, false); + f.rotator.reset(); + + new RotateController().turnRight(f.rotator, f.vehicle, f.captain); + + assertEquals(-1.2f, boneYaw(f.rotator), 0.01f); + } + + private static Quaternionf yaw(float degrees) { + return new Quaternionf().rotateYXZ((float) Math.toRadians(degrees), 0, 0); + } + + private static float boneYaw(BoneRotator rotator) { + return new ConvertedAngle(rotator.getAnimator().getRotation()).getYaw(); + } + + private static final class Fixture { + final ActiveVehicle vehicle = mock(ActiveVehicle.class); + final Player captain = mock(Player.class); + final BoneRotator rotator; + + Fixture() { + AccessPanel panel = new AccessPanel(); + panel.setSpeed(0.13); + panel.setTurnRate(0.24); + when(vehicle.getAccessPanel()).thenReturn(panel); + Seat seat = mock(Seat.class); + when(seat.getType()).thenReturn(SeatType.CAPTAIN); + when(vehicle.getSeat(captain)).thenReturn(seat); + ModelBone bone = mock(ModelBone.class, RETURNS_DEEP_STUBS); + when(bone.getBoneId()).thenReturn("body_controller"); + rotator = new BoneRotator(vehicle, mock(Entity.class), bone, new RotationLimits()); + } + } +}