Refactor instance loading: extract initializeRenderers, fix deferred state transition

- Extract initializeRenderers() from loadTestTerrain() so WMO-only maps
  (dungeons/raids) initialize renderers directly without a dummy ADT path
- Defer setState(IN_GAME) until after processing any pending deferred world
  entry, preventing brief IN_GAME flicker on the wrong map
- Remove verbose area trigger debug logging (every-second position spam)
This commit is contained in:
Kelsi 2026-03-02 08:11:36 -08:00
parent 48eb0b70a3
commit 3c55b09a3f
4 changed files with 80 additions and 88 deletions

View file

@ -71,6 +71,12 @@ public:
*/
bool loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath);
/**
* Initialize all sub-renderers (WMO, M2, Character, terrain, water, minimap, etc.)
* without loading any ADT tile. Used by WMO-only maps (dungeons/raids/BGs).
*/
bool initializeRenderers(pipeline::AssetManager* assetManager, const std::string& mapName);
/**
* Enable/disable terrain rendering
*/

View file

@ -3430,19 +3430,14 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
// Initialize renderers if they don't exist yet (first login to a WMO-only map).
// On map change, renderers already exist from the previous map.
if (!renderer->getWMORenderer() || !renderer->getTerrainManager()) {
auto [tileX, tileY] = core::coords::canonicalToTile(spawnCanonical.x, spawnCanonical.y);
std::string dummyAdtPath = "World\\Maps\\" + mapName + "\\" + mapName + "_" +
std::to_string(tileX) + "_" + std::to_string(tileY) + ".adt";
LOG_WARNING("WMO-only: calling loadTestTerrain to create renderers");
renderer->loadTestTerrain(assetManager.get(), dummyAdtPath);
renderer->initializeRenderers(assetManager.get(), mapName);
}
// Set map name on WMO and terrain renderers
// Set map name on WMO renderer and disable terrain streaming (no ADT tiles for instances)
if (renderer->getWMORenderer()) {
renderer->getWMORenderer()->setMapName(mapName);
}
if (renderer->getTerrainManager()) {
renderer->getTerrainManager()->setMapName(mapName);
renderer->getTerrainManager()->setStreamingEnabled(false);
}
@ -3635,14 +3630,10 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
LOG_INFO("Online world terrain loading initiated");
}
// Set map name on the newly-created WMO/terrain renderers
// (loadTestTerrain creates them, so the earlier setMapName at line ~3296 was a no-op)
// Set map name on WMO renderer (initializeRenderers handles terrain/minimap/worldMap)
if (renderer->getWMORenderer()) {
renderer->getWMORenderer()->setMapName(mapName);
}
if (renderer->getTerrainManager()) {
renderer->getTerrainManager()->setMapName(mapName);
}
// Character renderer is created inside loadTestTerrain(), so spawn the
// player model now that the renderer actually exists.
@ -3873,9 +3864,6 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
// Track which map we actually loaded (used by same-map teleport check).
loadedMapId_ = mapId;
// Set game state
setState(AppState::IN_GAME);
// Clear loading flag and process any deferred world entry.
// A deferred entry occurs when SMSG_NEW_WORLD arrived during our warmup
// (e.g., an area trigger in a dungeon immediately teleporting the player out).
@ -3887,9 +3875,13 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
worldEntryMovementGraceTimer_ = 2.0f;
taxiLandingClampTimer_ = 0.0f;
lastTaxiFlight_ = false;
// Recursive call — sets loadedMapId_ to entry.mapId inside.
// Recursive call — sets loadedMapId_ and IN_GAME state for the final map.
loadOnlineWorldTerrain(entry.mapId, entry.x, entry.y, entry.z);
return; // The recursive call handles setState(IN_GAME).
}
// Only enter IN_GAME when this is the final map (no deferred entry pending).
setState(AppState::IN_GAME);
}
void Application::buildCreatureDisplayLookups() {

View file

@ -8795,33 +8795,6 @@ void GameHandler::checkAreaTriggers() {
const float py = movementInfo.y;
const float pz = movementInfo.z;
// Debug: log player position periodically to verify trigger proximity
static int debugCounter = 0;
if (++debugCounter >= 4) { // every ~1s at 0.25s interval
debugCounter = 0;
int mapTriggerCount = 0;
float closestDist = 999999.0f;
uint32_t closestId = 0;
float closestR = 0, closestBoxL = 0, closestBoxW = 0, closestBoxH = 0;
bool closestActive = false;
for (const auto& at : areaTriggers_) {
if (at.mapId != currentMapId_) continue;
mapTriggerCount++;
float dx = px - at.x, dy = py - at.y, dz = pz - at.z;
float dist = std::sqrt(dx*dx + dy*dy + dz*dz);
if (dist < closestDist) {
closestDist = dist; closestId = at.id;
closestR = at.radius; closestBoxL = at.boxLength; closestBoxW = at.boxWidth; closestBoxH = at.boxHeight;
closestActive = activeAreaTriggers_.count(at.id) > 0;
}
}
LOG_WARNING("AreaTrigger check: player=(", px, ", ", py, ", ", pz,
") map=", currentMapId_, " closest=AT", closestId,
" dist=", closestDist, " r=", closestR,
" box=(", closestBoxL, ",", closestBoxW, ",", closestBoxH,
") active=", closestActive);
}
// On first check after map transfer, just mark which triggers we're inside
// without firing them — prevents exit portal from immediately sending us back
bool suppressFirst = areaTriggerSuppressFirst_;

View file

@ -3344,13 +3344,13 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
// initPostProcess(), resizePostProcess(), shutdownPostProcess() removed —
// post-process pipeline is now handled by Vulkan (Phase 6 cleanup).
bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath) {
bool Renderer::initializeRenderers(pipeline::AssetManager* assetManager, const std::string& mapName) {
if (!assetManager) {
LOG_ERROR("Asset manager is null");
return false;
}
LOG_INFO("Loading test terrain: ", adtPath);
LOG_INFO("Initializing renderers for map: ", mapName);
// Create terrain renderer if not already created
if (!terrainRenderer) {
@ -3466,49 +3466,12 @@ bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::
}
}
// Parse tile coordinates from ADT path
// Format: World\Maps\{MapName}\{MapName}_{X}_{Y}.adt
int tileX = 32, tileY = 49; // defaults
{
// Find last path separator
size_t lastSep = adtPath.find_last_of("\\/");
if (lastSep != std::string::npos) {
std::string filename = adtPath.substr(lastSep + 1);
// Find first underscore after map name
size_t firstUnderscore = filename.find('_');
if (firstUnderscore != std::string::npos) {
size_t secondUnderscore = filename.find('_', firstUnderscore + 1);
if (secondUnderscore != std::string::npos) {
size_t dot = filename.find('.', secondUnderscore);
if (dot != std::string::npos) {
tileX = std::stoi(filename.substr(firstUnderscore + 1, secondUnderscore - firstUnderscore - 1));
tileY = std::stoi(filename.substr(secondUnderscore + 1, dot - secondUnderscore - 1));
}
}
}
// Extract map name
std::string mapName = filename.substr(0, firstUnderscore != std::string::npos ? firstUnderscore : filename.size());
terrainManager->setMapName(mapName);
if (minimap) {
minimap->setMapName(mapName);
}
if (worldMap) {
worldMap->setMapName(mapName);
}
}
}
// Set map name on sub-renderers
if (terrainManager) terrainManager->setMapName(mapName);
if (minimap) minimap->setMapName(mapName);
if (worldMap) worldMap->setMapName(mapName);
LOG_INFO("Enqueuing initial tile [", tileX, ",", tileY, "] via terrain manager");
// Enqueue the initial tile for async loading (avoids long sync stalls)
if (!terrainManager->enqueueTile(tileX, tileY)) {
LOG_ERROR("Failed to enqueue initial tile [", tileX, ",", tileY, "]");
return false;
}
terrainLoaded = true;
// Initialize music manager with asset manager
// Initialize audio managers
if (musicManager && assetManager && !cachedAssetManager) {
audio::AudioEngine::instance().setAssetManager(assetManager);
musicManager->initialize(assetManager);
@ -3569,11 +3532,69 @@ bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::
cachedAssetManager = assetManager;
}
// Snap camera to ground now that terrain is loaded
// Snap camera to ground
if (cameraController) {
cameraController->reset();
}
return true;
}
bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath) {
if (!assetManager) {
LOG_ERROR("Asset manager is null");
return false;
}
LOG_INFO("Loading test terrain: ", adtPath);
// Extract map name from ADT path for renderer initialization
std::string mapName;
{
size_t lastSep = adtPath.find_last_of("\\/");
if (lastSep != std::string::npos) {
std::string filename = adtPath.substr(lastSep + 1);
size_t firstUnderscore = filename.find('_');
mapName = filename.substr(0, firstUnderscore != std::string::npos ? firstUnderscore : filename.size());
}
}
// Initialize all sub-renderers
if (!initializeRenderers(assetManager, mapName)) {
return false;
}
// Parse tile coordinates from ADT path
// Format: World\Maps\{MapName}\{MapName}_{X}_{Y}.adt
int tileX = 32, tileY = 49; // defaults
{
size_t lastSep = adtPath.find_last_of("\\/");
if (lastSep != std::string::npos) {
std::string filename = adtPath.substr(lastSep + 1);
size_t firstUnderscore = filename.find('_');
if (firstUnderscore != std::string::npos) {
size_t secondUnderscore = filename.find('_', firstUnderscore + 1);
if (secondUnderscore != std::string::npos) {
size_t dot = filename.find('.', secondUnderscore);
if (dot != std::string::npos) {
tileX = std::stoi(filename.substr(firstUnderscore + 1, secondUnderscore - firstUnderscore - 1));
tileY = std::stoi(filename.substr(secondUnderscore + 1, dot - secondUnderscore - 1));
}
}
}
}
}
LOG_INFO("Enqueuing initial tile [", tileX, ",", tileY, "] via terrain manager");
// Enqueue the initial tile for async loading (avoids long sync stalls)
if (!terrainManager->enqueueTile(tileX, tileY)) {
LOG_ERROR("Failed to enqueue initial tile [", tileX, ",", tileY, "]");
return false;
}
terrainLoaded = true;
LOG_INFO("Test terrain loaded successfully!");
LOG_INFO(" Chunks: ", terrainRenderer->getChunkCount());
LOG_INFO(" Triangles: ", terrainRenderer->getTriangleCount());