mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Fix Deeprun Tram: visual movement, direction, and player riding
- Fix NULL renderer pointers by moving TransportManager connection after initializeRenderers for WMO-only maps - Fix tram direction by negating DBC TransportAnimation X/Y local offsets before serverToCanonical conversion - Implement client-side M2 transport boarding via proximity detection (server doesn't send transport attachment for trams) - Use position-delta approach: player keeps normal movement while transport's frame-to-frame motion is applied on top - Prevent server movement packets from clearing client-side M2 transport state (isClientM2Transport guard) - Fix getPlayerWorldPosition for M2 transports: simple canonical addition instead of render-space matrix multiplication
This commit is contained in:
parent
e001aaa2b6
commit
f4c115ade9
5 changed files with 233 additions and 70 deletions
|
|
@ -658,6 +658,9 @@ public:
|
|||
playerTransportStickyTimer_ = 8.0f;
|
||||
movementInfo.transportGuid = transportGuid;
|
||||
}
|
||||
void setPlayerTransportOffset(const glm::vec3& offset) {
|
||||
playerTransportOffset_ = offset;
|
||||
}
|
||||
void clearPlayerTransport() {
|
||||
if (playerTransportGuid_ != 0) {
|
||||
playerTransportStickyGuid_ = playerTransportGuid_;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
namespace wowee::rendering {
|
||||
class WMORenderer;
|
||||
class M2Renderer;
|
||||
}
|
||||
|
||||
namespace wowee::pipeline {
|
||||
|
|
@ -71,6 +72,7 @@ struct ActiveTransport {
|
|||
float serverAngularVelocity;
|
||||
bool hasServerVelocity;
|
||||
bool allowBootstrapVelocity; // Disable DBC bootstrap when spawn/path mismatch is clearly invalid
|
||||
bool isM2 = false; // True if rendered as M2 (not WMO), uses M2Renderer for transforms
|
||||
};
|
||||
|
||||
class TransportManager {
|
||||
|
|
@ -79,12 +81,14 @@ public:
|
|||
~TransportManager();
|
||||
|
||||
void setWMORenderer(rendering::WMORenderer* renderer) { wmoRenderer_ = renderer; }
|
||||
void setM2Renderer(rendering::M2Renderer* renderer) { m2Renderer_ = renderer; }
|
||||
|
||||
void update(float deltaTime);
|
||||
void registerTransport(uint64_t guid, uint32_t wmoInstanceId, uint32_t pathId, const glm::vec3& spawnWorldPos, uint32_t entry = 0);
|
||||
void unregisterTransport(uint64_t guid);
|
||||
|
||||
ActiveTransport* getTransport(uint64_t guid);
|
||||
const std::unordered_map<uint64_t, ActiveTransport>& getTransports() const { return transports_; }
|
||||
glm::vec3 getPlayerWorldPosition(uint64_t transportGuid, const glm::vec3& localOffset);
|
||||
glm::mat4 getTransportInvTransform(uint64_t transportGuid);
|
||||
|
||||
|
|
@ -141,6 +145,7 @@ private:
|
|||
std::unordered_map<uint32_t, TransportPath> paths_; // Indexed by transportEntry (pathId from TransportAnimation.dbc)
|
||||
std::unordered_map<uint32_t, TransportPath> taxiPaths_; // Indexed by TaxiPath.dbc ID (world-coord paths for MO_TRANSPORT)
|
||||
rendering::WMORenderer* wmoRenderer_ = nullptr;
|
||||
rendering::M2Renderer* m2Renderer_ = nullptr;
|
||||
bool clientSideAnimation_ = false; // DISABLED - use server positions instead of client prediction
|
||||
float elapsedTime_ = 0.0f; // Total elapsed time (seconds)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -968,6 +968,15 @@ void Application::update(float deltaTime) {
|
|||
gameHandler->isTaxiMountActive() ||
|
||||
gameHandler->isTaxiActivationPending());
|
||||
bool onTransportNow = gameHandler && gameHandler->isOnTransport();
|
||||
// M2 transports (trams) use position-delta approach: player keeps normal
|
||||
// movement and the transport's frame-to-frame delta is applied on top.
|
||||
// Only WMO transports (ships) use full external-driven mode.
|
||||
bool isM2Transport = false;
|
||||
if (onTransportNow && gameHandler->getTransportManager()) {
|
||||
auto* tr = gameHandler->getTransportManager()->getTransport(gameHandler->getPlayerTransportGuid());
|
||||
isM2Transport = (tr && tr->isM2);
|
||||
}
|
||||
bool onWMOTransport = onTransportNow && !isM2Transport;
|
||||
if (worldEntryMovementGraceTimer_ > 0.0f) {
|
||||
worldEntryMovementGraceTimer_ -= deltaTime;
|
||||
// Clear stale movement from before teleport each frame
|
||||
|
|
@ -976,7 +985,7 @@ void Application::update(float deltaTime) {
|
|||
renderer->getCameraController()->clearMovementInputs();
|
||||
}
|
||||
if (renderer && renderer->getCameraController()) {
|
||||
const bool externallyDrivenMotion = onTaxi || onTransportNow || chargeActive_;
|
||||
const bool externallyDrivenMotion = onTaxi || onWMOTransport || chargeActive_;
|
||||
// Keep physics frozen (externalFollow) during landing clamp when terrain
|
||||
// hasn't loaded yet — prevents gravity from pulling player through void.
|
||||
bool landingClampActive = !onTaxi && taxiLandingClampTimer_ > 0.0f &&
|
||||
|
|
@ -1057,14 +1066,18 @@ void Application::update(float deltaTime) {
|
|||
|
||||
// Sync character render position ↔ canonical WoW coords each frame
|
||||
if (renderer && gameHandler) {
|
||||
bool onTransport = gameHandler->isOnTransport();
|
||||
// For position sync branching, only WMO transports use the dedicated
|
||||
// onTransport branch. M2 transports use the normal movement else branch
|
||||
// with a position-delta correction applied on top.
|
||||
bool onTransport = onWMOTransport;
|
||||
|
||||
// Debug: Log transport state changes
|
||||
static bool wasOnTransport = false;
|
||||
if (onTransport != wasOnTransport) {
|
||||
LOG_DEBUG("Transport state changed: onTransport=", onTransport,
|
||||
bool onTransportNowDbg = gameHandler->isOnTransport();
|
||||
if (onTransportNowDbg != wasOnTransport) {
|
||||
LOG_DEBUG("Transport state changed: onTransport=", onTransportNowDbg,
|
||||
" isM2=", isM2Transport,
|
||||
" guid=0x", std::hex, gameHandler->getPlayerTransportGuid(), std::dec);
|
||||
wasOnTransport = onTransport;
|
||||
wasOnTransport = onTransportNowDbg;
|
||||
}
|
||||
|
||||
if (onTaxi) {
|
||||
|
|
@ -1092,13 +1105,11 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
} else if (onTransport) {
|
||||
// Transport mode: compose world position from transport transform + local offset
|
||||
// WMO transport mode (ships): compose world position from transform + local offset
|
||||
glm::vec3 canonical = gameHandler->getComposedWorldPosition();
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(canonical);
|
||||
renderer->getCharacterPosition() = renderPos;
|
||||
// Keep movementInfo in lockstep with composed transport world position.
|
||||
gameHandler->setPosition(canonical.x, canonical.y, canonical.z);
|
||||
// Update camera follow target
|
||||
if (renderer->getCameraController()) {
|
||||
glm::vec3* followTarget = renderer->getCameraController()->getFollowTargetMutable();
|
||||
if (followTarget) {
|
||||
|
|
@ -1172,6 +1183,27 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
} else {
|
||||
glm::vec3 renderPos = renderer->getCharacterPosition();
|
||||
|
||||
// M2 transport riding: apply transport's frame-to-frame position delta
|
||||
// so the player moves with the tram while retaining normal movement input.
|
||||
if (isM2Transport && gameHandler->getTransportManager()) {
|
||||
auto* tr = gameHandler->getTransportManager()->getTransport(
|
||||
gameHandler->getPlayerTransportGuid());
|
||||
if (tr) {
|
||||
static glm::vec3 lastTransportCanonical(0);
|
||||
static uint64_t lastTransportGuid = 0;
|
||||
if (lastTransportGuid == gameHandler->getPlayerTransportGuid()) {
|
||||
glm::vec3 deltaCanonical = tr->position - lastTransportCanonical;
|
||||
glm::vec3 deltaRender = core::coords::canonicalToRender(deltaCanonical)
|
||||
- core::coords::canonicalToRender(glm::vec3(0));
|
||||
renderPos += deltaRender;
|
||||
renderer->getCharacterPosition() = renderPos;
|
||||
}
|
||||
lastTransportCanonical = tr->position;
|
||||
lastTransportGuid = gameHandler->getPlayerTransportGuid();
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 canonical = core::coords::renderToCanonical(renderPos);
|
||||
gameHandler->setPosition(canonical.x, canonical.y, canonical.z);
|
||||
|
||||
|
|
@ -1203,6 +1235,41 @@ void Application::update(float deltaTime) {
|
|||
facingSendCooldown_ = 0.1f; // max 10 Hz
|
||||
}
|
||||
}
|
||||
|
||||
// Client-side transport boarding detection (for M2 transports like trams
|
||||
// where the server doesn't send transport attachment data).
|
||||
// Use a generous AABB around each transport's current position.
|
||||
if (gameHandler->getTransportManager() && !gameHandler->isOnTransport()) {
|
||||
auto* tm = gameHandler->getTransportManager();
|
||||
glm::vec3 playerCanonical = core::coords::renderToCanonical(renderPos);
|
||||
|
||||
for (auto& [guid, transport] : tm->getTransports()) {
|
||||
if (!transport.isM2) continue;
|
||||
glm::vec3 diff = playerCanonical - transport.position;
|
||||
float horizDistSq = diff.x * diff.x + diff.y * diff.y;
|
||||
float vertDist = std::abs(diff.z);
|
||||
if (horizDistSq < 144.0f && vertDist < 15.0f) {
|
||||
gameHandler->setPlayerOnTransport(guid, playerCanonical - transport.position);
|
||||
LOG_DEBUG("M2 transport boarding: guid=0x", std::hex, guid, std::dec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// M2 transport disembark: player walked far enough from transport center
|
||||
if (isM2Transport && gameHandler->getTransportManager()) {
|
||||
auto* tm = gameHandler->getTransportManager();
|
||||
auto* tr = tm->getTransport(gameHandler->getPlayerTransportGuid());
|
||||
if (tr) {
|
||||
glm::vec3 playerCanonical = core::coords::renderToCanonical(renderPos);
|
||||
glm::vec3 diff = playerCanonical - tr->position;
|
||||
float horizDistSq = diff.x * diff.x + diff.y * diff.y;
|
||||
if (horizDistSq > 225.0f) {
|
||||
gameHandler->clearPlayerTransport();
|
||||
LOG_DEBUG("M2 transport disembark");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -2073,7 +2140,7 @@ void Application::setupUICallbacks() {
|
|||
}
|
||||
|
||||
uint32_t wmoInstanceId = it->second.instanceId;
|
||||
LOG_DEBUG("Registering server transport: GUID=0x", std::hex, guid, std::dec,
|
||||
LOG_WARNING("Registering server transport: GUID=0x", std::hex, guid, std::dec,
|
||||
" entry=", entry, " displayId=", displayId, " wmoInstance=", wmoInstanceId,
|
||||
" pos=(", x, ", ", y, ", ", z, ")");
|
||||
|
||||
|
|
@ -2101,15 +2168,18 @@ void Application::setupUICallbacks() {
|
|||
hasUsablePath = transportManager->hasUsableMovingPathForEntry(entry, 25.0f);
|
||||
}
|
||||
|
||||
LOG_WARNING("Transport path check: entry=", entry, " hasUsablePath=", hasUsablePath,
|
||||
" preferServerData=", preferServerData, " shipOrZepDisplay=", shipOrZeppelinDisplay);
|
||||
|
||||
if (preferServerData) {
|
||||
// Strict server-authoritative mode: do not infer/remap fallback routes.
|
||||
if (!hasUsablePath) {
|
||||
std::vector<glm::vec3> path = { canonicalSpawnPos };
|
||||
transportManager->loadPathFromNodes(pathId, path, false, 0.0f);
|
||||
LOG_DEBUG("Server-first strict registration: stationary fallback for GUID 0x",
|
||||
LOG_WARNING("Server-first strict registration: stationary fallback for GUID 0x",
|
||||
std::hex, guid, std::dec, " entry=", entry);
|
||||
} else {
|
||||
LOG_DEBUG("Server-first transport registration: using entry DBC path for entry ", entry);
|
||||
LOG_WARNING("Server-first transport registration: using entry DBC path for entry ", entry);
|
||||
}
|
||||
} else if (!hasUsablePath) {
|
||||
// Remap/infer path by spawn position when entry doesn't map 1:1 to DBC ids.
|
||||
|
|
@ -2119,12 +2189,12 @@ void Application::setupUICallbacks() {
|
|||
canonicalSpawnPos, 1200.0f, allowZOnly);
|
||||
if (inferredPath != 0) {
|
||||
pathId = inferredPath;
|
||||
LOG_DEBUG("Using inferred transport path ", pathId, " for entry ", entry);
|
||||
LOG_WARNING("Using inferred transport path ", pathId, " for entry ", entry);
|
||||
} else {
|
||||
uint32_t remappedPath = transportManager->pickFallbackMovingPath(entry, displayId);
|
||||
if (remappedPath != 0) {
|
||||
pathId = remappedPath;
|
||||
LOG_DEBUG("Using remapped fallback transport path ", pathId,
|
||||
LOG_WARNING("Using remapped fallback transport path ", pathId,
|
||||
" for entry ", entry, " displayId=", displayId,
|
||||
" (usableEntryPath=", transportManager->hasPathForEntry(entry), ")");
|
||||
} else {
|
||||
|
|
@ -2137,12 +2207,19 @@ void Application::setupUICallbacks() {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG("Using real transport path from TransportAnimation.dbc for entry ", entry);
|
||||
LOG_WARNING("Using real transport path from TransportAnimation.dbc for entry ", entry);
|
||||
}
|
||||
|
||||
// Register the transport with spawn position (prevents rendering at origin until server update)
|
||||
transportManager->registerTransport(guid, wmoInstanceId, pathId, canonicalSpawnPos, entry);
|
||||
|
||||
// Mark M2 transports (e.g. Deeprun Tram cars) so TransportManager uses M2Renderer
|
||||
if (!it->second.isWmo) {
|
||||
if (auto* tr = transportManager->getTransport(guid)) {
|
||||
tr->isM2 = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Server-authoritative movement - set initial position from spawn data
|
||||
glm::vec3 canonicalPos(x, y, z);
|
||||
transportManager->updateServerTransport(guid, canonicalPos, orientation);
|
||||
|
|
@ -2171,7 +2248,7 @@ void Application::setupUICallbacks() {
|
|||
}
|
||||
|
||||
if (auto* tr = transportManager->getTransport(guid); tr) {
|
||||
LOG_DEBUG("Transport registered: guid=0x", std::hex, guid, std::dec,
|
||||
LOG_WARNING("Transport registered: guid=0x", std::hex, guid, std::dec,
|
||||
" entry=", entry, " displayId=", displayId,
|
||||
" pathId=", tr->pathId,
|
||||
" mode=", (tr->useClientAnimation ? "client" : "server"),
|
||||
|
|
@ -3458,11 +3535,7 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
renderer->getTerrainManager()->setMapName(mapName);
|
||||
}
|
||||
|
||||
// Connect TransportManager to WMORenderer (for server transports)
|
||||
if (gameHandler && gameHandler->getTransportManager() && renderer->getWMORenderer()) {
|
||||
gameHandler->getTransportManager()->setWMORenderer(renderer->getWMORenderer());
|
||||
LOG_INFO("TransportManager connected to WMORenderer for online mode");
|
||||
}
|
||||
// NOTE: TransportManager renderer connection moved to after initializeRenderers (later in this function)
|
||||
|
||||
// Connect WMORenderer to M2Renderer (for hierarchical transforms: doodads following WMO parents)
|
||||
if (renderer->getWMORenderer() && renderer->getM2Renderer()) {
|
||||
|
|
@ -3931,9 +4004,18 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
renderer->getCameraController()->reset();
|
||||
}
|
||||
|
||||
// Set up test transport (development feature)
|
||||
// Test transport disabled — real transports come from server via UPDATEFLAG_TRANSPORT
|
||||
showProgress("Finalizing world...", 0.94f);
|
||||
setupTestTransport();
|
||||
// setupTestTransport();
|
||||
|
||||
// Connect TransportManager to renderers (must happen AFTER initializeRenderers)
|
||||
if (gameHandler && gameHandler->getTransportManager()) {
|
||||
auto* tm = gameHandler->getTransportManager();
|
||||
if (renderer->getWMORenderer()) tm->setWMORenderer(renderer->getWMORenderer());
|
||||
if (renderer->getM2Renderer()) tm->setM2Renderer(renderer->getM2Renderer());
|
||||
LOG_WARNING("TransportManager connected: wmoR=", (renderer->getWMORenderer() ? "yes" : "NULL"),
|
||||
" m2R=", (renderer->getM2Renderer() ? "yes" : "NULL"));
|
||||
}
|
||||
|
||||
// Set up NPC animation callbacks (for online creatures)
|
||||
showProgress("Preparing creatures...", 0.97f);
|
||||
|
|
@ -6368,6 +6450,10 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
|
|||
} else if (displayId == 2454 || displayId == 181688 || displayId == 190536) {
|
||||
modelPath = "World\\wmo\\transports\\icebreaker\\Transport_Icebreaker_ship.wmo";
|
||||
LOG_INFO("Overriding transport displayId ", displayId, " → Transport_Icebreaker_ship.wmo");
|
||||
} else if (displayId == 3831) {
|
||||
// Deeprun Tram car
|
||||
modelPath = "World\\Generic\\Gnome\\Passive Doodads\\Subway\\SubwayCar.m2";
|
||||
LOG_WARNING("Overriding transport displayId ", displayId, " → SubwayCar.m2");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6508,7 +6594,12 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
|
|||
// Transport GameObjects are not always named "transport" in their WMO path
|
||||
// (e.g. elevators/lifts). If the server marks it as a transport, always
|
||||
// notify so TransportManager can animate/carry passengers.
|
||||
if (gameHandler && gameHandler->isTransportGuid(guid)) {
|
||||
bool isTG = gameHandler && gameHandler->isTransportGuid(guid);
|
||||
LOG_WARNING("WMO GO spawned: guid=0x", std::hex, guid, std::dec,
|
||||
" entry=", entry, " displayId=", displayId,
|
||||
" isTransport=", isTG,
|
||||
" pos=(", x, ", ", y, ", ", z, ")");
|
||||
if (isTG) {
|
||||
gameHandler->notifyTransportSpawned(guid, entry, displayId, x, y, z, orientation);
|
||||
}
|
||||
|
||||
|
|
@ -6572,18 +6663,27 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
|
|||
return;
|
||||
}
|
||||
|
||||
// Freeze animation for static gameobjects, but let portals/effects animate
|
||||
// Freeze animation for static gameobjects, but let portals/effects/transports animate
|
||||
bool isTransportGO = gameHandler && gameHandler->isTransportGuid(guid);
|
||||
std::string lowerPath = modelPath;
|
||||
std::transform(lowerPath.begin(), lowerPath.end(), lowerPath.begin(), ::tolower);
|
||||
bool isAnimatedEffect = (lowerPath.find("instanceportal") != std::string::npos ||
|
||||
lowerPath.find("instancenewportal") != std::string::npos ||
|
||||
lowerPath.find("portalfx") != std::string::npos ||
|
||||
lowerPath.find("spellportal") != std::string::npos);
|
||||
if (!isAnimatedEffect) {
|
||||
if (!isAnimatedEffect && !isTransportGO) {
|
||||
m2Renderer->setInstanceAnimationFrozen(instanceId, true);
|
||||
}
|
||||
|
||||
gameObjectInstances_[guid] = {modelId, instanceId, false};
|
||||
|
||||
// Notify transport system for M2 transports (e.g. Deeprun Tram cars)
|
||||
if (gameHandler && gameHandler->isTransportGuid(guid)) {
|
||||
LOG_WARNING("M2 transport spawned: guid=0x", std::hex, guid, std::dec,
|
||||
" entry=", entry, " displayId=", displayId,
|
||||
" instanceId=", instanceId);
|
||||
gameHandler->notifyTransportSpawned(guid, entry, displayId, x, y, z, orientation);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("Spawned gameobject: guid=0x", std::hex, guid, std::dec,
|
||||
|
|
|
|||
|
|
@ -4936,10 +4936,17 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
LOG_INFO("Player on transport: 0x", std::hex, playerTransportGuid_, std::dec,
|
||||
" offset=(", playerTransportOffset_.x, ", ", playerTransportOffset_.y, ", ", playerTransportOffset_.z, ")");
|
||||
} else {
|
||||
if (playerTransportGuid_ != 0) {
|
||||
LOG_INFO("Player left transport");
|
||||
// Don't clear client-side M2 transport boarding (trams) —
|
||||
// the server doesn't know about client-detected transport attachment.
|
||||
bool isClientM2Transport = false;
|
||||
if (playerTransportGuid_ != 0 && transportManager_) {
|
||||
auto* tr = transportManager_->getTransport(playerTransportGuid_);
|
||||
isClientM2Transport = (tr && tr->isM2);
|
||||
}
|
||||
if (playerTransportGuid_ != 0 && !isClientM2Transport) {
|
||||
LOG_INFO("Player left transport");
|
||||
clearPlayerTransport();
|
||||
}
|
||||
clearPlayerTransport();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5173,9 +5180,13 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
queryGameObjectInfo(itEntry->second, block.guid);
|
||||
}
|
||||
// Detect transport GameObjects via UPDATEFLAG_TRANSPORT (0x0002)
|
||||
LOG_WARNING("GameObject CREATE: guid=0x", std::hex, block.guid, std::dec,
|
||||
" entry=", go->getEntry(), " displayId=", go->getDisplayId(),
|
||||
" updateFlags=0x", std::hex, block.updateFlags, std::dec,
|
||||
" pos=(", go->getX(), ", ", go->getY(), ", ", go->getZ(), ")");
|
||||
if (block.updateFlags & 0x0002) {
|
||||
transportGuids_.insert(block.guid);
|
||||
LOG_INFO("Detected transport GameObject: 0x", std::hex, block.guid, std::dec,
|
||||
LOG_WARNING("Detected transport GameObject: 0x", std::hex, block.guid, std::dec,
|
||||
" entry=", go->getEntry(),
|
||||
" displayId=", go->getDisplayId(),
|
||||
" pos=(", go->getX(), ", ", go->getY(), ", ", go->getZ(), ")");
|
||||
|
|
@ -5691,7 +5702,13 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
movementInfo.x = pos.x;
|
||||
movementInfo.y = pos.y;
|
||||
movementInfo.z = pos.z;
|
||||
if (playerTransportGuid_ != 0) {
|
||||
// Don't clear client-side M2 transport boarding
|
||||
bool isClientM2Transport = false;
|
||||
if (playerTransportGuid_ != 0 && transportManager_) {
|
||||
auto* tr = transportManager_->getTransport(playerTransportGuid_);
|
||||
isClientM2Transport = (tr && tr->isM2);
|
||||
}
|
||||
if (playerTransportGuid_ != 0 && !isClientM2Transport) {
|
||||
LOG_INFO("Player left transport (MOVEMENT)");
|
||||
clearPlayerTransport();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "game/transport_manager.hpp"
|
||||
#include "rendering/wmo_renderer.hpp"
|
||||
#include "rendering/m2_renderer.hpp"
|
||||
#include "core/coordinates.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include "pipeline/dbc_loader.hpp"
|
||||
|
|
@ -80,10 +81,11 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId,
|
|||
transport.localClockMs = 0;
|
||||
transport.hasServerClock = false;
|
||||
transport.serverClockOffsetMs = 0;
|
||||
// Default is server-authoritative movement.
|
||||
// Exception: elevator-style transports (z-only DBC paths) often do not stream continuous
|
||||
// movement updates from the server, but the client is expected to animate them.
|
||||
transport.useClientAnimation = (path.fromDBC && path.zOnly && path.durationMs > 0);
|
||||
// Start with client-side animation for all DBC paths with real movement.
|
||||
// If the server sends actual position updates, updateServerTransport() will switch
|
||||
// to server-driven mode. This ensures transports like trams (which the server doesn't
|
||||
// stream updates for) still animate, while ships/zeppelins switch to server authority.
|
||||
transport.useClientAnimation = (path.fromDBC && path.durationMs > 0);
|
||||
transport.clientAnimationReverse = false;
|
||||
transport.serverYaw = 0.0f;
|
||||
transport.hasServerYaw = false;
|
||||
|
|
@ -98,16 +100,19 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId,
|
|||
if (transport.useClientAnimation && path.durationMs > 0) {
|
||||
// Seed to a stable phase based on our local clock so elevators don't all start at t=0.
|
||||
transport.localClockMs = static_cast<uint32_t>(elapsedTime_ * 1000.0f) % path.durationMs;
|
||||
LOG_INFO("TransportManager: Enabled client animation for z-only transport 0x",
|
||||
LOG_INFO("TransportManager: Enabled client animation for transport 0x",
|
||||
std::hex, guid, std::dec, " path=", pathId,
|
||||
" durationMs=", path.durationMs, " seedMs=", transport.localClockMs);
|
||||
" durationMs=", path.durationMs, " seedMs=", transport.localClockMs,
|
||||
(path.worldCoords ? " [worldCoords]" : (path.zOnly ? " [z-only]" : "")));
|
||||
}
|
||||
|
||||
updateTransformMatrices(transport);
|
||||
|
||||
// CRITICAL: Update WMO renderer with initial transform
|
||||
if (wmoRenderer_) {
|
||||
wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||
if (transport.isM2) {
|
||||
if (m2Renderer_) m2Renderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||
} else {
|
||||
if (wmoRenderer_) wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||
}
|
||||
|
||||
transports_[guid] = transport;
|
||||
|
|
@ -140,6 +145,14 @@ glm::vec3 TransportManager::getPlayerWorldPosition(uint64_t transportGuid, const
|
|||
return localOffset; // Fallback
|
||||
}
|
||||
|
||||
if (transport->isM2) {
|
||||
// M2 transports (trams): localOffset is a canonical world-space delta
|
||||
// from the transport's canonical position. Just add directly.
|
||||
return transport->position + localOffset;
|
||||
}
|
||||
|
||||
// WMO transports (ships): localOffset is in transport-local space,
|
||||
// use the render-space transform matrix.
|
||||
glm::vec4 localPos(localOffset, 1.0f);
|
||||
glm::vec4 worldPos = transport->transform * localPos;
|
||||
return glm::vec3(worldPos);
|
||||
|
|
@ -284,14 +297,17 @@ void TransportManager::updateTransportMovement(ActiveTransport& transport, float
|
|||
glm::vec3 pathOffset = evalTimedCatmullRom(path, pathTimeMs);
|
||||
// Guard against bad fallback Z curves on some remapped transport paths (notably icebreakers),
|
||||
// where path offsets can sink far below sea level when we only have spawn-time data.
|
||||
if (transport.useClientAnimation && transport.serverUpdateCount <= 1) {
|
||||
constexpr float kMinFallbackZOffset = -2.0f;
|
||||
pathOffset.z = glm::max(pathOffset.z, kMinFallbackZOffset);
|
||||
}
|
||||
if (!transport.useClientAnimation && !transport.hasServerClock) {
|
||||
constexpr float kMinFallbackZOffset = -2.0f;
|
||||
constexpr float kMaxFallbackZOffset = 8.0f;
|
||||
pathOffset.z = glm::clamp(pathOffset.z, kMinFallbackZOffset, kMaxFallbackZOffset);
|
||||
// Skip Z clamping for world-coordinate paths (TaxiPathNode) where values are absolute positions.
|
||||
if (!path.worldCoords) {
|
||||
if (transport.useClientAnimation && transport.serverUpdateCount <= 1) {
|
||||
constexpr float kMinFallbackZOffset = -2.0f;
|
||||
pathOffset.z = glm::max(pathOffset.z, kMinFallbackZOffset);
|
||||
}
|
||||
if (!transport.useClientAnimation && !transport.hasServerClock) {
|
||||
constexpr float kMinFallbackZOffset = -2.0f;
|
||||
constexpr float kMaxFallbackZOffset = 8.0f;
|
||||
pathOffset.z = glm::clamp(pathOffset.z, kMinFallbackZOffset, kMaxFallbackZOffset);
|
||||
}
|
||||
}
|
||||
transport.position = transport.basePosition + pathOffset;
|
||||
|
||||
|
|
@ -307,24 +323,20 @@ void TransportManager::updateTransportMovement(ActiveTransport& transport, float
|
|||
updateTransformMatrices(transport);
|
||||
|
||||
// Update WMO instance position
|
||||
if (wmoRenderer_) {
|
||||
wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||
if (transport.isM2) {
|
||||
if (m2Renderer_) m2Renderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||
} else {
|
||||
if (wmoRenderer_) wmoRenderer_->setInstanceTransform(transport.wmoInstanceId, transport.transform);
|
||||
}
|
||||
|
||||
// Debug logging every 120 frames (~2 seconds at 60fps)
|
||||
// Debug logging every 600 frames (~10 seconds at 60fps)
|
||||
static int debugFrameCount = 0;
|
||||
if (debugFrameCount++ % 120 == 0) {
|
||||
// Log canonical position AND render position to check coordinate conversion
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(transport.position);
|
||||
if (debugFrameCount++ % 600 == 0) {
|
||||
LOG_DEBUG("Transport 0x", std::hex, transport.guid, std::dec,
|
||||
" pathTime=", pathTimeMs, "ms / ", path.durationMs, "ms",
|
||||
" canonicalPos=(", transport.position.x, ", ", transport.position.y, ", ", transport.position.z, ")",
|
||||
" renderPos=(", renderPos.x, ", ", renderPos.y, ", ", renderPos.z, ")",
|
||||
" basePos=(", transport.basePosition.x, ", ", transport.basePosition.y, ", ", transport.basePosition.z, ")",
|
||||
" pathOffset=(", pathOffset.x, ", ", pathOffset.y, ", ", pathOffset.z, ")",
|
||||
" pos=(", transport.position.x, ", ", transport.position.y, ", ", transport.position.z, ")",
|
||||
" mode=", (transport.useClientAnimation ? "client" : "server"),
|
||||
" hasServerClock=", transport.hasServerClock,
|
||||
" offset=", transport.serverClockOffsetMs, "ms");
|
||||
" isM2=", transport.isM2);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -561,12 +573,24 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
|
|||
// Track server updates
|
||||
transport->serverUpdateCount++;
|
||||
transport->lastServerUpdate = elapsedTime_;
|
||||
// Server updates take precedence for moving XY transports, but z-only elevators should
|
||||
// remain client-animated (server may only send sparse state updates).
|
||||
if (!isZOnlyPath) {
|
||||
transport->useClientAnimation = false;
|
||||
} else {
|
||||
// Z-only elevators and world-coordinate paths (TaxiPathNode) always stay client-driven.
|
||||
// For other DBC paths (trams, ships): only switch to server-driven mode when the server
|
||||
// sends a position that actually differs from the current position, indicating it's
|
||||
// actively streaming movement data (not just echoing the spawn position).
|
||||
if (isZOnlyPath || isWorldCoordPath) {
|
||||
transport->useClientAnimation = true;
|
||||
} else if (transport->useClientAnimation && hasPath && pathIt->second.fromDBC) {
|
||||
float posDelta = glm::length(position - transport->position);
|
||||
if (posDelta > 1.0f) {
|
||||
// Server sent a meaningfully different position — it's actively driving this transport
|
||||
transport->useClientAnimation = false;
|
||||
LOG_INFO("Transport 0x", std::hex, guid, std::dec,
|
||||
" switching to server-driven (posDelta=", posDelta, ")");
|
||||
}
|
||||
// Otherwise keep client animation (server just echoed spawn pos or sent small jitter)
|
||||
} else if (!hasPath || !pathIt->second.fromDBC) {
|
||||
// No DBC path — purely server-driven
|
||||
transport->useClientAnimation = false;
|
||||
}
|
||||
transport->clientAnimationReverse = false;
|
||||
|
||||
|
|
@ -576,8 +600,10 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
|
|||
transport->position = position;
|
||||
transport->rotation = glm::angleAxis(orientation, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
updateTransformMatrices(*transport);
|
||||
if (wmoRenderer_) {
|
||||
wmoRenderer_->setInstanceTransform(transport->wmoInstanceId, transport->transform);
|
||||
if (transport->isM2) {
|
||||
if (m2Renderer_) m2Renderer_->setInstanceTransform(transport->wmoInstanceId, transport->transform);
|
||||
} else {
|
||||
if (wmoRenderer_) wmoRenderer_->setInstanceTransform(transport->wmoInstanceId, transport->transform);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -846,12 +872,23 @@ bool TransportManager::loadTransportAnimationDBC(pipeline::AssetManager* assetMg
|
|||
std::vector<TimedPoint> timedPoints;
|
||||
timedPoints.reserve(sortedWaypoints.size() + 1); // +1 for wrap point
|
||||
|
||||
// Log first few waypoints for transport 2074 to see conversion
|
||||
// Log DBC waypoints for tram entries
|
||||
if (transportEntry >= 176080 && transportEntry <= 176085) {
|
||||
size_t mid = sortedWaypoints.size() / 4; // ~quarter through
|
||||
size_t mid2 = sortedWaypoints.size() / 2; // ~halfway
|
||||
LOG_WARNING("DBC path entry=", transportEntry, " nPts=", sortedWaypoints.size(),
|
||||
" [0] t=", sortedWaypoints[0].first, " raw=(", sortedWaypoints[0].second.x, ",", sortedWaypoints[0].second.y, ",", sortedWaypoints[0].second.z, ")",
|
||||
" [", mid, "] t=", sortedWaypoints[mid].first, " raw=(", sortedWaypoints[mid].second.x, ",", sortedWaypoints[mid].second.y, ",", sortedWaypoints[mid].second.z, ")",
|
||||
" [", mid2, "] t=", sortedWaypoints[mid2].first, " raw=(", sortedWaypoints[mid2].second.x, ",", sortedWaypoints[mid2].second.y, ",", sortedWaypoints[mid2].second.z, ")");
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < sortedWaypoints.size(); idx++) {
|
||||
const auto& [tMs, pos] = sortedWaypoints[idx];
|
||||
|
||||
// TransportAnimation.dbc uses server coordinates - convert to canonical
|
||||
glm::vec3 canonical = core::coords::serverToCanonical(pos);
|
||||
// TransportAnimation.dbc local offsets use a coordinate system where
|
||||
// the travel axis is negated relative to server world coords.
|
||||
// Negate X and Y before converting to canonical (Z=height stays the same).
|
||||
glm::vec3 canonical = core::coords::serverToCanonical(glm::vec3(-pos.x, -pos.y, pos.z));
|
||||
|
||||
// CRITICAL: Detect if serverToCanonical is zeroing nonzero inputs
|
||||
if ((pos.x != 0.0f || pos.y != 0.0f || pos.z != 0.0f) &&
|
||||
|
|
@ -896,7 +933,8 @@ bool TransportManager::loadTransportAnimationDBC(pipeline::AssetManager* assetMg
|
|||
|
||||
// Add duplicate first point at end with wrap duration
|
||||
// This makes the wrap segment (last → first) have proper duration
|
||||
glm::vec3 firstCanonical = core::coords::serverToCanonical(sortedWaypoints.front().second);
|
||||
const auto& fp = sortedWaypoints.front().second;
|
||||
glm::vec3 firstCanonical = core::coords::serverToCanonical(glm::vec3(-fp.x, -fp.y, fp.z));
|
||||
timedPoints.push_back({lastTimeMs + wrapMs, firstCanonical});
|
||||
|
||||
uint32_t durationMs = lastTimeMs + wrapMs;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue