From 85714fd7f6553e7d1bff4519c19bc31f079871d4 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 17 Feb 2026 02:23:41 -0800 Subject: [PATCH] Fix taxi flight: camera panning, world reload, gryphon display, and animations - Clear introActive/idleOrbit in externalFollow block so mouse panning works during taxi - Skip full world reload on same-map teleports (taxi landing) by tracking loadedMapId - Collect all model IDs for a path when resolving gryphon display ID (fixes displayId=0) - Remove incorrect MountDisplayId fields from Vanilla/Turtle TaxiNodes DBC layouts - Add Vanilla fly animation IDs (234/229/233) to taxi mount animation candidates --- Data/expansions/classic/dbc_layouts.json | 3 +- Data/expansions/turtle/dbc_layouts.json | 3 +- include/core/application.hpp | 1 + include/rendering/renderer.hpp | 1 + src/core/application.cpp | 48 ++++++++++++++++++++---- src/game/game_handler.cpp | 3 +- src/rendering/camera_controller.cpp | 6 +++ src/rendering/renderer.cpp | 39 +++++++++++++++---- 8 files changed, 85 insertions(+), 19 deletions(-) diff --git a/Data/expansions/classic/dbc_layouts.json b/Data/expansions/classic/dbc_layouts.json index 5a38f46c..1abe53c9 100644 --- a/Data/expansions/classic/dbc_layouts.json +++ b/Data/expansions/classic/dbc_layouts.json @@ -41,8 +41,7 @@ "Skin1": 6, "Skin2": 7, "Skin3": 8 }, "TaxiNodes": { - "ID": 0, "MapID": 1, "X": 2, "Y": 3, "Z": 4, "Name": 5, - "MountDisplayIdAlliance": 12, "MountDisplayIdHorde": 13 + "ID": 0, "MapID": 1, "X": 2, "Y": 3, "Z": 4, "Name": 5 }, "TaxiPath": { "ID": 0, "FromNode": 1, "ToNode": 2, "Cost": 3 }, "TaxiPathNode": { diff --git a/Data/expansions/turtle/dbc_layouts.json b/Data/expansions/turtle/dbc_layouts.json index 45517b04..7daffa66 100644 --- a/Data/expansions/turtle/dbc_layouts.json +++ b/Data/expansions/turtle/dbc_layouts.json @@ -41,8 +41,7 @@ "Skin1": 6, "Skin2": 7, "Skin3": 8 }, "TaxiNodes": { - "ID": 0, "MapID": 1, "X": 2, "Y": 3, "Z": 4, "Name": 5, - "MountDisplayIdAlliance": 12, "MountDisplayIdHorde": 13 + "ID": 0, "MapID": 1, "X": 2, "Y": 3, "Z": 4, "Name": 5 }, "TaxiPath": { "ID": 0, "FromNode": 1, "ToNode": 2, "Cost": 3 }, "TaxiPathNode": { diff --git a/include/core/application.hpp b/include/core/application.hpp index 1c669aff..d6651361 100644 --- a/include/core/application.hpp +++ b/include/core/application.hpp @@ -180,6 +180,7 @@ private: uint32_t gryphonDisplayId_ = 0; uint32_t wyvernDisplayId_ = 0; bool lastTaxiFlight_ = false; + uint32_t loadedMapId_ = 0xFFFFFFFF; // Map ID of currently loaded terrain (0xFFFFFFFF = none) float taxiLandingClampTimer_ = 0.0f; float worldEntryMovementGraceTimer_ = 0.0f; float taxiStreamCooldown_ = 0.0f; diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index e5d1f165..d3c8c8a5 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -339,6 +339,7 @@ private: float mountIdleSoundTimer_ = 0.0f; // Timer for ambient idle sounds uint32_t mountActiveFidget_ = 0; // Currently playing fidget animation ID (0 = none) bool taxiFlight_ = false; + bool taxiAnimsLogged_ = false; bool terrainEnabled = true; bool terrainLoaded = false; diff --git a/src/core/application.cpp b/src/core/application.cpp index 29c1495b..afada5f5 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -396,6 +396,7 @@ void Application::setState(AppState newState) { playerCharacterSpawned = false; weaponsSheathed_ = false; wasAutoAttacking_ = false; + loadedMapId_ = 0xFFFFFFFF; spawnedPlayerGuid_ = 0; spawnedAppearanceBytes_ = 0; spawnedFacialFeatures_ = 0; @@ -498,6 +499,7 @@ void Application::logoutToLogin() { playerCharacterSpawned = false; weaponsSheathed_ = false; wasAutoAttacking_ = false; + loadedMapId_ = 0xFFFFFFFF; world.reset(); if (renderer) { // Remove old player model so it doesn't persist into next session @@ -998,10 +1000,30 @@ void Application::setupUICallbacks() { // World entry callback (online mode) - load terrain when entering world gameHandler->setWorldEntryCallback([this](uint32_t mapId, float x, float y, float z) { LOG_INFO("Online world entry: mapId=", mapId, " pos=(", x, ", ", y, ", ", z, ")"); + + // Same-map teleport (taxi landing, GM teleport on same continent): + // just update position, let terrain streamer handle tile loading incrementally. + // A full reload is only needed on first entry or map change. + if (mapId == loadedMapId_ && renderer && renderer->getTerrainManager()) { + LOG_INFO("Same-map teleport (map ", mapId, "), skipping full world reload"); + glm::vec3 canonical = core::coords::serverToCanonical(glm::vec3(x, y, z)); + glm::vec3 renderPos = core::coords::canonicalToRender(canonical); + renderer->getCharacterPosition() = renderPos; + if (renderer->getCameraController()) { + auto* ft = renderer->getCameraController()->getFollowTargetMutable(); + if (ft) *ft = renderPos; + } + worldEntryMovementGraceTimer_ = 2.0f; + taxiLandingClampTimer_ = 0.0f; + lastTaxiFlight_ = false; + return; + } + worldEntryMovementGraceTimer_ = 2.0f; taxiLandingClampTimer_ = 0.0f; lastTaxiFlight_ = false; loadOnlineWorldTerrain(mapId, x, y, z); + loadedMapId_ = mapId; }); auto sampleBestFloorAt = [this](float x, float y, float probeZ) -> std::optional { @@ -2824,18 +2846,23 @@ void Application::buildCreatureDisplayLookups() { }; auto resolveDisplayIdForExactPath = [&](const std::string& exactPath) -> uint32_t { const std::string target = normalizePath(exactPath); - uint32_t modelId = 0; + // Collect ALL model IDs that map to this path (multiple model IDs can + // share the same .m2 file, e.g. modelId 147 and 792 both → Gryphon.m2) + std::vector modelIds; for (const auto& [mid, path] : modelIdToPath_) { if (normalizePath(path) == target) { - modelId = mid; - break; + modelIds.push_back(mid); } } - if (modelId == 0) return 0; + if (modelIds.empty()) return 0; uint32_t bestDisplayId = 0; int bestScore = -1; for (const auto& [dispId, data] : displayDataMap_) { - if (data.modelId != modelId) continue; + bool matches = false; + for (uint32_t mid : modelIds) { + if (data.modelId == mid) { matches = true; break; } + } + if (!matches) continue; int score = 0; if (!data.skin1.empty()) score += 3; if (!data.skin2.empty()) score += 2; @@ -4845,8 +4872,15 @@ void Application::processPendingMount() { bool isTaxi = gameHandler && gameHandler->isOnTaxiFlight(); uint32_t startAnim = 0; // ANIM_STAND if (isTaxi) { - if (charRenderer->hasAnimation(instanceId, 159)) startAnim = 159; // FlyForward - else if (charRenderer->hasAnimation(instanceId, 158)) startAnim = 158; // FlyIdle + // Try WotLK fly anims first, then Vanilla-friendly fallbacks + uint32_t taxiCandidates[] = {159, 158, 234, 229, 233, 141, 369, 6, 5}; // FlyForward, FlyIdle, FlyRun(234), FlyStand(229), FlyWalk(233), FlyMounted, FlyRun, Fly, Run + for (uint32_t anim : taxiCandidates) { + if (charRenderer->hasAnimation(instanceId, anim)) { + startAnim = anim; + break; + } + } + // If none found, startAnim stays 0 (Stand/hover) which is fine for flying creatures } charRenderer->playAnimation(instanceId, startAnim, true); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index b58e3bdf..17aab3aa 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -8688,7 +8688,8 @@ void GameHandler::handleTeleportAck(network::Packet& packet) { LOG_INFO("Sent MSG_MOVE_TELEPORT_ACK response"); } - // Notify application to reload terrain at new position + // Notify application of teleport — the callback decides whether to do + // a full world reload (map change) or just update position (same map). if (worldEntryCallback_) { worldEntryCallback_(currentMapId_, serverX, serverY, serverZ); } diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index e084c67a..e4488c6b 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -127,6 +127,12 @@ void CameraController::update(float deltaTime) { // During taxi flights, skip movement logic but keep camera orbit/zoom controls. if (externalFollow_) { + // Cancel any active intro/idle orbit so mouse panning works during taxi. + // The intro handling code (below) is unreachable during externalFollow_. + introActive = false; + idleOrbit_ = false; + idleTimer_ = 0.0f; + camera->setRotation(yaw, pitch); float zoomLerp = 1.0f - std::exp(-ZOOM_SMOOTH_SPEED * deltaTime); currentDistance += (userTargetDistance - currentDistance) * zoomLerp; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index feb411bb..6d166ce3 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -1023,21 +1023,46 @@ void Renderer::updateCharacterAnimation() { // Taxi flight: use flying animations instead of ground movement if (taxiFlight_) { - // Prefer FlyForward, fall back to FlyIdle, then ANIM_RUN - if (characterRenderer->hasAnimation(mountInstanceId_, ANIM_FLY_FORWARD)) { - mountAnimId = ANIM_FLY_FORWARD; - } else if (characterRenderer->hasAnimation(mountInstanceId_, ANIM_FLY_IDLE)) { - mountAnimId = ANIM_FLY_IDLE; - } else { - mountAnimId = ANIM_RUN; + // Log available animations once when taxi starts + if (!taxiAnimsLogged_) { + taxiAnimsLogged_ = true; + LOG_INFO("Taxi flight active: mountInstanceId_=", mountInstanceId_, + " curMountAnim=", curMountAnim, " haveMountState=", haveMountState); + std::vector seqs; + if (characterRenderer->getAnimationSequences(mountInstanceId_, seqs)) { + std::string animList; + for (const auto& s : seqs) { + if (!animList.empty()) animList += ", "; + animList += std::to_string(s.id); + } + LOG_INFO("Taxi mount available animations: [", animList, "]"); + } + } + + // Try multiple flying animation IDs in priority order: + // 159=FlyForward, 158=FlyIdle (WotLK flying mounts) + // 234=FlyRun, 229=FlyStand (Vanilla creature fly anims) + // 233=FlyWalk, 141=FlyMounted, 369=FlyRun (alternate IDs) + // 6=Fly (classic creature fly) + // Fallback: Run, then Stand (hover) + uint32_t flyAnims[] = {ANIM_FLY_FORWARD, ANIM_FLY_IDLE, 234, 229, 233, 141, 369, 6, ANIM_RUN}; + mountAnimId = ANIM_STAND; // ultimate fallback: hover/idle + for (uint32_t fa : flyAnims) { + if (characterRenderer->hasAnimation(mountInstanceId_, fa)) { + mountAnimId = fa; + break; + } } if (!haveMountState || curMountAnim != mountAnimId) { + LOG_INFO("Taxi mount: playing animation ", mountAnimId); characterRenderer->playAnimation(mountInstanceId_, mountAnimId, true); } // Skip all ground mount logic (jumps, fidgets, etc.) goto taxi_mount_done; + } else { + taxiAnimsLogged_ = false; } // Check for jump trigger - use cached per-mount animation IDs