diff --git a/include/core/coordinates.hpp b/include/core/coordinates.hpp index 9d6cfb3..85a3154 100644 --- a/include/core/coordinates.hpp +++ b/include/core/coordinates.hpp @@ -9,6 +9,8 @@ namespace wowee::core::coords { inline constexpr float TILE_SIZE = 533.33333f; inline constexpr float ZEROPOINT = 32.0f * TILE_SIZE; +inline constexpr float PI = 3.14159265358979323846f; +inline constexpr float TWO_PI = 6.28318530717958647692f; // ---- Canonical WoW world coordinate system (per-map) ---- // +X = North, +Y = West, +Z = Up (height) @@ -42,6 +44,28 @@ inline glm::vec3 canonicalToServer(const glm::vec3& canonical) { return glm::vec3(canonical.y, canonical.x, canonical.z); } +// Normalize angle to [-PI, PI]. +inline float normalizeAngleRad(float a) { + while (a > PI) a -= TWO_PI; + while (a < -PI) a += TWO_PI; + return a; +} + +// Convert server/wire yaw (radians) → canonical yaw (radians). +// +// Under server<->canonical X/Y swap: +// dir_s = (cos(s), sin(s)) +// dir_c = swap(dir_s) = (sin(s), cos(s)) => c = PI/2 - s +inline float serverToCanonicalYaw(float serverYaw) { + return normalizeAngleRad((PI * 0.5f) - serverYaw); +} + +// Convert canonical yaw (radians) → server/wire yaw (radians). +// This mapping is its own inverse. +inline float canonicalToServerYaw(float canonicalYaw) { + return normalizeAngleRad((PI * 0.5f) - canonicalYaw); +} + // Convert between canonical WoW and engine rendering coordinates (just swap X/Y). inline glm::vec3 canonicalToRender(const glm::vec3& wow) { return glm::vec3(wow.y, wow.x, wow.z); diff --git a/src/core/application.cpp b/src/core/application.cpp index aa62964..ef9c494 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -685,8 +685,12 @@ void Application::update(float deltaTime) { // Sync orientation: camera yaw (degrees) → WoW orientation (radians) float yawDeg = renderer->getCharacterYaw(); - float wowOrientation = glm::radians(yawDeg - 90.0f); - gameHandler->setOrientation(wowOrientation); + // Keep all game-side orientation in canonical space. + // We historically sent serverYaw = radians(yawDeg - 90). With the new + // canonical<->server mapping (serverYaw = PI/2 - canonicalYaw), the + // equivalent canonical yaw is radians(180 - yawDeg). + float canonicalYaw = core::coords::normalizeAngleRad(glm::radians(180.0f - yawDeg)); + gameHandler->setOrientation(canonicalYaw); } } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index cf19b22..fd8de7b 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1794,7 +1794,7 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) { movementInfo.x = canonical.x; movementInfo.y = canonical.y; movementInfo.z = canonical.z; - movementInfo.orientation = data.orientation; + movementInfo.orientation = core::coords::serverToCanonicalYaw(data.orientation); movementInfo.flags = 0; movementInfo.flags2 = 0; movementInfo.time = 0; @@ -2322,23 +2322,20 @@ void GameHandler::sendMovement(Opcode opcode) { movementInfo.transportTime2 = movementInfo.time; // ONTRANSPORT expects local orientation (player yaw relative to transport yaw). - float transportYaw = 0.0f; + // Keep internal yaw canonical; convert to server yaw on the wire. + float transportYawCanonical = 0.0f; if (transportManager_) { if (auto* tr = transportManager_->getTransport(playerTransportGuid_); tr) { if (tr->hasServerYaw) { - transportYaw = tr->serverYaw; + transportYawCanonical = tr->serverYaw; } else { - transportYaw = glm::eulerAngles(tr->rotation).z; + transportYawCanonical = glm::eulerAngles(tr->rotation).z; } } } - float localTransportO = movementInfo.orientation - transportYaw; - constexpr float kPi = 3.14159265359f; - constexpr float kTwoPi = 6.28318530718f; - while (localTransportO > kPi) localTransportO -= kTwoPi; - while (localTransportO < -kPi) localTransportO += kTwoPi; - movementInfo.transportO = localTransportO; + movementInfo.transportO = + core::coords::normalizeAngleRad(movementInfo.orientation - transportYawCanonical); } else { // Clear transport flag if not on transport movementInfo.flags &= ~static_cast(MovementFlags::ONTRANSPORT); @@ -2357,6 +2354,9 @@ void GameHandler::sendMovement(Opcode opcode) { wireInfo.y = serverPos.y; wireInfo.z = serverPos.z; + // Convert canonical → server yaw for the wire + wireInfo.orientation = core::coords::canonicalToServerYaw(wireInfo.orientation); + // Also convert transport local position to server coordinates if on transport if (isOnTransport()) { glm::vec3 serverTransportPos = core::coords::canonicalToServer( @@ -2364,6 +2364,8 @@ void GameHandler::sendMovement(Opcode opcode) { wireInfo.transportX = serverTransportPos.x; wireInfo.transportY = serverTransportPos.y; wireInfo.transportZ = serverTransportPos.z; + // transportO is a local delta; server<->canonical swap negates delta yaw. + wireInfo.transportO = core::coords::normalizeAngleRad(-wireInfo.transportO); } // Build and send movement packet @@ -2520,7 +2522,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { // Set position from movement block (server → canonical) if (block.hasMovement) { glm::vec3 pos = core::coords::serverToCanonical(glm::vec3(block.x, block.y, block.z)); - entity->setPosition(pos.x, pos.y, pos.z, block.orientation); + float oCanonical = core::coords::serverToCanonicalYaw(block.orientation); + entity->setPosition(pos.x, pos.y, pos.z, oCanonical); LOG_DEBUG(" Position: (", pos.x, ", ", pos.y, ", ", pos.z, ")"); if (block.guid == playerGuid && block.runSpeed > 0.1f && block.runSpeed < 100.0f) { serverRunSpeed_ = block.runSpeed; @@ -2534,7 +2537,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { playerTransportOffset_ = core::coords::serverToCanonical(serverOffset); if (transportManager_ && transportManager_->getTransport(playerTransportGuid_)) { glm::vec3 composed = transportManager_->getPlayerWorldPosition(playerTransportGuid_, playerTransportOffset_); - entity->setPosition(composed.x, composed.y, composed.z, block.orientation); + entity->setPosition(composed.x, composed.y, composed.z, oCanonical); movementInfo.x = composed.x; movementInfo.y = composed.y; movementInfo.z = composed.z; @@ -2556,8 +2559,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { glm::vec3 localOffset = core::coords::serverToCanonical( glm::vec3(block.transportX, block.transportY, block.transportZ)); const bool hasLocalOrientation = (block.updateFlags & 0x0020) != 0; // UPDATEFLAG_LIVING + float localOriCanonical = core::coords::normalizeAngleRad(-block.transportO); setTransportAttachment(block.guid, block.objectType, block.transportGuid, - localOffset, hasLocalOrientation, block.transportO); + localOffset, hasLocalOrientation, localOriCanonical); if (transportManager_ && transportManager_->getTransport(block.transportGuid)) { glm::vec3 composed = transportManager_->getPlayerWorldPosition(block.transportGuid, localOffset); entity->setPosition(composed.x, composed.y, composed.z, entity->getOrientation()); @@ -2870,7 +2874,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { if (entity) { if (block.hasMovement) { glm::vec3 pos = core::coords::serverToCanonical(glm::vec3(block.x, block.y, block.z)); - entity->setPosition(pos.x, pos.y, pos.z, block.orientation); + float oCanonical = core::coords::serverToCanonicalYaw(block.orientation); + entity->setPosition(pos.x, pos.y, pos.z, oCanonical); if (block.guid != playerGuid && (entity->getType() == ObjectType::UNIT || entity->getType() == ObjectType::GAMEOBJECT)) { @@ -2878,8 +2883,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { glm::vec3 localOffset = core::coords::serverToCanonical( glm::vec3(block.transportX, block.transportY, block.transportZ)); const bool hasLocalOrientation = (block.updateFlags & 0x0020) != 0; // UPDATEFLAG_LIVING + float localOriCanonical = core::coords::normalizeAngleRad(-block.transportO); setTransportAttachment(block.guid, entity->getType(), block.transportGuid, - localOffset, hasLocalOrientation, block.transportO); + localOffset, hasLocalOrientation, localOriCanonical); if (transportManager_ && transportManager_->getTransport(block.transportGuid)) { glm::vec3 composed = transportManager_->getPlayerWorldPosition(block.transportGuid, localOffset); entity->setPosition(composed.x, composed.y, composed.z, entity->getOrientation()); @@ -3108,7 +3114,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { auto entity = entityManager.getEntity(block.guid); if (entity) { glm::vec3 pos = core::coords::serverToCanonical(glm::vec3(block.x, block.y, block.z)); - entity->setPosition(pos.x, pos.y, pos.z, block.orientation); + float oCanonical = core::coords::serverToCanonicalYaw(block.orientation); + entity->setPosition(pos.x, pos.y, pos.z, oCanonical); LOG_DEBUG("Updated entity position: 0x", std::hex, block.guid, std::dec); if (block.guid != playerGuid && @@ -3117,8 +3124,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { glm::vec3 localOffset = core::coords::serverToCanonical( glm::vec3(block.transportX, block.transportY, block.transportZ)); const bool hasLocalOrientation = (block.updateFlags & 0x0020) != 0; // UPDATEFLAG_LIVING + float localOriCanonical = core::coords::normalizeAngleRad(-block.transportO); setTransportAttachment(block.guid, entity->getType(), block.transportGuid, - localOffset, hasLocalOrientation, block.transportO); + localOffset, hasLocalOrientation, localOriCanonical); if (transportManager_ && transportManager_->getTransport(block.transportGuid)) { glm::vec3 composed = transportManager_->getPlayerWorldPosition(block.transportGuid, localOffset); entity->setPosition(composed.x, composed.y, composed.z, entity->getOrientation()); @@ -3129,7 +3137,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { } if (block.guid == playerGuid) { - movementInfo.orientation = block.orientation; + movementInfo.orientation = oCanonical; // Track player-on-transport state from MOVEMENT updates if (block.onTransport) { @@ -3139,7 +3147,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { playerTransportOffset_ = core::coords::serverToCanonical(serverOffset); if (transportManager_ && transportManager_->getTransport(playerTransportGuid_)) { glm::vec3 composed = transportManager_->getPlayerWorldPosition(playerTransportGuid_, playerTransportOffset_); - entity->setPosition(composed.x, composed.y, composed.z, block.orientation); + entity->setPosition(composed.x, composed.y, composed.z, oCanonical); movementInfo.x = composed.x; movementInfo.y = composed.y; movementInfo.z = composed.z; @@ -3163,7 +3171,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { // Fire transport move callback if this is a known transport if (transportGuids_.count(block.guid) && transportMoveCallback_) { serverUpdatedTransportGuids_.insert(block.guid); - transportMoveCallback_(block.guid, pos.x, pos.y, pos.z, block.orientation); + transportMoveCallback_(block.guid, pos.x, pos.y, pos.z, oCanonical); } // Fire move callback for non-transport gameobjects. if (entity->getType() == ObjectType::GAMEOBJECT && @@ -5081,7 +5089,7 @@ void GameHandler::handleMonsterMove(network::Packet& packet) { float orientation = entity->getOrientation(); if (data.moveType == 4) { // FacingAngle - server specifies exact angle - orientation = data.facingAngle; + orientation = core::coords::serverToCanonicalYaw(data.facingAngle); } else if (data.moveType == 3) { // FacingTarget - face toward the target entity auto target = entityManager.getEntity(data.facingTarget); @@ -5134,7 +5142,7 @@ void GameHandler::handleMonsterMoveTransport(network::Packet& packet) { uint8_t unk = packet.readUInt8(); // Unknown byte (usually 0) uint64_t transportGuid = packet.readUInt64(); - // Transport-local coordinates + // Transport-local coordinates (server space) float localX = packet.readFloat(); float localY = packet.readFloat(); float localZ = packet.readFloat(); @@ -5152,9 +5160,9 @@ void GameHandler::handleMonsterMoveTransport(network::Packet& packet) { if (transportManager_) { // Use TransportManager to compose world position from local offset - glm::vec3 localPos(localX, localY, localZ); - setTransportAttachment(moverGuid, entity->getType(), transportGuid, localPos, false, 0.0f); - glm::vec3 worldPos = transportManager_->getPlayerWorldPosition(transportGuid, localPos); + glm::vec3 localPosCanonical = core::coords::serverToCanonical(glm::vec3(localX, localY, localZ)); + setTransportAttachment(moverGuid, entity->getType(), transportGuid, localPosCanonical, false, 0.0f); + glm::vec3 worldPos = transportManager_->getPlayerWorldPosition(transportGuid, localPosCanonical); entity->setPosition(worldPos.x, worldPos.y, worldPos.z, entity->getOrientation()); @@ -6601,7 +6609,7 @@ void GameHandler::handleTeleportAck(network::Packet& packet) { movementInfo.x = canonical.x; movementInfo.y = canonical.y; movementInfo.z = canonical.z; - movementInfo.orientation = orientation; + movementInfo.orientation = core::coords::serverToCanonicalYaw(orientation); movementInfo.flags = 0; // Send the ack back to the server @@ -6661,7 +6669,7 @@ void GameHandler::handleNewWorld(network::Packet& packet) { movementInfo.x = canonical.x; movementInfo.y = canonical.y; movementInfo.z = canonical.z; - movementInfo.orientation = orientation; + movementInfo.orientation = core::coords::serverToCanonicalYaw(orientation); movementInfo.flags = 0; movementInfo.flags2 = 0; resurrectPending_ = false;