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
This commit is contained in:
Kelsi 2026-02-17 02:23:41 -08:00
parent 4a08271b75
commit 85714fd7f6
8 changed files with 85 additions and 19 deletions

View file

@ -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": {

View file

@ -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": {

View file

@ -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;

View file

@ -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;

View file

@ -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<float> {
@ -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<uint32_t> 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);

View file

@ -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);
}

View file

@ -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;

View file

@ -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<pipeline::M2Sequence> 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