Queue transport pre-spawn moves and add Z weapon sheath toggle

This commit is contained in:
Kelsi 2026-02-12 00:14:39 -08:00
parent d6e7b0809c
commit 5d63bb0988
4 changed files with 120 additions and 47 deletions

View file

@ -176,6 +176,13 @@ private:
std::unordered_map<uint32_t, uint32_t> gameObjectDisplayIdModelCache_; // displayId → M2 modelId
std::unordered_map<uint32_t, uint32_t> gameObjectDisplayIdWmoCache_; // displayId → WMO modelId
std::unordered_map<uint64_t, GameObjectInstanceInfo> 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<uint64_t, PendingTransportMove> 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;

View file

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

View file

@ -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<float, std::milli>(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);

View file

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