Harden transport updates and fix waterfall particle tint

This commit is contained in:
Kelsi 2026-02-12 00:45:24 -08:00
parent d8f04b27d1
commit 8e95b69949
6 changed files with 137 additions and 100 deletions

View file

@ -217,6 +217,13 @@ void GameHandler::update(float deltaTime) {
if (taxiStartGrace_ > 0.0f) {
taxiStartGrace_ -= deltaTime;
}
if (playerTransportStickyTimer_ > 0.0f) {
playerTransportStickyTimer_ -= deltaTime;
if (playerTransportStickyTimer_ <= 0.0f) {
playerTransportStickyTimer_ = 0.0f;
playerTransportStickyGuid_ = 0;
}
}
// Taxi logic timing
auto taxiStart = std::chrono::high_resolution_clock::now();
@ -1985,11 +1992,18 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
if (entityManager.hasEntity(guid)) {
const bool isKnownTransport = transportGuids_.count(guid) > 0;
if (isKnownTransport) {
if (playerTransportGuid_ == guid) {
LOG_INFO("Keeping transport in-range while player is aboard: 0x", std::hex, guid, std::dec);
continue;
}
LOG_INFO("Processing out-of-range removal for transport: 0x", std::hex, guid, std::dec);
// Keep transports alive across out-of-range flapping.
// Boats/zeppelins are global movers and removing them here can make
// them disappear until a later movement snapshot happens to recreate them.
const bool playerAboardNow = (playerTransportGuid_ == guid);
const bool stickyAboard = (playerTransportStickyGuid_ == guid && playerTransportStickyTimer_ > 0.0f);
const bool movementSaysAboard = (movementInfo.transportGuid == guid);
LOG_INFO("Preserving transport on out-of-range: 0x",
std::hex, guid, std::dec,
" now=", playerAboardNow,
" sticky=", stickyAboard,
" movement=", movementSaysAboard);
continue;
}
LOG_DEBUG("Entity went out of range: 0x", std::hex, guid, std::dec);
@ -2006,8 +2020,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
serverUpdatedTransportGuids_.erase(guid);
clearTransportAttachment(guid);
if (playerTransportGuid_ == guid) {
playerTransportGuid_ = 0;
playerTransportOffset_ = glm::vec3(0.0f);
clearPlayerTransport();
}
entityManager.removeEntity(guid);
}
@ -2051,7 +2064,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
// Track player-on-transport state
if (block.guid == playerGuid) {
if (block.onTransport) {
playerTransportGuid_ = block.transportGuid;
setPlayerOnTransport(block.transportGuid, glm::vec3(0.0f));
// Convert transport offset from server → canonical coordinates
glm::vec3 serverOffset(block.transportX, block.transportY, block.transportZ);
playerTransportOffset_ = core::coords::serverToCanonical(serverOffset);
@ -2063,13 +2076,12 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
movementInfo.z = composed.z;
}
LOG_INFO("Player on transport: 0x", std::hex, playerTransportGuid_, std::dec,
" offset=(", playerTransportOffset_.x, ", ", playerTransportOffset_.y, ", ", playerTransportOffset_.z, ")");
" offset=(", playerTransportOffset_.x, ", ", playerTransportOffset_.y, ", ", playerTransportOffset_.z, ")");
} else {
if (playerTransportGuid_ != 0) {
LOG_INFO("Player left transport");
}
playerTransportGuid_ = 0;
playerTransportOffset_ = glm::vec3(0.0f);
clearPlayerTransport();
}
}
@ -2657,7 +2669,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
// Track player-on-transport state from MOVEMENT updates
if (block.onTransport) {
playerTransportGuid_ = block.transportGuid;
setPlayerOnTransport(block.transportGuid, glm::vec3(0.0f));
// Convert transport offset from server → canonical coordinates
glm::vec3 serverOffset(block.transportX, block.transportY, block.transportZ);
playerTransportOffset_ = core::coords::serverToCanonical(serverOffset);
@ -2679,8 +2691,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
movementInfo.z = pos.z;
if (playerTransportGuid_ != 0) {
LOG_INFO("Player left transport (MOVEMENT)");
playerTransportGuid_ = 0;
playerTransportOffset_ = glm::vec3(0.0f);
clearPlayerTransport();
}
}
}
@ -2772,9 +2783,17 @@ void GameHandler::handleDestroyObject(network::Packet& packet) {
// Remove entity
if (entityManager.hasEntity(data.guid)) {
if (transportGuids_.count(data.guid) > 0) {
serverUpdatedTransportGuids_.erase(data.guid);
LOG_INFO("Ignoring destroy for transport entity: 0x", std::hex, data.guid, std::dec);
return;
const bool playerAboardNow = (playerTransportGuid_ == data.guid);
const bool stickyAboard = (playerTransportStickyGuid_ == data.guid && playerTransportStickyTimer_ > 0.0f);
const bool movementSaysAboard = (movementInfo.transportGuid == data.guid);
if (playerAboardNow || stickyAboard || movementSaysAboard) {
serverUpdatedTransportGuids_.erase(data.guid);
LOG_INFO("Preserving in-use transport on destroy: 0x", std::hex, data.guid, std::dec,
" now=", playerAboardNow,
" sticky=", stickyAboard,
" movement=", movementSaysAboard);
return;
}
}
// Mirror out-of-range handling: invoke render-layer despawn callbacks before entity removal.
auto entity = entityManager.getEntity(data.guid);
@ -2785,6 +2804,13 @@ void GameHandler::handleDestroyObject(network::Packet& packet) {
gameObjectDespawnCallback_(data.guid);
}
}
if (transportGuids_.count(data.guid) > 0) {
transportGuids_.erase(data.guid);
serverUpdatedTransportGuids_.erase(data.guid);
if (playerTransportGuid_ == data.guid) {
clearPlayerTransport();
}
}
clearTransportAttachment(data.guid);
entityManager.removeEntity(data.guid);
LOG_INFO("Destroyed entity: 0x", std::hex, data.guid, std::dec,

View file

@ -57,22 +57,13 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId,
transport.basePosition = spawnWorldPos - offset0; // Infer base from spawn
transport.position = spawnWorldPos; // Start at spawn position (base + offset0)
// Sanity check: firstWaypoint should match spawnWorldPos
// TransportAnimation paths are local offsets; first waypoint is expected near origin.
// Warn only if the local path itself looks suspicious.
glm::vec3 firstWaypoint = path.points[0].pos;
glm::vec3 waypointDiff = spawnWorldPos - firstWaypoint;
const float mismatchDist = glm::length(waypointDiff);
if (mismatchDist > 1.0f) {
if (glm::length(firstWaypoint) > 10.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, ")");
": first local waypoint far from origin: (",
firstWaypoint.x, ",", firstWaypoint.y, ",", firstWaypoint.z, ")");
}
}
@ -576,14 +567,17 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
if (dt > 0.001f) {
glm::vec3 v = (position - prevPos) / dt;
const float speed = glm::length(v);
constexpr float kMinAuthoritativeSpeed = 0.15f;
constexpr float kMaxSpeed = 60.0f;
if (speed > kMaxSpeed) {
v *= (kMaxSpeed / speed);
}
if (speed >= kMinAuthoritativeSpeed) {
if (speed > kMaxSpeed) {
v *= (kMaxSpeed / speed);
}
transport->serverLinearVelocity = v;
transport->serverAngularVelocity = 0.0f;
transport->hasServerVelocity = true;
transport->serverLinearVelocity = v;
transport->serverAngularVelocity = 0.0f;
transport->hasServerVelocity = true;
}
}
} else {
// Bootstrap velocity from mapped DBC path on first authoritative sample.
@ -611,55 +605,41 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
std::sqrt(bestDistSq), ", path=", transport->pathId, ")");
} else {
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;
constexpr float kMinBootstrapSpeed = 0.25f;
constexpr float kMaxSpeed = 60.0f;
auto tryApplySegment = [&](size_t a, size_t b) {
uint32_t t0 = path.points[a].tMs;
uint32_t t1 = path.points[b].tMs;
if (b == 0 && t1 <= t0 && path.durationMs > 0) {
t1 = path.durationMs;
}
if (t1 <= t0) return;
glm::vec3 seg = path.points[b].pos - path.points[a].pos;
float dtSeg = static_cast<float>(t1 - t0) / 1000.0f;
if (dtSeg <= 0.001f) return;
glm::vec3 v = seg / dtSeg;
float speed = glm::length(v);
if (speed < kMinBootstrapSpeed) return;
if (speed > kMaxSpeed) {
v *= (kMaxSpeed / speed);
}
transport->serverLinearVelocity = v;
transport->serverAngularVelocity = 0.0f;
transport->hasServerVelocity = true;
};
// Prefer nearest forward meaningful segment from bestIdx.
for (size_t step = 1; step < n && !transport->hasServerVelocity; ++step) {
size_t a = (bestIdx + step - 1) % n;
size_t b = (bestIdx + step) % n;
tryApplySegment(a, b);
}
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<float>(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<float>(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;
}
}
// Fallback: nearest backward meaningful segment.
for (size_t step = 1; step < n && !transport->hasServerVelocity; ++step) {
size_t b = (bestIdx + n - step + 1) % n;
size_t a = (bestIdx + n - step) % n;
tryApplySegment(a, b);
}
if (transport->hasServerVelocity) {