From 76edd3260fc0b8b0f769482d17f99150b8111a44 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Feb 2026 15:38:39 -0800 Subject: [PATCH] Infer and animate elevator transport paths --- include/game/transport_manager.hpp | 6 ++++++ src/core/application.cpp | 10 ++++++++-- src/game/game_handler.cpp | 1 + src/game/transport_manager.cpp | 31 ++++++++++++++++++++++++------ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/include/game/transport_manager.hpp b/include/game/transport_manager.hpp index 53f9e5c9..f4ab5ef8 100644 --- a/include/game/transport_manager.hpp +++ b/include/game/transport_manager.hpp @@ -102,6 +102,12 @@ public: // Returns 0 when no suitable path match is found. uint32_t inferMovingPathForSpawn(const glm::vec3& spawnWorldPos, float maxDistance = 1200.0f) const; + // Infer a DBC path by spawn position, optionally including z-only elevator paths. + // Returns 0 when no suitable path match is found. + uint32_t inferDbcPathForSpawn(const glm::vec3& spawnWorldPos, + float maxDistance, + bool allowZOnly) const; + // Choose a deterministic fallback moving DBC path for known server transport entries/displayIds. // Returns 0 when no suitable moving path is available. uint32_t pickFallbackMovingPath(uint32_t entry, uint32_t displayId) const; diff --git a/src/core/application.cpp b/src/core/application.cpp index 5a271dcd..f981b554 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1250,7 +1250,11 @@ void Application::setupUICallbacks() { LOG_INFO("Server-first transport registration: using entry DBC path for entry ", entry); } } else if (!hasUsablePath) { - uint32_t inferredPath = transportManager->inferMovingPathForSpawn(canonicalSpawnPos); + // Remap/infer path by spawn position when entry doesn't map 1:1 to DBC ids. + // For elevators (TB lift platforms), we must allow z-only paths here. + bool allowZOnly = (displayId == 455 || displayId == 462); + uint32_t inferredPath = transportManager->inferDbcPathForSpawn( + canonicalSpawnPos, 1200.0f, allowZOnly); if (inferredPath != 0) { pathId = inferredPath; LOG_INFO("Using inferred transport path ", pathId, " for entry ", entry); @@ -1359,7 +1363,9 @@ void Application::setupUICallbacks() { " displayId=", displayId, " wmoInstance=", wmoInstanceId); } } else if (!hasUsablePath) { - uint32_t inferredPath = transportManager->inferMovingPathForSpawn(canonicalSpawnPos); + bool allowZOnly = (displayId == 455 || displayId == 462); + uint32_t inferredPath = transportManager->inferDbcPathForSpawn( + canonicalSpawnPos, 1200.0f, allowZOnly); if (inferredPath != 0) { pathId = inferredPath; LOG_INFO("Auto-spawned transport with inferred path: entry=", entry, diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index fd8de7b3..7eee2924 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -2701,6 +2701,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { if (block.updateFlags & 0x0002) { transportGuids_.insert(block.guid); LOG_INFO("Detected transport GameObject: 0x", std::hex, block.guid, std::dec, + " entry=", go->getEntry(), " displayId=", go->getDisplayId(), " pos=(", go->getX(), ", ", go->getY(), ", ", go->getZ(), ")"); // Note: TransportSpawnCallback will be invoked from Application after WMO instance is created diff --git a/src/game/transport_manager.cpp b/src/game/transport_manager.cpp index d3096acc..9c3907f9 100644 --- a/src/game/transport_manager.cpp +++ b/src/game/transport_manager.cpp @@ -891,15 +891,23 @@ bool TransportManager::loadTransportAnimationDBC(pipeline::AssetManager* assetMg float maxX = timedPoints[0].pos.x; float minY = timedPoints[0].pos.y; float maxY = timedPoints[0].pos.y; + float minZ = timedPoints[0].pos.z; + float maxZ = timedPoints[0].pos.z; for (const auto& pt : timedPoints) { minX = std::min(minX, pt.pos.x); maxX = std::max(maxX, pt.pos.x); minY = std::min(minY, pt.pos.y); maxY = std::max(maxY, pt.pos.y); + minZ = std::min(minZ, pt.pos.z); + maxZ = std::max(maxZ, pt.pos.z); } float rangeX = maxX - minX; float rangeY = maxY - minY; - bool isZOnly = (rangeX < 0.01f && rangeY < 0.01f); + float rangeZ = maxZ - minZ; + float rangeXY = std::max(rangeX, rangeY); + // Some elevator paths have tiny XY jitter. Treat them as z-only when horizontal travel + // is negligible compared to vertical motion. + bool isZOnly = (rangeXY < 0.01f) || (rangeXY < 1.0f && rangeZ > 2.0f); // Store path TransportPath path; @@ -921,7 +929,8 @@ bool TransportManager::loadTransportAnimationDBC(pipeline::AssetManager* assetMg glm::vec3 lastOffset = timedPoints[timedPoints.size() - 2].pos; // -2 to skip wrap duplicate LOG_INFO(" Transport ", transportEntry, ": ", timedPoints.size() - 1, " waypoints + wrap, ", durationMs, "ms duration (wrap=", wrapMs, "ms, t0_normalized=", timedPoints[0].tMs, "ms)", - " rangeXY=(", rangeX, ",", rangeY, ") ", (isZOnly ? "[Z-ONLY]" : "[XY-PATH]"), + " rangeXY=(", rangeX, ",", rangeY, ") rangeZ=", rangeZ, " ", + (isZOnly ? "[Z-ONLY]" : "[XY-PATH]"), " firstOffset=(", firstOffset.x, ", ", firstOffset.y, ", ", firstOffset.z, ")", " midOffset=(", midOffset.x, ", ", midOffset.y, ", ", midOffset.z, ")", " lastOffset=(", lastOffset.x, ", ", lastOffset.y, ", ", lastOffset.z, ")"); @@ -960,12 +969,17 @@ bool TransportManager::hasUsableMovingPathForEntry(uint32_t entry, float minXYRa return rangeXY >= minXYRange; } -uint32_t TransportManager::inferMovingPathForSpawn(const glm::vec3& spawnWorldPos, float maxDistance) const { +uint32_t TransportManager::inferDbcPathForSpawn(const glm::vec3& spawnWorldPos, + float maxDistance, + bool allowZOnly) const { float bestD2 = maxDistance * maxDistance; uint32_t bestPathId = 0; for (const auto& [pathId, path] : paths_) { - if (!path.fromDBC || path.durationMs == 0 || path.zOnly || path.points.empty()) { + if (!path.fromDBC || path.durationMs == 0 || path.points.empty()) { + continue; + } + if (!allowZOnly && path.zOnly) { continue; } @@ -981,14 +995,19 @@ uint32_t TransportManager::inferMovingPathForSpawn(const glm::vec3& spawnWorldPo } if (bestPathId != 0) { - LOG_INFO("TransportManager: Inferred moving DBC path ", bestPathId, - " for spawn at (", spawnWorldPos.x, ", ", spawnWorldPos.y, ", ", spawnWorldPos.z, + LOG_INFO("TransportManager: Inferred DBC path ", bestPathId, + " (allowZOnly=", allowZOnly ? "yes" : "no", + ") for spawn at (", spawnWorldPos.x, ", ", spawnWorldPos.y, ", ", spawnWorldPos.z, "), dist=", std::sqrt(bestD2)); } return bestPathId; } +uint32_t TransportManager::inferMovingPathForSpawn(const glm::vec3& spawnWorldPos, float maxDistance) const { + return inferDbcPathForSpawn(spawnWorldPos, maxDistance, /*allowZOnly=*/false); +} + uint32_t TransportManager::pickFallbackMovingPath(uint32_t entry, uint32_t displayId) const { auto isUsableMovingPath = [this](uint32_t pathId) -> bool { auto it = paths_.find(pathId);