From bbf978872b75d55ea743e011dae55b671c1ae815 Mon Sep 17 00:00:00 2001 From: MagicBot Date: Tue, 27 Oct 2026 06:43:28 -0400 Subject: [PATCH] Fix /rotate for NPCs, mobs and buildings past 180 degrees /rotate took degrees but stored them on NPCs and mobs where the code expects cos of half the yaw, so the client got NaN and the DB got degrees; /rotate face passed radians the same way. Buildings used |cos| for w, which mirrored any angle above 180 (270 became 90). Every kind now takes a yaw in radians (degrees from the command, the facing angle for "face") through Quaternion.fromYaw. NPC and mob yaws are wrapped to [0, 2 pi) so the stored cos reloads to the same angle. Value changes: /rotate N on NPCs/mobs now faces N degrees (was NaN); buildings past 180 degrees now face the given angle; npc_rotation and mob_rotation saved by /rotate hold cos of half the yaw instead of the raw input. Co-Authored-By: Claude Opus 5.5 --- src/engine/devcmd/cmds/RotateCmd.java | 46 +++++++++++++-------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/engine/devcmd/cmds/RotateCmd.java b/src/engine/devcmd/cmds/RotateCmd.java index fc8b22c6..d9e16f57 100644 --- a/src/engine/devcmd/cmds/RotateCmd.java +++ b/src/engine/devcmd/cmds/RotateCmd.java @@ -14,6 +14,7 @@ import engine.devcmd.AbstractDevCmd; import engine.gameManager.BuildingManager; import engine.gameManager.ChatManager; import engine.gameManager.DbManager; +import engine.math.FastMath; import engine.math.Quaternion; import engine.math.Vector3fImmutable; import engine.objects.*; @@ -64,11 +65,11 @@ public class RotateCmd extends AbstractDevCmd { } if (target instanceof Building) - rotateBuilding(pc, (Building) target, rot); + rotateBuilding(pc, (Building) target, (float) Math.toRadians(rot)); else if (target instanceof NPC) - rotateNPC(pc, (NPC) target, rot); + rotateNPC(pc, (NPC) target, (float) Math.toRadians(rot)); else if (target instanceof Mob) - rotateMob(pc, (Mob) target, rot); + rotateMob(pc, (Mob) target, (float) Math.toRadians(rot)); else throwbackError(pc, "Target " + target.getObjectType() + " is not a valid object type"); @@ -145,7 +146,7 @@ public class RotateCmd extends AbstractDevCmd { else b = getTargetAsBuilding(pc); if (b != null) { - rotateBuilding(pc, b, rot); + rotateBuilding(pc, b, (float) Math.toRadians(rot)); return; } @@ -156,7 +157,7 @@ public class RotateCmd extends AbstractDevCmd { else npc = getTargetAsNPC(pc); if (npc != null) { - rotateNPC(pc, npc, rot); + rotateNPC(pc, npc, (float) Math.toRadians(rot)); return; } @@ -167,7 +168,7 @@ public class RotateCmd extends AbstractDevCmd { else mob = getTargetAsMob(pc); if (mob != null) { - rotateMob(pc, mob, rot); + rotateMob(pc, mob, (float) Math.toRadians(rot)); return; } throwbackError(pc, "Nothing found to rotate."); @@ -181,12 +182,11 @@ public class RotateCmd extends AbstractDevCmd { @Override protected String _getUsageString() { - return "' /rotate [objectID] rotation' || ' /rot [objectID] rotation'"; + return "' /rotate [objectID] degrees' || ' /rot [objectID] degrees' || ' /rotate face'"; } - private void rotateBuilding(PlayerCharacter pc, Building building, float orig) { - building.setRotation(new Quaternion(0f, (float) Math.sin(Math.toRadians(orig) / 2), 0f, - (float) Math.abs(Math.cos(Math.toRadians(orig) / 2)))); + private void rotateBuilding(PlayerCharacter pc, Building building, float yaw) { + building.setRotation(Quaternion.fromYaw(yaw)); building.getBounds().setBounds(building); WorldGrid.updateObject(building, pc); DbManager.BuildingQueries.SET_PROPERTY(building, "rotY", building.getRotation().y); @@ -195,24 +195,27 @@ public class RotateCmd extends AbstractDevCmd { "Building with ID " + building.getObjectUUID() + " rotated"); } - private void rotateNPC(PlayerCharacter pc, NPC npc, float rot) { - npc.setRotation(Quaternion.fromYaw((float) Math.acos(rot) * 2)); - DbManager.NPCQueries.SET_PROPERTY(npc, "npc_rotation", rot); + private void rotateNPC(PlayerCharacter pc, NPC npc, float yaw) { + npc.setRotation(characterRotation(yaw)); + DbManager.NPCQueries.SET_PROPERTY(npc, "npc_rotation", npc.getRotation().w); WorldGrid.updateObject(npc, pc); - //no rotation for npc's in db currently ChatManager.chatSayInfo(pc, "NPC with ID " + npc.getObjectUUID() + " rotated"); } - private void rotateMob(PlayerCharacter pc, Mob mob, float rot) { - mob.setRotation(Quaternion.fromYaw((float) Math.acos(rot) * 2)); - DbManager.MobQueries.SET_PROPERTY(mob, "mob_rotation", rot); + private void rotateMob(PlayerCharacter pc, Mob mob, float yaw) { + mob.setRotation(characterRotation(yaw)); + DbManager.MobQueries.SET_PROPERTY(mob, "mob_rotation", mob.getRotation().w); WorldGrid.updateObject(mob, pc); - //no rotation for mobs's in db currently ChatManager.chatSayInfo(pc, "Mob with ID " + mob.getObjectUUID() + " rotated"); } + // npc_rotation and mob_rotation store w = cos θ/2, which reloads to the same yaw only for θ in [0, 2π). + private static Quaternion characterRotation(float yaw) { + return Quaternion.fromYaw((float) (yaw - FastMath.TWO_PI * Math.floor(yaw / FastMath.TWO_PI))); + } + private void rotateFace(PlayerCharacter pc, AbstractGameObject target) { AbstractWorldObject awo = (AbstractWorldObject) target; if (awo == null) @@ -224,13 +227,8 @@ public class RotateCmd extends AbstractDevCmd { float rotangle = faceDirection.getRotation(); - float rot = (float) Math.toDegrees(rotangle); - - if (rot > 180) - rot *= -1; - if (target instanceof Building) - rotateBuilding(pc, (Building) target, rot); + rotateBuilding(pc, (Building) target, rotangle); else if (target instanceof NPC) rotateNPC(pc, (NPC) target, rotangle); else if (target instanceof Mob)