diff --git a/include/core/application.hpp b/include/core/application.hpp index 66f22a79..143ba8a3 100644 --- a/include/core/application.hpp +++ b/include/core/application.hpp @@ -176,6 +176,13 @@ private: std::unordered_map gameObjectDisplayIdModelCache_; // displayId → M2 modelId std::unordered_map gameObjectDisplayIdWmoCache_; // displayId → WMO modelId std::unordered_map gameObjectInstances_; // guid → instance info + struct PendingTransportMove { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float orientation = 0.0f; + }; + std::unordered_map pendingTransportMoves_; // guid -> latest pre-registration move uint32_t nextGameObjectModelId_ = 20000; uint32_t nextGameObjectWmoModelId_ = 40000; bool gameObjectLookupsBuilt_ = false; @@ -184,6 +191,7 @@ private: uint32_t mountInstanceId_ = 0; uint32_t mountModelId_ = 0; uint32_t pendingMountDisplayId_ = 0; // Deferred mount load (0 = none pending) + bool weaponsSheathed_ = false; void processPendingMount(); bool creatureLookupsBuilt_ = false; diff --git a/include/game/transport_manager.hpp b/include/game/transport_manager.hpp index a65b7474..42842e13 100644 --- a/include/game/transport_manager.hpp +++ b/include/game/transport_manager.hpp @@ -66,6 +66,7 @@ struct ActiveTransport { glm::vec3 serverLinearVelocity; float serverAngularVelocity; bool hasServerVelocity; + bool allowBootstrapVelocity; // Disable DBC bootstrap when spawn/path mismatch is clearly invalid }; class TransportManager { diff --git a/src/core/application.cpp b/src/core/application.cpp index 6fd912fd..36043c15 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -351,6 +351,7 @@ void Application::logoutToLogin() { } npcsSpawned = false; playerCharacterSpawned = false; + weaponsSheathed_ = false; world.reset(); if (renderer) { // Remove old player model so it doesn't persist into next session @@ -408,6 +409,16 @@ void Application::update(float deltaTime) { auto gh2 = std::chrono::high_resolution_clock::now(); ghTime += std::chrono::duration(gh2 - gh1).count(); + // Toggle weapon sheathe state with Z (ignored while UI captures keyboard). + { + const bool uiWantsKeyboard = ImGui::GetIO().WantCaptureKeyboard; + auto& input = Input::getInstance(); + if (!uiWantsKeyboard && input.isKeyJustPressed(SDL_SCANCODE_Z)) { + weaponsSheathed_ = !weaponsSheathed_; + loadEquippedWeapons(); + } + } + auto w1 = std::chrono::high_resolution_clock::now(); if (world) { world->update(deltaTime); @@ -1235,6 +1246,16 @@ void Application::setupUICallbacks() { // Server-authoritative movement - set initial position from spawn data glm::vec3 canonicalPos(x, y, z); transportManager->updateServerTransport(guid, canonicalPos, orientation); + + // If a move packet arrived before registration completed, replay latest now. + auto pendingIt = pendingTransportMoves_.find(guid); + if (pendingIt != pendingTransportMoves_.end()) { + const PendingTransportMove pending = pendingIt->second; + transportManager->updateServerTransport(guid, glm::vec3(pending.x, pending.y, pending.z), pending.orientation); + LOG_INFO("Replayed queued transport move for GUID=0x", std::hex, guid, std::dec, + " pos=(", pending.x, ", ", pending.y, ", ", pending.z, ") orientation=", pending.orientation); + pendingTransportMoves_.erase(pendingIt); + } LOG_INFO("Transport registered - server-authoritative movement"); }); @@ -1329,13 +1350,15 @@ void Application::setupUICallbacks() { transportManager->registerTransport(guid, wmoInstanceId, pathId, canonicalSpawnPos); } else { + pendingTransportMoves_[guid] = PendingTransportMove{x, y, z, orientation}; LOG_WARNING("Cannot auto-spawn transport 0x", std::hex, guid, std::dec, - " - WMO instance not found"); + " - WMO instance not found (queued move for replay)"); return; } } else { + pendingTransportMoves_[guid] = PendingTransportMove{x, y, z, orientation}; LOG_WARNING("Cannot auto-spawn transport 0x", std::hex, guid, std::dec, - " - entity not found in EntityManager"); + " - entity not found in EntityManager (queued move for replay)"); return; } } @@ -1879,6 +1902,13 @@ void Application::loadEquippedWeapons() { { game::EquipSlot::OFF_HAND, 2 }, }; + if (weaponsSheathed_) { + for (const auto& ws : weaponSlots) { + charRenderer->detachWeapon(charInstanceId, ws.attachmentId); + } + return; + } + for (const auto& ws : weaponSlots) { const auto& equipSlot = inventory.getEquipSlot(ws.slot); diff --git a/src/game/transport_manager.cpp b/src/game/transport_manager.cpp index 118d691f..9f149760 100644 --- a/src/game/transport_manager.cpp +++ b/src/game/transport_manager.cpp @@ -43,6 +43,7 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId, transport.guid = guid; transport.wmoInstanceId = wmoInstanceId; transport.pathId = pathId; + transport.allowBootstrapVelocity = true; // CRITICAL: Set basePosition from spawn position and t=0 offset // For stationary paths (1 waypoint), just use spawn position directly @@ -59,12 +60,20 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId, // Sanity check: firstWaypoint should match spawnWorldPos glm::vec3 firstWaypoint = path.points[0].pos; glm::vec3 waypointDiff = spawnWorldPos - firstWaypoint; - if (glm::length(waypointDiff) > 1.0f) { + const float mismatchDist = glm::length(waypointDiff); + if (mismatchDist > 1.0f) { LOG_WARNING("Transport 0x", std::hex, guid, std::dec, " path ", pathId, ": firstWaypoint mismatch! spawnPos=(", spawnWorldPos.x, ",", spawnWorldPos.y, ",", spawnWorldPos.z, ")", " firstWaypoint=(", firstWaypoint.x, ",", firstWaypoint.y, ",", firstWaypoint.z, ")", " diff=(", waypointDiff.x, ",", waypointDiff.y, ",", waypointDiff.z, ")"); } + const bool firstWaypointIsOrigin = glm::dot(firstWaypoint, firstWaypoint) < 0.0001f; + if (mismatchDist > 500.0f || (firstWaypointIsOrigin && mismatchDist > 50.0f)) { + transport.allowBootstrapVelocity = false; + LOG_WARNING("Transport 0x", std::hex, guid, std::dec, + " disabling DBC bootstrap velocity due to large spawn/path mismatch (dist=", + mismatchDist, ", firstIsOrigin=", firstWaypointIsOrigin, ")"); + } } transport.rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // Identity quaternion @@ -580,7 +589,7 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos // Bootstrap velocity from mapped DBC path on first authoritative sample. // This avoids "stalled at dock" when server sends sparse transport snapshots. auto pathIt2 = paths_.find(transport->pathId); - if (pathIt2 != paths_.end()) { + if (transport->allowBootstrapVelocity && pathIt2 != paths_.end()) { const auto& path = pathIt2->second; if (path.points.size() >= 2 && path.durationMs > 0) { glm::vec3 local = position - transport->basePosition; @@ -595,54 +604,79 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos } } - size_t n = path.points.size(); - size_t nextIdx = (bestIdx + 1) % n; - uint32_t t0 = path.points[bestIdx].tMs; - uint32_t t1 = path.points[nextIdx].tMs; - if (nextIdx == 0 && t1 <= t0 && path.durationMs > 0) { - t1 = path.durationMs; - } - if (t1 <= t0 && n > 2) { - size_t prevIdx = (bestIdx + n - 1) % n; - t0 = path.points[prevIdx].tMs; - t1 = path.points[bestIdx].tMs; - glm::vec3 seg = path.points[bestIdx].pos - path.points[prevIdx].pos; - float dtSeg = static_cast(t1 - t0) / 1000.0f; - if (dtSeg > 0.001f) { - glm::vec3 v = seg / dtSeg; - float speed = glm::length(v); - constexpr float kMaxSpeed = 60.0f; - if (speed > kMaxSpeed) { - v *= (kMaxSpeed / speed); - } - transport->serverLinearVelocity = v; - transport->serverAngularVelocity = 0.0f; - transport->hasServerVelocity = true; - } + constexpr float kMaxBootstrapNearestDist = 80.0f; + if (bestDistSq > (kMaxBootstrapNearestDist * kMaxBootstrapNearestDist)) { + LOG_WARNING("Transport 0x", std::hex, guid, std::dec, + " skipping DBC bootstrap velocity: nearest path point too far (dist=", + std::sqrt(bestDistSq), ", path=", transport->pathId, ")"); } else { - glm::vec3 seg = path.points[nextIdx].pos - path.points[bestIdx].pos; - float dtSeg = static_cast(t1 - t0) / 1000.0f; - if (dtSeg > 0.001f) { - glm::vec3 v = seg / dtSeg; - float speed = glm::length(v); - constexpr float kMaxSpeed = 60.0f; - if (speed > kMaxSpeed) { - v *= (kMaxSpeed / speed); - } - transport->serverLinearVelocity = v; - transport->serverAngularVelocity = 0.0f; - transport->hasServerVelocity = true; + size_t n = path.points.size(); + size_t nextIdx = (bestIdx + 1) % n; + uint32_t t0 = path.points[bestIdx].tMs; + uint32_t t1 = path.points[nextIdx].tMs; + if (nextIdx == 0 && t1 <= t0 && path.durationMs > 0) { + t1 = path.durationMs; + } + if (t1 <= t0 && n > 2) { + size_t prevIdx = (bestIdx + n - 1) % n; + t0 = path.points[prevIdx].tMs; + t1 = path.points[bestIdx].tMs; + glm::vec3 seg = path.points[bestIdx].pos - path.points[prevIdx].pos; + float dtSeg = static_cast(t1 - t0) / 1000.0f; + if (dtSeg > 0.001f) { + glm::vec3 v = seg / dtSeg; + float speed = glm::length(v); + constexpr float kMinBootstrapSpeed = 0.25f; + constexpr float kMaxSpeed = 60.0f; + if (speed < kMinBootstrapSpeed) { + speed = 0.0f; + } + if (speed > kMaxSpeed) { + v *= (kMaxSpeed / speed); + } + if (speed >= kMinBootstrapSpeed) { + transport->serverLinearVelocity = v; + transport->serverAngularVelocity = 0.0f; + transport->hasServerVelocity = true; + } + } + } else { + glm::vec3 seg = path.points[nextIdx].pos - path.points[bestIdx].pos; + float dtSeg = static_cast(t1 - t0) / 1000.0f; + if (dtSeg > 0.001f) { + glm::vec3 v = seg / dtSeg; + float speed = glm::length(v); + constexpr float kMinBootstrapSpeed = 0.25f; + constexpr float kMaxSpeed = 60.0f; + if (speed < kMinBootstrapSpeed) { + speed = 0.0f; + } + if (speed > kMaxSpeed) { + v *= (kMaxSpeed / speed); + } + if (speed >= kMinBootstrapSpeed) { + transport->serverLinearVelocity = v; + transport->serverAngularVelocity = 0.0f; + transport->hasServerVelocity = true; + } + } } - } - if (transport->hasServerVelocity) { - LOG_INFO("Transport 0x", std::hex, guid, std::dec, - " bootstrapped velocity from DBC path ", transport->pathId, - " v=(", transport->serverLinearVelocity.x, ", ", - transport->serverLinearVelocity.y, ", ", - transport->serverLinearVelocity.z, ")"); + if (transport->hasServerVelocity) { + LOG_INFO("Transport 0x", std::hex, guid, std::dec, + " bootstrapped velocity from DBC path ", transport->pathId, + " v=(", transport->serverLinearVelocity.x, ", ", + transport->serverLinearVelocity.y, ", ", + transport->serverLinearVelocity.z, ")"); + } else { + LOG_INFO("Transport 0x", std::hex, guid, std::dec, + " skipped DBC bootstrap velocity (segment too short/static)"); + } } } + } else if (!transport->allowBootstrapVelocity) { + LOG_INFO("Transport 0x", std::hex, guid, std::dec, + " DBC bootstrap velocity disabled for this transport"); } }