mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Fix taxi startup/attachment and reduce taxi streaming hitches
This commit is contained in:
parent
f752a4f517
commit
38cef8d9c6
11 changed files with 396 additions and 190 deletions
|
|
@ -622,6 +622,7 @@ public:
|
|||
void closeTaxi();
|
||||
void activateTaxi(uint32_t destNodeId);
|
||||
bool isOnTaxiFlight() const { return onTaxiFlight_; }
|
||||
bool isTaxiMountActive() const { return taxiMountActive_; }
|
||||
const ShowTaxiNodesData& getTaxiData() const { return currentTaxiData_; }
|
||||
uint32_t getTaxiCurrentNode() const { return currentTaxiData_.nearestNode; }
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ private:
|
|||
|
||||
MPQManager mpqManager;
|
||||
mutable std::mutex readMutex;
|
||||
mutable std::mutex cacheMutex;
|
||||
std::map<std::string, std::shared_ptr<DBCFile>> dbcCache;
|
||||
|
||||
// Decompressed file cache (LRU, dynamic budget based on system RAM)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <list>
|
||||
#include <vector>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace wowee {
|
||||
|
|
@ -187,6 +188,7 @@ public:
|
|||
void setUnloadRadius(int radius) { unloadRadius = radius; }
|
||||
void setStreamingEnabled(bool enabled) { streamingEnabled = enabled; }
|
||||
void setUpdateInterval(float seconds) { updateInterval = seconds; }
|
||||
void setTaxiStreamingMode(bool enabled) { taxiStreamingMode_ = enabled; }
|
||||
void setWaterRenderer(WaterRenderer* renderer) { waterRenderer = renderer; }
|
||||
void setM2Renderer(M2Renderer* renderer) { m2Renderer = renderer; }
|
||||
void setWMORenderer(WMORenderer* renderer) { wmoRenderer = renderer; }
|
||||
|
|
@ -286,6 +288,7 @@ private:
|
|||
int unloadRadius = 7; // Unload tiles beyond this radius
|
||||
float updateInterval = 0.033f; // Check streaming every 33ms (~30 fps)
|
||||
float timeSinceLastUpdate = 0.0f;
|
||||
bool taxiStreamingMode_ = false;
|
||||
|
||||
// Tile size constants (WoW ADT specifications)
|
||||
// A tile (ADT) = 16x16 chunks = 533.33 units across
|
||||
|
|
@ -298,7 +301,7 @@ private:
|
|||
int workerCount = 0;
|
||||
std::mutex queueMutex;
|
||||
std::condition_variable queueCV;
|
||||
std::queue<TileCoord> loadQueue;
|
||||
std::deque<TileCoord> loadQueue;
|
||||
std::queue<std::shared_ptr<PendingTile>> readyQueue;
|
||||
|
||||
// In-RAM tile cache (LRU) to avoid re-reading from disk
|
||||
|
|
|
|||
|
|
@ -447,10 +447,14 @@ void Application::update(float deltaTime) {
|
|||
renderer->getCameraController()->setRunSpeedOverride(gameHandler->getServerRunSpeed());
|
||||
}
|
||||
|
||||
bool onTaxi = gameHandler && gameHandler->isOnTaxiFlight();
|
||||
bool onTaxi = gameHandler && (gameHandler->isOnTaxiFlight() || gameHandler->isTaxiMountActive());
|
||||
if (renderer && renderer->getCameraController()) {
|
||||
renderer->getCameraController()->setExternalFollow(onTaxi);
|
||||
renderer->getCameraController()->setExternalMoving(onTaxi);
|
||||
if (onTaxi) {
|
||||
// Drop any stale local movement toggles while server drives taxi motion.
|
||||
renderer->getCameraController()->clearMovementInputs();
|
||||
}
|
||||
if (lastTaxiFlight_ && !onTaxi) {
|
||||
renderer->getCameraController()->clearMovementInputs();
|
||||
}
|
||||
|
|
@ -467,7 +471,12 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
if (renderer && renderer->getTerrainManager()) {
|
||||
renderer->getTerrainManager()->setStreamingEnabled(true);
|
||||
renderer->getTerrainManager()->setUpdateInterval(0.1f);
|
||||
// Slightly slower stream tick on taxi reduces bursty I/O and frame hitches.
|
||||
renderer->getTerrainManager()->setUpdateInterval(onTaxi ? 0.2f : 0.1f);
|
||||
// Keep taxi streaming focused ahead on the route to reduce burst loads.
|
||||
renderer->getTerrainManager()->setLoadRadius(onTaxi ? 2 : 4);
|
||||
renderer->getTerrainManager()->setUnloadRadius(onTaxi ? 5 : 7);
|
||||
renderer->getTerrainManager()->setTaxiStreamingMode(onTaxi);
|
||||
}
|
||||
lastTaxiFlight_ = onTaxi;
|
||||
|
||||
|
|
@ -489,9 +498,6 @@ void Application::update(float deltaTime) {
|
|||
glm::vec3 canonical(playerEntity->getX(), playerEntity->getY(), playerEntity->getZ());
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(canonical);
|
||||
renderer->getCharacterPosition() = renderPos;
|
||||
// Lock yaw to server/taxi orientation so mouse cannot steer during taxi.
|
||||
float yawDeg = glm::degrees(playerEntity->getOrientation()) + 90.0f;
|
||||
renderer->setCharacterYaw(yawDeg);
|
||||
}
|
||||
} else if (onTransport) {
|
||||
// Transport mode: compose world position from transport transform + local offset
|
||||
|
|
@ -518,7 +524,7 @@ void Application::update(float deltaTime) {
|
|||
}
|
||||
|
||||
// Send movement heartbeat every 500ms (keeps server position in sync)
|
||||
// Skip during taxi flights - server controls position
|
||||
// Skip periodic taxi heartbeats; taxi start sends explicit heartbeats already.
|
||||
if (gameHandler && renderer && !onTaxi) {
|
||||
movementHeartbeatTimer += deltaTime;
|
||||
if (movementHeartbeatTimer >= 0.5f) {
|
||||
|
|
@ -791,21 +797,27 @@ void Application::setupUICallbacks() {
|
|||
|
||||
std::set<std::pair<int, int>> uniqueTiles;
|
||||
|
||||
// Sample waypoints along path and gather tiles
|
||||
for (const auto& waypoint : path) {
|
||||
// Sample waypoints along path and gather tiles.
|
||||
// Use stride to avoid enqueueing huge numbers of tiles at once.
|
||||
const size_t stride = 4;
|
||||
for (size_t i = 0; i < path.size(); i += stride) {
|
||||
const auto& waypoint = path[i];
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(waypoint);
|
||||
int tileX = static_cast<int>(32 - (renderPos.x / 533.33333f));
|
||||
int tileY = static_cast<int>(32 - (renderPos.y / 533.33333f));
|
||||
|
||||
// Load tile at waypoint + 1 radius around it (3x3 per waypoint)
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
int tx = tileX + dx;
|
||||
int ty = tileY + dy;
|
||||
if (tx >= 0 && tx <= 63 && ty >= 0 && ty <= 63) {
|
||||
uniqueTiles.insert({tx, ty});
|
||||
}
|
||||
}
|
||||
// Precache only the sampled tile itself; terrain streaming handles neighbors.
|
||||
if (tileX >= 0 && tileX <= 63 && tileY >= 0 && tileY <= 63) {
|
||||
uniqueTiles.insert({tileX, tileY});
|
||||
}
|
||||
}
|
||||
// Ensure final destination tile is included.
|
||||
if (!path.empty()) {
|
||||
glm::vec3 renderPos = core::coords::canonicalToRender(path.back());
|
||||
int tileX = static_cast<int>(32 - (renderPos.x / 533.33333f));
|
||||
int tileY = static_cast<int>(32 - (renderPos.y / 533.33333f));
|
||||
if (tileX >= 0 && tileX <= 63 && tileY >= 0 && tileY <= 63) {
|
||||
uniqueTiles.insert({tileX, tileY});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -817,25 +829,22 @@ void Application::setupUICallbacks() {
|
|||
// Taxi orientation callback - update mount rotation during flight
|
||||
gameHandler->setTaxiOrientationCallback([this](float yaw, float pitch, float roll) {
|
||||
if (renderer && renderer->getCameraController()) {
|
||||
// Convert radians to degrees for camera controller (character facing)
|
||||
// Taxi callback now provides render-space yaw directly.
|
||||
float yawDegrees = glm::degrees(yaw);
|
||||
renderer->getCameraController()->setFacingYaw(yawDegrees);
|
||||
renderer->setCharacterYaw(yawDegrees);
|
||||
// Set mount pitch and roll for realistic flight animation
|
||||
renderer->setMountPitchRoll(pitch, roll);
|
||||
}
|
||||
});
|
||||
|
||||
// Taxi flight start callback - upload all precached tiles to GPU before flight begins
|
||||
// Taxi flight start callback - keep non-blocking to avoid hitching at takeoff.
|
||||
gameHandler->setTaxiFlightStartCallback([this]() {
|
||||
if (renderer && renderer->getTerrainManager() && renderer->getM2Renderer()) {
|
||||
LOG_INFO("Uploading all precached tiles (terrain + M2 models) to GPU before taxi flight...");
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
renderer->getTerrainManager()->processAllReadyTiles();
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
LOG_INFO("Taxi flight start: incremental terrain/M2 streaming active");
|
||||
uint32_t m2Count = renderer->getM2Renderer()->getModelCount();
|
||||
uint32_t instCount = renderer->getM2Renderer()->getInstanceCount();
|
||||
LOG_INFO("GPU upload completed in ", durationMs, "ms - ", m2Count, " M2 models in VRAM (", instCount, " instances)");
|
||||
LOG_INFO("Current M2 VRAM state: ", m2Count, " models (", instCount, " instances)");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -3080,7 +3089,7 @@ void Application::processCreatureSpawnQueue() {
|
|||
retries = it->second;
|
||||
}
|
||||
if (retries < MAX_CREATURE_SPAWN_RETRIES) {
|
||||
creatureSpawnRetryCounts_[s.guid] = static_cast<uint8_t>(retries + 1);
|
||||
creatureSpawnRetryCounts_[s.guid] = static_cast<uint16_t>(retries + 1);
|
||||
pendingCreatureSpawns_.push_back(s);
|
||||
pendingCreatureSpawnGuids_.insert(s.guid);
|
||||
} else {
|
||||
|
|
@ -3176,35 +3185,87 @@ void Application::processPendingMount() {
|
|||
|
||||
// Apply creature skin textures from CreatureDisplayInfo.dbc.
|
||||
// Re-apply even for cached models so transient failures can self-heal.
|
||||
std::string modelDir;
|
||||
size_t lastSlash = m2Path.find_last_of("\\/");
|
||||
if (lastSlash != std::string::npos) {
|
||||
modelDir = m2Path.substr(0, lastSlash + 1);
|
||||
}
|
||||
|
||||
auto itDisplayData = displayDataMap_.find(mountDisplayId);
|
||||
bool haveDisplayData = false;
|
||||
CreatureDisplayData dispData{};
|
||||
if (itDisplayData != displayDataMap_.end()) {
|
||||
CreatureDisplayData dispData = itDisplayData->second;
|
||||
dispData = itDisplayData->second;
|
||||
haveDisplayData = true;
|
||||
} else {
|
||||
// Some taxi mount display IDs are sparse; recover skins by matching model path.
|
||||
std::string lowerMountPath = m2Path;
|
||||
std::transform(lowerMountPath.begin(), lowerMountPath.end(), lowerMountPath.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
int bestScore = -1;
|
||||
for (const auto& [dispId, data] : displayDataMap_) {
|
||||
auto pit = modelIdToPath_.find(data.modelId);
|
||||
if (pit == modelIdToPath_.end()) continue;
|
||||
std::string p = pit->second;
|
||||
std::transform(p.begin(), p.end(), p.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
if (p != lowerMountPath) continue;
|
||||
int score = 0;
|
||||
if (!data.skin1.empty()) {
|
||||
std::string p1 = modelDir + data.skin1 + ".blp";
|
||||
score += assetManager->fileExists(p1) ? 30 : 3;
|
||||
}
|
||||
if (!data.skin2.empty()) {
|
||||
std::string p2 = modelDir + data.skin2 + ".blp";
|
||||
score += assetManager->fileExists(p2) ? 20 : 2;
|
||||
}
|
||||
if (!data.skin3.empty()) {
|
||||
std::string p3 = modelDir + data.skin3 + ".blp";
|
||||
score += assetManager->fileExists(p3) ? 10 : 1;
|
||||
}
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
dispData = data;
|
||||
haveDisplayData = true;
|
||||
}
|
||||
}
|
||||
if (haveDisplayData) {
|
||||
LOG_INFO("Recovered mount display data by model path for displayId=", mountDisplayId,
|
||||
" skin1='", dispData.skin1, "' skin2='", dispData.skin2,
|
||||
"' skin3='", dispData.skin3, "'");
|
||||
}
|
||||
}
|
||||
if (haveDisplayData) {
|
||||
// If this displayId has no skins, try to find another displayId for the same model with skins.
|
||||
if (dispData.skin1.empty() && dispData.skin2.empty() && dispData.skin3.empty()) {
|
||||
uint32_t modelId = dispData.modelId;
|
||||
uint32_t sourceModelId = dispData.modelId;
|
||||
int bestScore = -1;
|
||||
for (const auto& [dispId, data] : displayDataMap_) {
|
||||
if (data.modelId != modelId) continue;
|
||||
if (data.modelId != sourceModelId) continue;
|
||||
int score = 0;
|
||||
if (!data.skin1.empty()) score += 3;
|
||||
if (!data.skin2.empty()) score += 2;
|
||||
if (!data.skin3.empty()) score += 1;
|
||||
if (!data.skin1.empty()) {
|
||||
std::string p = modelDir + data.skin1 + ".blp";
|
||||
score += assetManager->fileExists(p) ? 30 : 3;
|
||||
}
|
||||
if (!data.skin2.empty()) {
|
||||
std::string p = modelDir + data.skin2 + ".blp";
|
||||
score += assetManager->fileExists(p) ? 20 : 2;
|
||||
}
|
||||
if (!data.skin3.empty()) {
|
||||
std::string p = modelDir + data.skin3 + ".blp";
|
||||
score += assetManager->fileExists(p) ? 10 : 1;
|
||||
}
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
dispData = data;
|
||||
}
|
||||
}
|
||||
LOG_INFO("Mount skin fallback for displayId=", mountDisplayId,
|
||||
" modelId=", modelId, " skin1='", dispData.skin1,
|
||||
" modelId=", sourceModelId, " skin1='", dispData.skin1,
|
||||
"' skin2='", dispData.skin2, "' skin3='", dispData.skin3, "'");
|
||||
}
|
||||
const auto* md = charRenderer->getModelData(modelId);
|
||||
if (md) {
|
||||
std::string modelDir;
|
||||
size_t lastSlash = m2Path.find_last_of("\\/");
|
||||
if (lastSlash != std::string::npos) {
|
||||
modelDir = m2Path.substr(0, lastSlash + 1);
|
||||
}
|
||||
int replaced = 0;
|
||||
for (size_t ti = 0; ti < md->textures.size(); ti++) {
|
||||
const auto& tex = md->textures[ti];
|
||||
|
|
@ -3231,6 +3292,7 @@ void Application::processPendingMount() {
|
|||
if (skinTex != 0) {
|
||||
charRenderer->setModelTexture(modelId, 0, skinTex);
|
||||
LOG_INFO("Forced mount skin1 texture on slot 0: ", texPath);
|
||||
replaced++;
|
||||
}
|
||||
} else if (replaced == 0 && !md->textures.empty() && !md->textures[0].filename.empty()) {
|
||||
// Last-resort: use the model's first texture filename if it exists.
|
||||
|
|
@ -3238,6 +3300,27 @@ void Application::processPendingMount() {
|
|||
if (texId != 0) {
|
||||
charRenderer->setModelTexture(modelId, 0, texId);
|
||||
LOG_INFO("Forced mount model texture on slot 0: ", md->textures[0].filename);
|
||||
replaced++;
|
||||
}
|
||||
}
|
||||
|
||||
// Final taxi mount fallback for gryphon/wyvern models when display tables are sparse.
|
||||
if (replaced == 0 && !md->textures.empty()) {
|
||||
std::string lowerMountPath = m2Path;
|
||||
std::transform(lowerMountPath.begin(), lowerMountPath.end(), lowerMountPath.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
if (lowerMountPath.find("creature\\gryphon\\gryphon.m2") != std::string::npos) {
|
||||
GLuint texId = charRenderer->loadTexture("Creature\\Gryphon\\Gryphon.blp");
|
||||
if (texId != 0) {
|
||||
charRenderer->setModelTexture(modelId, 0, texId);
|
||||
LOG_INFO("Forced canonical gryphon texture on slot 0");
|
||||
}
|
||||
} else if (lowerMountPath.find("creature\\wyvern\\wyvern.m2") != std::string::npos) {
|
||||
GLuint texId = charRenderer->loadTexture("Creature\\Wyvern\\Wyvern.blp");
|
||||
if (texId != 0) {
|
||||
charRenderer->setModelTexture(modelId, 0, texId);
|
||||
LOG_INFO("Forced canonical wyvern texture on slot 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,49 +210,58 @@ void GameHandler::update(float deltaTime) {
|
|||
// Detect taxi flight landing: UNIT_FLAG_TAXI_FLIGHT (0x00000100) cleared
|
||||
if (onTaxiFlight_) {
|
||||
updateClientTaxi(deltaTime);
|
||||
if (!taxiMountActive_ && !taxiClientActive_ && taxiClientPath_.empty()) {
|
||||
onTaxiFlight_ = false;
|
||||
LOG_INFO("Cleared stale taxi state in update");
|
||||
}
|
||||
auto playerEntity = entityManager.getEntity(playerGuid);
|
||||
if (playerEntity && playerEntity->getType() == ObjectType::UNIT) {
|
||||
auto unit = std::static_pointer_cast<Unit>(playerEntity);
|
||||
if ((unit->getUnitFlags() & 0x00000100) == 0) {
|
||||
onTaxiFlight_ = false;
|
||||
taxiLandingCooldown_ = 2.0f; // 2 second cooldown to prevent re-entering
|
||||
if (taxiMountActive_ && mountCallback_) {
|
||||
mountCallback_(0);
|
||||
}
|
||||
taxiMountActive_ = false;
|
||||
taxiMountDisplayId_ = 0;
|
||||
currentMountDisplayId_ = 0;
|
||||
taxiClientActive_ = false;
|
||||
taxiClientPath_.clear();
|
||||
taxiRecoverPending_ = false;
|
||||
movementInfo.flags = 0;
|
||||
movementInfo.flags2 = 0;
|
||||
if (socket) {
|
||||
sendMovement(Opcode::CMSG_MOVE_STOP);
|
||||
sendMovement(Opcode::CMSG_MOVE_HEARTBEAT);
|
||||
}
|
||||
LOG_INFO("Taxi flight landed");
|
||||
auto unit = std::dynamic_pointer_cast<Unit>(playerEntity);
|
||||
if (unit &&
|
||||
(unit->getUnitFlags() & 0x00000100) == 0 &&
|
||||
!taxiClientActive_ &&
|
||||
!taxiActivatePending_) {
|
||||
onTaxiFlight_ = false;
|
||||
taxiLandingCooldown_ = 2.0f; // 2 second cooldown to prevent re-entering
|
||||
if (taxiMountActive_ && mountCallback_) {
|
||||
mountCallback_(0);
|
||||
}
|
||||
taxiMountActive_ = false;
|
||||
taxiMountDisplayId_ = 0;
|
||||
currentMountDisplayId_ = 0;
|
||||
taxiClientActive_ = false;
|
||||
taxiClientPath_.clear();
|
||||
taxiRecoverPending_ = false;
|
||||
movementInfo.flags = 0;
|
||||
movementInfo.flags2 = 0;
|
||||
if (socket) {
|
||||
sendMovement(Opcode::CMSG_MOVE_STOP);
|
||||
sendMovement(Opcode::CMSG_MOVE_HEARTBEAT);
|
||||
}
|
||||
LOG_INFO("Taxi flight landed");
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: if taxi flight ended but mount is still active, force dismount.
|
||||
// Guard against transient taxi-state flicker.
|
||||
if (!onTaxiFlight_ && taxiMountActive_) {
|
||||
if (mountCallback_) mountCallback_(0);
|
||||
taxiMountActive_ = false;
|
||||
taxiMountDisplayId_ = 0;
|
||||
currentMountDisplayId_ = 0;
|
||||
movementInfo.flags = 0;
|
||||
movementInfo.flags2 = 0;
|
||||
if (socket) {
|
||||
sendMovement(Opcode::CMSG_MOVE_STOP);
|
||||
sendMovement(Opcode::CMSG_MOVE_HEARTBEAT);
|
||||
bool serverStillTaxi = false;
|
||||
auto playerEntity = entityManager.getEntity(playerGuid);
|
||||
auto playerUnit = std::dynamic_pointer_cast<Unit>(playerEntity);
|
||||
if (playerUnit) {
|
||||
serverStillTaxi = (playerUnit->getUnitFlags() & 0x00000100) != 0;
|
||||
}
|
||||
|
||||
if (serverStillTaxi || taxiClientActive_ || taxiActivatePending_) {
|
||||
onTaxiFlight_ = true;
|
||||
} else {
|
||||
if (mountCallback_) mountCallback_(0);
|
||||
taxiMountActive_ = false;
|
||||
taxiMountDisplayId_ = 0;
|
||||
currentMountDisplayId_ = 0;
|
||||
movementInfo.flags = 0;
|
||||
movementInfo.flags2 = 0;
|
||||
if (socket) {
|
||||
sendMovement(Opcode::CMSG_MOVE_STOP);
|
||||
sendMovement(Opcode::CMSG_MOVE_HEARTBEAT);
|
||||
}
|
||||
LOG_INFO("Taxi dismount cleanup");
|
||||
}
|
||||
LOG_INFO("Taxi dismount cleanup");
|
||||
}
|
||||
|
||||
if (taxiRecoverPending_ && state == WorldState::IN_WORLD) {
|
||||
|
|
@ -1696,16 +1705,8 @@ void GameHandler::sendMovement(Opcode opcode) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Block movement during taxi flight
|
||||
if (onTaxiFlight_) {
|
||||
// If taxi visuals are already gone, clear taxi state to avoid stuck movement.
|
||||
if (!taxiMountActive_ && !taxiClientActive_ && taxiClientPath_.empty()) {
|
||||
onTaxiFlight_ = false;
|
||||
LOG_INFO("Cleared stale taxi state in sendMovement");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Block manual movement while taxi is active/mounted, but still allow heartbeat packets.
|
||||
if ((onTaxiFlight_ || taxiMountActive_) && opcode != Opcode::CMSG_MOVE_HEARTBEAT) return;
|
||||
if (resurrectPending_) return;
|
||||
|
||||
// Use real millisecond timestamp (server validates for anti-cheat)
|
||||
|
|
@ -2346,6 +2347,16 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
if (block.guid == playerGuid) {
|
||||
if (block.hasMovement && block.runSpeed > 0.1f && block.runSpeed < 100.0f) {
|
||||
serverRunSpeed_ = block.runSpeed;
|
||||
// Some server dismount paths update run speed without updating mount display field.
|
||||
if (!onTaxiFlight_ && !taxiMountActive_ &&
|
||||
currentMountDisplayId_ != 0 && block.runSpeed <= 8.5f) {
|
||||
LOG_INFO("Auto-clearing mount from movement speed update: speed=", block.runSpeed,
|
||||
" displayId=", currentMountDisplayId_);
|
||||
currentMountDisplayId_ = 0;
|
||||
if (mountCallback_) {
|
||||
mountCallback_(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
lastPlayerFields_[key] = val;
|
||||
|
|
@ -4158,6 +4169,17 @@ void GameHandler::handleForceRunSpeedChange(network::Packet& packet) {
|
|||
}
|
||||
|
||||
serverRunSpeed_ = newSpeed;
|
||||
|
||||
// Server can auto-dismount (e.g. entering no-mount areas) and only send a speed change.
|
||||
// Keep client mount visuals in sync with server-authoritative movement speed.
|
||||
if (!onTaxiFlight_ && !taxiMountActive_ && currentMountDisplayId_ != 0 && newSpeed <= 8.5f) {
|
||||
LOG_INFO("Auto-clearing mount from speed change: speed=", newSpeed,
|
||||
" displayId=", currentMountDisplayId_);
|
||||
currentMountDisplayId_ = 0;
|
||||
if (mountCallback_) {
|
||||
mountCallback_(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -6057,9 +6079,11 @@ void GameHandler::applyTaxiMountForCurrentNode() {
|
|||
}
|
||||
uint32_t mountId = isAlliance ? it->second.mountDisplayIdAlliance
|
||||
: it->second.mountDisplayIdHorde;
|
||||
if (mountId == 541) mountId = 0; // Placeholder/invalid in some DBC sets
|
||||
if (mountId == 0) {
|
||||
mountId = isAlliance ? it->second.mountDisplayIdHorde
|
||||
: it->second.mountDisplayIdAlliance;
|
||||
if (mountId == 541) mountId = 0;
|
||||
}
|
||||
if (mountId == 0) {
|
||||
auto& app = core::Application::getInstance();
|
||||
|
|
@ -6076,7 +6100,17 @@ void GameHandler::applyTaxiMountForCurrentNode() {
|
|||
if (it->second.mountDisplayIdAlliance != 0) mountId = it->second.mountDisplayIdAlliance;
|
||||
else if (it->second.mountDisplayIdHorde != 0) mountId = it->second.mountDisplayIdHorde;
|
||||
}
|
||||
if (mountId == 0 || mountId == 541) {
|
||||
if (mountId == 0) {
|
||||
// 3.3.5a fallback display IDs (real CreatureDisplayInfo entries).
|
||||
// Alliance taxi gryphons commonly use 1210-1213.
|
||||
// Horde taxi wyverns commonly use 1310-1312.
|
||||
static const uint32_t kAllianceTaxiDisplays[] = {1210u, 1211u, 1212u, 1213u};
|
||||
static const uint32_t kHordeTaxiDisplays[] = {1310u, 1311u, 1312u};
|
||||
mountId = isAlliance ? kAllianceTaxiDisplays[0] : kHordeTaxiDisplays[0];
|
||||
}
|
||||
|
||||
// Last resort legacy fallback.
|
||||
if (mountId == 0) {
|
||||
mountId = isAlliance ? 30412u : 30413u;
|
||||
}
|
||||
if (mountId != 0) {
|
||||
|
|
@ -6122,34 +6156,58 @@ void GameHandler::startClientTaxiPath(const std::vector<uint32_t>& pathNodes) {
|
|||
}
|
||||
}
|
||||
|
||||
if (taxiClientPath_.size() < 2) {
|
||||
// Fallback: use TaxiNodes directly when TaxiPathNode spline data is missing.
|
||||
taxiClientPath_.clear();
|
||||
for (uint32_t nodeId : pathNodes) {
|
||||
auto nodeIt = taxiNodes_.find(nodeId);
|
||||
if (nodeIt == taxiNodes_.end()) continue;
|
||||
glm::vec3 serverPos(nodeIt->second.x, nodeIt->second.y, nodeIt->second.z);
|
||||
taxiClientPath_.push_back(core::coords::serverToCanonical(serverPos));
|
||||
}
|
||||
}
|
||||
|
||||
if (taxiClientPath_.size() < 2) {
|
||||
LOG_WARNING("Taxi path too short: ", taxiClientPath_.size(), " waypoints");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set initial orientation to face the first flight segment
|
||||
if (!entityManager.hasEntity(playerGuid)) return;
|
||||
// Set initial orientation to face the first non-degenerate flight segment.
|
||||
glm::vec3 start = taxiClientPath_[0];
|
||||
glm::vec3 dir(0.0f);
|
||||
float dirLen = 0.0f;
|
||||
for (size_t i = 1; i < taxiClientPath_.size(); i++) {
|
||||
dir = taxiClientPath_[i] - start;
|
||||
dirLen = glm::length(dir);
|
||||
if (dirLen >= 0.001f) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float initialOrientation = movementInfo.orientation;
|
||||
float initialRenderYaw = movementInfo.orientation;
|
||||
float initialPitch = 0.0f;
|
||||
float initialRoll = 0.0f;
|
||||
if (dirLen >= 0.001f) {
|
||||
initialOrientation = std::atan2(dir.y, dir.x);
|
||||
glm::vec3 renderDir = core::coords::canonicalToRender(dir);
|
||||
initialRenderYaw = std::atan2(renderDir.y, renderDir.x);
|
||||
glm::vec3 dirNorm = dir / dirLen;
|
||||
initialPitch = std::asin(std::clamp(dirNorm.z, -1.0f, 1.0f));
|
||||
}
|
||||
|
||||
movementInfo.x = start.x;
|
||||
movementInfo.y = start.y;
|
||||
movementInfo.z = start.z;
|
||||
movementInfo.orientation = initialOrientation;
|
||||
|
||||
auto playerEntity = entityManager.getEntity(playerGuid);
|
||||
if (playerEntity) {
|
||||
glm::vec3 start = taxiClientPath_[0];
|
||||
glm::vec3 end = taxiClientPath_[1];
|
||||
glm::vec3 dir = end - start;
|
||||
float dirLen = glm::length(dir);
|
||||
if (dirLen < 0.001f) return;
|
||||
float initialOrientation = std::atan2(dir.y, dir.x) - 1.57079632679f;
|
||||
|
||||
// Calculate initial pitch from altitude change
|
||||
glm::vec3 dirNorm = dir / dirLen;
|
||||
float initialPitch = std::asin(std::clamp(dirNorm.z, -1.0f, 1.0f));
|
||||
float initialRoll = 0.0f; // No initial banking
|
||||
|
||||
playerEntity->setPosition(start.x, start.y, start.z, initialOrientation);
|
||||
movementInfo.orientation = initialOrientation;
|
||||
}
|
||||
|
||||
// Update mount rotation immediately with pitch and roll
|
||||
if (taxiOrientationCallback_) {
|
||||
taxiOrientationCallback_(initialOrientation, initialPitch, initialRoll);
|
||||
}
|
||||
if (taxiOrientationCallback_) {
|
||||
taxiOrientationCallback_(initialRenderYaw, initialPitch, initialRoll);
|
||||
}
|
||||
|
||||
LOG_INFO("Taxi flight started with ", taxiClientPath_.size(), " spline waypoints");
|
||||
|
|
@ -6158,31 +6216,9 @@ void GameHandler::startClientTaxiPath(const std::vector<uint32_t>& pathNodes) {
|
|||
|
||||
void GameHandler::updateClientTaxi(float deltaTime) {
|
||||
if (!taxiClientActive_ || taxiClientPath_.size() < 2) return;
|
||||
if (!entityManager.hasEntity(playerGuid)) return;
|
||||
auto playerEntity = entityManager.getEntity(playerGuid);
|
||||
if (!playerEntity) return;
|
||||
|
||||
if (taxiClientIndex_ + 1 >= taxiClientPath_.size()) {
|
||||
taxiClientActive_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 start = taxiClientPath_[taxiClientIndex_];
|
||||
glm::vec3 end = taxiClientPath_[taxiClientIndex_ + 1];
|
||||
glm::vec3 dir = end - start;
|
||||
float segmentLen = glm::length(dir);
|
||||
if (segmentLen < 0.01f) {
|
||||
taxiClientIndex_++;
|
||||
taxiClientSegmentProgress_ = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
taxiClientSegmentProgress_ += taxiClientSpeed_ * deltaTime;
|
||||
float t = taxiClientSegmentProgress_ / segmentLen;
|
||||
if (t >= 1.0f) {
|
||||
taxiClientIndex_++;
|
||||
taxiClientSegmentProgress_ = 0.0f;
|
||||
if (taxiClientIndex_ + 1 >= taxiClientPath_.size()) {
|
||||
auto finishTaxiFlight = [&]() {
|
||||
taxiClientActive_ = false;
|
||||
onTaxiFlight_ = false;
|
||||
taxiLandingCooldown_ = 2.0f; // 2 second cooldown to prevent re-entering
|
||||
|
|
@ -6201,10 +6237,50 @@ void GameHandler::updateClientTaxi(float deltaTime) {
|
|||
sendMovement(Opcode::CMSG_MOVE_HEARTBEAT);
|
||||
}
|
||||
LOG_INFO("Taxi flight landed (client path)");
|
||||
}
|
||||
};
|
||||
|
||||
if (taxiClientIndex_ + 1 >= taxiClientPath_.size()) {
|
||||
finishTaxiFlight();
|
||||
return;
|
||||
}
|
||||
|
||||
float remainingDistance = taxiClientSegmentProgress_ + (taxiClientSpeed_ * deltaTime);
|
||||
glm::vec3 start(0.0f);
|
||||
glm::vec3 end(0.0f);
|
||||
glm::vec3 dir(0.0f);
|
||||
float segmentLen = 0.0f;
|
||||
float t = 0.0f;
|
||||
|
||||
// Consume as many tiny/finished segments as needed this frame so taxi doesn't stall
|
||||
// on dense/degenerate node clusters near takeoff/landing.
|
||||
while (true) {
|
||||
if (taxiClientIndex_ + 1 >= taxiClientPath_.size()) {
|
||||
finishTaxiFlight();
|
||||
return;
|
||||
}
|
||||
|
||||
start = taxiClientPath_[taxiClientIndex_];
|
||||
end = taxiClientPath_[taxiClientIndex_ + 1];
|
||||
dir = end - start;
|
||||
segmentLen = glm::length(dir);
|
||||
|
||||
if (segmentLen < 0.01f) {
|
||||
taxiClientIndex_++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (remainingDistance >= segmentLen) {
|
||||
remainingDistance -= segmentLen;
|
||||
taxiClientIndex_++;
|
||||
taxiClientSegmentProgress_ = 0.0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
taxiClientSegmentProgress_ = remainingDistance;
|
||||
t = taxiClientSegmentProgress_ / segmentLen;
|
||||
break;
|
||||
}
|
||||
|
||||
// Use Catmull-Rom spline for smooth interpolation between waypoints
|
||||
// Get surrounding points for spline curve
|
||||
glm::vec3 p0 = (taxiClientIndex_ > 0) ? taxiClientPath_[taxiClientIndex_ - 1] : start;
|
||||
|
|
@ -6240,7 +6316,7 @@ void GameHandler::updateClientTaxi(float deltaTime) {
|
|||
}
|
||||
|
||||
// Calculate yaw from horizontal direction
|
||||
float targetOrientation = std::atan2(tangent.y, tangent.x) - 1.57079632679f;
|
||||
float targetOrientation = std::atan2(tangent.y, tangent.x);
|
||||
|
||||
// Calculate pitch from vertical component (altitude change)
|
||||
glm::vec3 tangentNorm = tangent / std::max(tangentLen, 0.0001f);
|
||||
|
|
@ -6259,15 +6335,20 @@ void GameHandler::updateClientTaxi(float deltaTime) {
|
|||
// Smooth rotation transition (lerp towards target)
|
||||
float smoothOrientation = currentOrientation + orientDiff * std::min(1.0f, deltaTime * 3.0f);
|
||||
|
||||
playerEntity->setPosition(nextPos.x, nextPos.y, nextPos.z, smoothOrientation);
|
||||
if (playerEntity) {
|
||||
playerEntity->setPosition(nextPos.x, nextPos.y, nextPos.z, smoothOrientation);
|
||||
}
|
||||
movementInfo.x = nextPos.x;
|
||||
movementInfo.y = nextPos.y;
|
||||
movementInfo.z = nextPos.z;
|
||||
movementInfo.orientation = smoothOrientation;
|
||||
|
||||
// Update mount rotation with yaw, pitch, and roll for realistic flight
|
||||
// Update mount rotation with yaw/pitch/roll. Use render-space tangent yaw to
|
||||
// avoid canonical<->render convention mismatches.
|
||||
if (taxiOrientationCallback_) {
|
||||
taxiOrientationCallback_(smoothOrientation, pitch, roll);
|
||||
glm::vec3 renderTangent = core::coords::canonicalToRender(tangent);
|
||||
float renderYaw = std::atan2(renderTangent.y, renderTangent.x);
|
||||
taxiOrientationCallback_(renderYaw, pitch, roll);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6279,13 +6360,19 @@ void GameHandler::handleActivateTaxiReply(network::Packet& packet) {
|
|||
}
|
||||
|
||||
if (data.result == 0) {
|
||||
// Some cores can emit duplicate success replies (e.g. basic + express activate).
|
||||
// Ignore repeats once taxi is already active and no activation is pending.
|
||||
if (onTaxiFlight_ && !taxiActivatePending_) {
|
||||
return;
|
||||
}
|
||||
onTaxiFlight_ = true;
|
||||
taxiWindowOpen_ = false;
|
||||
taxiMountActive_ = false;
|
||||
taxiMountDisplayId_ = 0;
|
||||
taxiActivatePending_ = false;
|
||||
taxiActivateTimer_ = 0.0f;
|
||||
applyTaxiMountForCurrentNode();
|
||||
if (socket) {
|
||||
sendMovement(Opcode::CMSG_MOVE_HEARTBEAT);
|
||||
}
|
||||
LOG_INFO("Taxi flight started!");
|
||||
} else {
|
||||
LOG_WARNING("Taxi activation failed, result=", data.result);
|
||||
|
|
@ -6438,6 +6525,9 @@ void GameHandler::activateTaxi(uint32_t destNodeId) {
|
|||
onTaxiFlight_ = true;
|
||||
applyTaxiMountForCurrentNode();
|
||||
}
|
||||
if (socket) {
|
||||
sendMovement(Opcode::CMSG_MOVE_HEARTBEAT);
|
||||
}
|
||||
|
||||
// Trigger terrain precache immediately (non-blocking).
|
||||
if (taxiPrecacheCallback_) {
|
||||
|
|
|
|||
|
|
@ -26,10 +26,11 @@ bool AssetManager::initialize(const std::string& dataPath_) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Set dynamic file cache budget based on available RAM
|
||||
// Set dynamic file cache budget based on available RAM.
|
||||
// Bias toward MPQ decompressed-file caching to minimize runtime read/decompress stalls.
|
||||
auto& memMonitor = core::MemoryMonitor::getInstance();
|
||||
size_t recommendedBudget = memMonitor.getRecommendedCacheBudget();
|
||||
fileCacheBudget = recommendedBudget / 2; // Split budget: half for file cache, half for other caches
|
||||
fileCacheBudget = (recommendedBudget * 3) / 4; // 75% of global cache budget
|
||||
|
||||
initialized = true;
|
||||
LOG_INFO("Asset manager initialized (dynamic file cache: ",
|
||||
|
|
@ -152,20 +153,26 @@ std::vector<uint8_t> AssetManager::readFile(const std::string& path) const {
|
|||
}
|
||||
|
||||
std::string normalized = normalizePath(path);
|
||||
std::lock_guard<std::mutex> lock(readMutex);
|
||||
|
||||
// Check cache first
|
||||
auto it = fileCache.find(normalized);
|
||||
if (it != fileCache.end()) {
|
||||
// Cache hit - update access time and return cached data
|
||||
it->second.lastAccessTime = ++fileCacheAccessCounter;
|
||||
fileCacheHits++;
|
||||
return it->second.data;
|
||||
{
|
||||
std::lock_guard<std::mutex> cacheLock(cacheMutex);
|
||||
// Check cache first
|
||||
auto it = fileCache.find(normalized);
|
||||
if (it != fileCache.end()) {
|
||||
// Cache hit - update access time and return cached data
|
||||
it->second.lastAccessTime = ++fileCacheAccessCounter;
|
||||
fileCacheHits++;
|
||||
return it->second.data;
|
||||
}
|
||||
fileCacheMisses++;
|
||||
}
|
||||
|
||||
// Cache miss - decompress from MPQ
|
||||
fileCacheMisses++;
|
||||
std::vector<uint8_t> data = mpqManager.readFile(normalized);
|
||||
// Cache miss - decompress from MPQ.
|
||||
// Keep MPQ reads serialized, but do not block cache-hit readers on this mutex.
|
||||
std::vector<uint8_t> data;
|
||||
{
|
||||
std::lock_guard<std::mutex> readLock(readMutex);
|
||||
data = mpqManager.readFile(normalized);
|
||||
}
|
||||
if (data.empty()) {
|
||||
return data; // File not found
|
||||
}
|
||||
|
|
@ -173,6 +180,7 @@ std::vector<uint8_t> AssetManager::readFile(const std::string& path) const {
|
|||
// Add to cache if within budget
|
||||
size_t fileSize = data.size();
|
||||
if (fileSize > 0 && fileSize < fileCacheBudget / 2) { // Don't cache files > 50% of budget (very aggressive)
|
||||
std::lock_guard<std::mutex> cacheLock(cacheMutex);
|
||||
// Evict old entries if needed (LRU)
|
||||
while (fileCacheTotalBytes + fileSize > fileCacheBudget && !fileCache.empty()) {
|
||||
// Find least recently used entry
|
||||
|
|
@ -198,7 +206,7 @@ std::vector<uint8_t> AssetManager::readFile(const std::string& path) const {
|
|||
}
|
||||
|
||||
void AssetManager::clearCache() {
|
||||
std::lock_guard<std::mutex> lock(readMutex);
|
||||
std::scoped_lock lock(readMutex, cacheMutex);
|
||||
dbcCache.clear();
|
||||
fileCache.clear();
|
||||
fileCacheTotalBytes = 0;
|
||||
|
|
@ -211,6 +219,9 @@ std::string AssetManager::normalizePath(const std::string& path) const {
|
|||
|
||||
// Convert forward slashes to backslashes (WoW uses backslashes)
|
||||
std::replace(normalized.begin(), normalized.end(), '/', '\\');
|
||||
// Lowercase for case-insensitive cache keys (improves hit rate across mixed-case callers).
|
||||
std::transform(normalized.begin(), normalized.end(), normalized.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,17 +112,12 @@ void CameraController::update(float deltaTime) {
|
|||
return;
|
||||
}
|
||||
|
||||
// During taxi flights, skip input/movement logic but still position camera
|
||||
// During taxi flights, skip movement logic but keep camera orbit/zoom controls.
|
||||
if (externalFollow_) {
|
||||
// Mouse look (right mouse button)
|
||||
if (rightMouseDown) {
|
||||
int mouseDX, mouseDY;
|
||||
SDL_GetRelativeMouseState(&mouseDX, &mouseDY);
|
||||
yaw -= mouseDX * mouseSensitivity;
|
||||
pitch -= mouseDY * mouseSensitivity;
|
||||
pitch = glm::clamp(pitch, -89.0f, 89.0f);
|
||||
camera->setRotation(yaw, pitch);
|
||||
}
|
||||
camera->setRotation(yaw, pitch);
|
||||
float zoomLerp = 1.0f - std::exp(-ZOOM_SMOOTH_SPEED * deltaTime);
|
||||
currentDistance += (userTargetDistance - currentDistance) * zoomLerp;
|
||||
collisionDistance = currentDistance;
|
||||
|
||||
// Position camera behind character during taxi
|
||||
if (thirdPerson && followTarget) {
|
||||
|
|
|
|||
|
|
@ -125,12 +125,9 @@ void Celestial::shutdown() {
|
|||
void Celestial::render(const Camera& camera, float timeOfDay,
|
||||
const glm::vec3* sunDir, const glm::vec3* sunColor, float gameTime) {
|
||||
if (!renderingEnabled || vao == 0 || !celestialShader) {
|
||||
LOG_WARNING("Celestial render blocked: enabled=", renderingEnabled, " vao=", vao, " shader=", (celestialShader ? "ok" : "null"));
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("Celestial render: timeOfDay=", timeOfDay, " gameTime=", gameTime);
|
||||
|
||||
// Update moon phases from game time if available (deterministic)
|
||||
if (gameTime >= 0.0f) {
|
||||
updatePhasesFromGameTime(gameTime);
|
||||
|
|
@ -166,22 +163,16 @@ void Celestial::renderSun(const Camera& camera, float timeOfDay,
|
|||
const glm::vec3* sunDir, const glm::vec3* sunColor) {
|
||||
// Sun visible from 5:00 to 19:00
|
||||
if (timeOfDay < 5.0f || timeOfDay >= 19.0f) {
|
||||
LOG_INFO("Sun not visible: timeOfDay=", timeOfDay, " (visible 5:00-19:00)");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("Rendering sun: timeOfDay=", timeOfDay, " sunDir=", (sunDir ? "yes" : "no"), " sunColor=", (sunColor ? "yes" : "no"));
|
||||
|
||||
celestialShader->use();
|
||||
|
||||
// TESTING: Try X-up (final axis test)
|
||||
glm::vec3 dir = glm::normalize(glm::vec3(1.0f, 0.0f, 0.0f)); // X-up test
|
||||
LOG_INFO("Sun direction (TESTING X-UP): dir=(", dir.x, ",", dir.y, ",", dir.z, ")");
|
||||
glm::vec3 dir = sunDir ? glm::normalize(*sunDir) : glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
|
||||
// Place sun on sky sphere at fixed distance
|
||||
const float sunDistance = 800.0f;
|
||||
glm::vec3 sunPos = dir * sunDistance;
|
||||
LOG_INFO("Sun position: dir * ", sunDistance, " = (", sunPos.x, ",", sunPos.y, ",", sunPos.z, ")");
|
||||
|
||||
// Create model matrix
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
|
|
|
|||
|
|
@ -957,13 +957,9 @@ void Renderer::updateCharacterAnimation() {
|
|||
|
||||
// Sync mount instance position and rotation
|
||||
float mountBob = 0.0f;
|
||||
float mountYawRad = glm::radians(characterYaw);
|
||||
if (mountInstanceId_ > 0) {
|
||||
characterRenderer->setInstancePosition(mountInstanceId_, characterPosition);
|
||||
float yawRad = glm::radians(characterYaw);
|
||||
if (taxiFlight_) {
|
||||
// Taxi mounts commonly use a different model-forward axis than player rigs.
|
||||
yawRad += 1.57079632679f;
|
||||
}
|
||||
|
||||
// Procedural lean into turns (ground mounts only, optional enhancement)
|
||||
if (!taxiFlight_ && moving && lastDeltaTime_ > 0.0f) {
|
||||
|
|
@ -982,7 +978,7 @@ void Renderer::updateCharacterAnimation() {
|
|||
}
|
||||
|
||||
// Apply pitch (up/down), roll (banking), and yaw for realistic flight
|
||||
characterRenderer->setInstanceRotation(mountInstanceId_, glm::vec3(mountPitch_, mountRoll_, yawRad));
|
||||
characterRenderer->setInstanceRotation(mountInstanceId_, glm::vec3(mountPitch_, mountRoll_, mountYawRad));
|
||||
|
||||
// Drive mount model animation: idle when still, run when moving
|
||||
auto pickMountAnim = [&](std::initializer_list<uint32_t> candidates, uint32_t fallback) -> uint32_t {
|
||||
|
|
@ -1172,7 +1168,42 @@ void Renderer::updateCharacterAnimation() {
|
|||
}
|
||||
}
|
||||
|
||||
// Use mount's attachment point for proper bone-driven rider positioning
|
||||
// Use mount's attachment point for proper bone-driven rider positioning.
|
||||
if (taxiFlight_) {
|
||||
glm::mat4 mountSeatTransform(1.0f);
|
||||
bool haveSeat = false;
|
||||
static constexpr uint32_t kTaxiSeatAttachmentId = 0; // deterministic rider seat
|
||||
if (mountSeatAttachmentId_ == -1) {
|
||||
mountSeatAttachmentId_ = static_cast<int>(kTaxiSeatAttachmentId);
|
||||
}
|
||||
if (mountSeatAttachmentId_ >= 0) {
|
||||
haveSeat = characterRenderer->getAttachmentTransform(
|
||||
mountInstanceId_, static_cast<uint32_t>(mountSeatAttachmentId_), mountSeatTransform);
|
||||
}
|
||||
if (!haveSeat) {
|
||||
mountSeatAttachmentId_ = -2;
|
||||
}
|
||||
|
||||
if (haveSeat) {
|
||||
glm::vec3 targetRiderPos = glm::vec3(mountSeatTransform[3]) + glm::vec3(0.0f, 0.0f, 0.02f);
|
||||
// Taxi passengers should be rigidly parented to mount attachment transforms.
|
||||
// Smoothing here introduces visible seat lag/drift on turns.
|
||||
mountSeatSmoothingInit_ = false;
|
||||
smoothedMountSeatPos_ = targetRiderPos;
|
||||
characterRenderer->setInstancePosition(characterInstanceId, targetRiderPos);
|
||||
} else {
|
||||
mountSeatSmoothingInit_ = false;
|
||||
glm::vec3 playerPos = characterPosition + glm::vec3(0.0f, 0.0f, mountHeightOffset_ + 0.10f);
|
||||
characterRenderer->setInstancePosition(characterInstanceId, playerPos);
|
||||
}
|
||||
|
||||
float riderPitch = mountPitch_ * 0.35f;
|
||||
float riderRoll = mountRoll_ * 0.35f;
|
||||
characterRenderer->setInstanceRotation(characterInstanceId, glm::vec3(riderPitch, riderRoll, mountYawRad));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ground mounts: try a seat attachment first.
|
||||
glm::mat4 mountSeatTransform;
|
||||
bool haveSeat = false;
|
||||
if (mountSeatAttachmentId_ >= 0) {
|
||||
|
|
|
|||
|
|
@ -146,14 +146,11 @@ void SkySystem::render(const Camera& camera, const SkyParams& params) {
|
|||
}
|
||||
|
||||
glm::vec3 SkySystem::getSunPosition(const SkyParams& params) const {
|
||||
// TESTING: X-up test
|
||||
glm::vec3 dir = glm::vec3(1.0f, 0.0f, 0.0f); // X-up
|
||||
glm::vec3 pos = dir * 800.0f;
|
||||
|
||||
static int counter = 0;
|
||||
if (counter++ % 100 == 0) {
|
||||
LOG_INFO("Flare TEST X-UP dir=(", dir.x, ",", dir.y, ",", dir.z, ") pos=(", pos.x, ",", pos.y, ",", pos.z, ")");
|
||||
glm::vec3 dir = glm::normalize(params.directionalDir);
|
||||
if (glm::length(dir) < 0.0001f) {
|
||||
dir = glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
glm::vec3 pos = dir * 800.0f;
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,9 +115,10 @@ bool TerrainManager::initialize(pipeline::AssetManager* assets, TerrainRenderer*
|
|||
return false;
|
||||
}
|
||||
|
||||
// Set dynamic tile cache budget (use other half of recommended budget)
|
||||
// Set dynamic tile cache budget.
|
||||
// Keep this lower so decompressed MPQ file cache can stay very aggressive.
|
||||
auto& memMonitor = core::MemoryMonitor::getInstance();
|
||||
tileCacheBudgetBytes_ = memMonitor.getRecommendedCacheBudget() / 2;
|
||||
tileCacheBudgetBytes_ = memMonitor.getRecommendedCacheBudget() / 4;
|
||||
LOG_INFO("Terrain tile cache budget: ", tileCacheBudgetBytes_ / (1024 * 1024), " MB (dynamic)");
|
||||
|
||||
// Start background worker pool (dynamic: scales with available cores)
|
||||
|
|
@ -222,7 +223,7 @@ bool TerrainManager::enqueueTile(int x, int y) {
|
|||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
loadQueue.push(coord);
|
||||
loadQueue.push_back(coord);
|
||||
pendingTiles[coord] = true;
|
||||
}
|
||||
queueCV.notify_all();
|
||||
|
|
@ -791,7 +792,7 @@ void TerrainManager::workerLoop() {
|
|||
|
||||
if (!loadQueue.empty()) {
|
||||
coord = loadQueue.front();
|
||||
loadQueue.pop();
|
||||
loadQueue.pop_front();
|
||||
hasWork = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1056,7 +1057,7 @@ void TerrainManager::unloadAll() {
|
|||
// Clear queues
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
while (!loadQueue.empty()) loadQueue.pop();
|
||||
while (!loadQueue.empty()) loadQueue.pop_front();
|
||||
while (!readyQueue.empty()) readyQueue.pop();
|
||||
}
|
||||
pendingTiles.clear();
|
||||
|
|
@ -1353,7 +1354,7 @@ void TerrainManager::streamTiles() {
|
|||
if (pendingTiles.find(coord) != pendingTiles.end()) continue;
|
||||
if (failedTiles.find(coord) != failedTiles.end()) continue;
|
||||
|
||||
loadQueue.push(coord);
|
||||
loadQueue.push_back(coord);
|
||||
pendingTiles[coord] = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1409,7 +1410,9 @@ void TerrainManager::precacheTiles(const std::vector<std::pair<int, int>>& tiles
|
|||
if (pendingTiles.find(coord) != pendingTiles.end()) continue;
|
||||
if (failedTiles.find(coord) != failedTiles.end()) continue;
|
||||
|
||||
loadQueue.push(coord);
|
||||
// Precache work is prioritized so taxi-route tiles are prepared before
|
||||
// opportunistic radius streaming tiles.
|
||||
loadQueue.push_front(coord);
|
||||
pendingTiles[coord] = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue