mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Fix instance portals: WDT byte order, box trigger sizing, suppress ping-pong, WMO cache cleanup
- Fix WDT chunk magic constants to big-endian ASCII (matching ADTLoader) - Add minimum effective size for box area triggers (90 units, like sphere 45-unit radius) - Add areaTriggerSuppressFirst_ flag to prevent portal ping-pong on map transfer - Add WMORenderer::clearAll() to clear models/textures on map change (prevents GPU crash) - Increase WMO texture cache default to 8GB - Fix setMapName called after loadTestTerrain so WMO renderer exists - Save/restore player position around CMSG_AREATRIGGER to prevent bad DB persistence
This commit is contained in:
parent
d0e8b44866
commit
16d88f19fc
6 changed files with 120 additions and 41 deletions
|
|
@ -1500,6 +1500,7 @@ private:
|
|||
std::vector<AreaTriggerEntry> areaTriggers_;
|
||||
std::unordered_set<uint32_t> activeAreaTriggers_; // triggers player is currently inside
|
||||
float areaTriggerCheckTimer_ = 0.0f;
|
||||
bool areaTriggerSuppressFirst_ = false; // suppress first check after map transfer
|
||||
|
||||
float castTimeTotal = 0.0f;
|
||||
std::array<ActionBarSlot, 12> actionBar{};
|
||||
|
|
|
|||
|
|
@ -136,6 +136,11 @@ public:
|
|||
*/
|
||||
void clearInstances();
|
||||
|
||||
/**
|
||||
* Clear all instances, loaded models, and texture cache (for map transitions)
|
||||
*/
|
||||
void clearAll();
|
||||
|
||||
/**
|
||||
* Render all WMO instances (Vulkan)
|
||||
* @param cmd Command buffer to record into
|
||||
|
|
@ -630,7 +635,7 @@ private:
|
|||
std::unordered_map<std::string, TextureCacheEntry> textureCache;
|
||||
size_t textureCacheBytes_ = 0;
|
||||
uint64_t textureCacheCounter_ = 0;
|
||||
size_t textureCacheBudgetBytes_ = 2048ull * 1024 * 1024; // Default, overridden at init
|
||||
size_t textureCacheBudgetBytes_ = 8192ull * 1024 * 1024; // 8 GB default, overridden at init
|
||||
std::unordered_set<std::string> failedTextureCache_;
|
||||
std::unordered_set<std::string> loggedTextureLoadFails_;
|
||||
uint32_t textureBudgetRejectWarnings_ = 0;
|
||||
|
|
|
|||
|
|
@ -3218,9 +3218,9 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
pendingTransportDoodadBatches_.clear();
|
||||
|
||||
if (renderer) {
|
||||
// Clear all world geometry from old map
|
||||
// Clear all world geometry from old map (including textures/models)
|
||||
if (auto* wmo = renderer->getWMORenderer()) {
|
||||
wmo->clearInstances();
|
||||
wmo->clearAll();
|
||||
}
|
||||
if (auto* m2 = renderer->getM2Renderer()) {
|
||||
m2->clear();
|
||||
|
|
@ -3384,7 +3384,7 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
|
||||
if (isWMOOnlyMap) {
|
||||
// ---- WMO-only map (dungeon/raid/BG): load root WMO directly ----
|
||||
LOG_INFO("WMO-only map detected — loading root WMO: ", wdtInfo.rootWMOPath);
|
||||
LOG_WARNING("WMO-only map detected — loading root WMO: ", wdtInfo.rootWMOPath);
|
||||
showProgress("Loading instance geometry...", 0.25f);
|
||||
|
||||
// Still call loadTestTerrain with a dummy path to initialize all renderers
|
||||
|
|
@ -3392,7 +3392,17 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
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 with dummy ADT: ", dummyAdtPath);
|
||||
renderer->loadTestTerrain(assetManager.get(), dummyAdtPath);
|
||||
LOG_WARNING("WMO-only: loadTestTerrain returned");
|
||||
|
||||
// Set map name on the newly-created WMO renderer (loadTestTerrain creates it)
|
||||
if (renderer->getWMORenderer()) {
|
||||
renderer->getWMORenderer()->setMapName(mapName);
|
||||
}
|
||||
if (renderer->getTerrainManager()) {
|
||||
renderer->getTerrainManager()->setMapName(mapName);
|
||||
}
|
||||
|
||||
// Disable terrain streaming — no ADT tiles for WMO-only maps
|
||||
if (renderer->getTerrainManager()) {
|
||||
|
|
@ -3407,10 +3417,14 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
|
||||
// Load the root WMO
|
||||
auto* wmoRenderer = renderer->getWMORenderer();
|
||||
LOG_WARNING("WMO-only: wmoRenderer=", (wmoRenderer ? "valid" : "NULL"));
|
||||
if (wmoRenderer) {
|
||||
LOG_WARNING("WMO-only: reading root WMO file: ", wdtInfo.rootWMOPath);
|
||||
std::vector<uint8_t> wmoData = assetManager->readFile(wdtInfo.rootWMOPath);
|
||||
LOG_WARNING("WMO-only: root WMO data size=", wmoData.size());
|
||||
if (!wmoData.empty()) {
|
||||
pipeline::WMOModel wmoModel = pipeline::WMOLoader::load(wmoData);
|
||||
LOG_WARNING("WMO-only: parsed WMO model, nGroups=", wmoModel.nGroups);
|
||||
|
||||
if (wmoModel.nGroups > 0) {
|
||||
showProgress("Loading instance groups...", 0.35f);
|
||||
|
|
|
|||
|
|
@ -3430,6 +3430,8 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) {
|
|||
|
||||
// Initialize movement info with world entry position (server → canonical)
|
||||
glm::vec3 canonical = core::coords::serverToCanonical(glm::vec3(data.x, data.y, data.z));
|
||||
LOG_WARNING("LOGIN_VERIFY_WORLD: server=(", data.x, ", ", data.y, ", ", data.z,
|
||||
") canonical=(", canonical.x, ", ", canonical.y, ", ", canonical.z, ") mapId=", data.mapId);
|
||||
movementInfo.x = canonical.x;
|
||||
movementInfo.y = canonical.y;
|
||||
movementInfo.z = canonical.z;
|
||||
|
|
@ -8800,27 +8802,31 @@ void GameHandler::checkAreaTriggers() {
|
|||
int mapTriggerCount = 0;
|
||||
float closestDist = 999999.0f;
|
||||
uint32_t closestId = 0;
|
||||
float closestX = 0, closestY = 0, closestZ = 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; closestX = at.x; closestY = at.y; closestZ = at.z; }
|
||||
}
|
||||
LOG_WARNING("AreaTrigger check: player=(", px, ", ", py, ", ", pz,
|
||||
") map=", currentMapId_, " triggers_on_map=", mapTriggerCount,
|
||||
" closest=AT", closestId, " at(", closestX, ", ", closestY, ", ", closestZ, ") dist=", closestDist);
|
||||
// Log AT 2173 (Stormwind tram entrance) specifically
|
||||
for (const auto& at : areaTriggers_) {
|
||||
if (at.id == 2173) {
|
||||
float dx = px - at.x, dy = py - at.y, dz = pz - at.z;
|
||||
float dist = std::sqrt(dx*dx + dy*dy + dz*dz);
|
||||
LOG_WARNING(" AT2173: map=", at.mapId, " pos=(", at.x, ", ", at.y, ", ", at.z,
|
||||
") r=", at.radius, " box=(", at.boxLength, ", ", at.boxWidth, ", ", at.boxHeight, ") dist=", dist);
|
||||
break;
|
||||
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_;
|
||||
if (suppressFirst) {
|
||||
areaTriggerSuppressFirst_ = false;
|
||||
}
|
||||
|
||||
for (const auto& at : areaTriggers_) {
|
||||
|
|
@ -8837,7 +8843,12 @@ void GameHandler::checkAreaTriggers() {
|
|||
float distSq = dx * dx + dy * dy + dz * dz;
|
||||
inside = (distSq <= effectiveRadius * effectiveRadius);
|
||||
} else if (at.boxLength > 0.0f || at.boxWidth > 0.0f || at.boxHeight > 0.0f) {
|
||||
// Box trigger (axis-aligned or rotated)
|
||||
// Box trigger — use generous minimum dimensions since WMO collision
|
||||
// may block the player from reaching small triggers inside doorways
|
||||
float effLength = std::max(at.boxLength, 90.0f);
|
||||
float effWidth = std::max(at.boxWidth, 90.0f);
|
||||
float effHeight = std::max(at.boxHeight, 90.0f);
|
||||
|
||||
float dx = px - at.x;
|
||||
float dy = py - at.y;
|
||||
float dz = pz - at.z;
|
||||
|
|
@ -8848,28 +8859,41 @@ void GameHandler::checkAreaTriggers() {
|
|||
float localX = dx * cosYaw - dy * sinYaw;
|
||||
float localY = dx * sinYaw + dy * cosYaw;
|
||||
|
||||
inside = (std::abs(localX) <= at.boxLength * 0.5f &&
|
||||
std::abs(localY) <= at.boxWidth * 0.5f &&
|
||||
std::abs(dz) <= at.boxHeight * 0.5f);
|
||||
inside = (std::abs(localX) <= effLength * 0.5f &&
|
||||
std::abs(localY) <= effWidth * 0.5f &&
|
||||
std::abs(dz) <= effHeight * 0.5f);
|
||||
}
|
||||
|
||||
if (inside) {
|
||||
// Only fire once per entry (don't re-send while standing inside)
|
||||
if (activeAreaTriggers_.count(at.id) == 0) {
|
||||
activeAreaTriggers_.insert(at.id);
|
||||
|
||||
// Move player to trigger center so the server's distance check passes
|
||||
// (WMO collision may prevent the client from physically reaching the trigger)
|
||||
movementInfo.x = at.x;
|
||||
movementInfo.y = at.y;
|
||||
movementInfo.z = at.z;
|
||||
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||
if (suppressFirst) {
|
||||
// After map transfer: mark triggers we're inside of, but don't fire them.
|
||||
// This prevents the exit portal from immediately sending us back.
|
||||
LOG_WARNING("AreaTrigger suppressed (post-transfer): AT", at.id);
|
||||
} else {
|
||||
// Temporarily move player to trigger center so the server's distance
|
||||
// check passes, then restore to actual position so the server doesn't
|
||||
// persist the fake position on disconnect.
|
||||
float savedX = movementInfo.x, savedY = movementInfo.y, savedZ = movementInfo.z;
|
||||
movementInfo.x = at.x;
|
||||
movementInfo.y = at.y;
|
||||
movementInfo.z = at.z;
|
||||
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||
|
||||
network::Packet pkt(wireOpcode(Opcode::CMSG_AREATRIGGER));
|
||||
pkt.writeUInt32(at.id);
|
||||
socket->send(pkt);
|
||||
LOG_WARNING("Fired CMSG_AREATRIGGER: id=", at.id,
|
||||
" at (", at.x, ", ", at.y, ", ", at.z, ")");
|
||||
network::Packet pkt(wireOpcode(Opcode::CMSG_AREATRIGGER));
|
||||
pkt.writeUInt32(at.id);
|
||||
socket->send(pkt);
|
||||
LOG_WARNING("Fired CMSG_AREATRIGGER: id=", at.id,
|
||||
" at (", at.x, ", ", at.y, ", ", at.z, ")");
|
||||
|
||||
// Restore actual player position
|
||||
movementInfo.x = savedX;
|
||||
movementInfo.y = savedY;
|
||||
movementInfo.z = savedZ;
|
||||
sendMovement(Opcode::MSG_MOVE_HEARTBEAT);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Player left the trigger — allow re-fire on re-entry
|
||||
|
|
@ -12232,6 +12256,7 @@ void GameHandler::handleNewWorld(network::Packet& packet) {
|
|||
worldStateZoneId_ = 0;
|
||||
activeAreaTriggers_.clear();
|
||||
areaTriggerCheckTimer_ = -5.0f; // 5-second cooldown after map transfer
|
||||
areaTriggerSuppressFirst_ = true; // first check just marks active triggers, doesn't fire
|
||||
stopAutoAttack();
|
||||
casting = false;
|
||||
currentCastSpellId = 0;
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ float readF32(const uint8_t* data, size_t offset) {
|
|||
return v;
|
||||
}
|
||||
|
||||
// Chunk magic constants (little-endian)
|
||||
constexpr uint32_t MVER = 0x5245564D; // "REVM"
|
||||
constexpr uint32_t MPHD = 0x4448504D; // "DHPM"
|
||||
constexpr uint32_t MAIN = 0x4E49414D; // "NIAM"
|
||||
constexpr uint32_t MWMO = 0x4F4D574D; // "OMWM"
|
||||
constexpr uint32_t MODF = 0x46444F4D; // "FDOM"
|
||||
// Chunk magic constants (big-endian ASCII, same as ADTLoader)
|
||||
constexpr uint32_t MVER = 0x4D564552; // "MVER"
|
||||
constexpr uint32_t MPHD = 0x4D504844; // "MPHD"
|
||||
constexpr uint32_t MAIN = 0x4D41494E; // "MAIN"
|
||||
constexpr uint32_t MWMO = 0x4D574D4F; // "MWMO"
|
||||
constexpr uint32_t MODF = 0x4D4F4446; // "MODF"
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ bool WMORenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
flatNormalTexture_->createSampler(device, VK_FILTER_LINEAR, VK_FILTER_LINEAR,
|
||||
VK_SAMPLER_ADDRESS_MODE_REPEAT);
|
||||
textureCacheBudgetBytes_ =
|
||||
envSizeMBOrDefault("WOWEE_WMO_TEX_CACHE_MB", 4096) * 1024ull * 1024ull;
|
||||
envSizeMBOrDefault("WOWEE_WMO_TEX_CACHE_MB", 8192) * 1024ull * 1024ull;
|
||||
modelCacheLimit_ = envSizeMBOrDefault("WOWEE_WMO_MODEL_LIMIT", 4000);
|
||||
core::Logger::getInstance().info("WMO texture cache budget: ",
|
||||
textureCacheBudgetBytes_ / (1024 * 1024), " MB");
|
||||
|
|
@ -1039,6 +1039,40 @@ void WMORenderer::clearInstances() {
|
|||
core::Logger::getInstance().info("Cleared all WMO instances");
|
||||
}
|
||||
|
||||
void WMORenderer::clearAll() {
|
||||
clearInstances();
|
||||
|
||||
if (vkCtx_) {
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
VmaAllocator allocator = vkCtx_->getAllocator();
|
||||
vkDeviceWaitIdle(device);
|
||||
|
||||
// Free GPU resources for loaded models
|
||||
for (auto& [id, model] : loadedModels) {
|
||||
for (auto& group : model.groups) {
|
||||
destroyGroupGPU(group);
|
||||
}
|
||||
}
|
||||
|
||||
// Free cached textures
|
||||
for (auto& [path, entry] : textureCache) {
|
||||
if (entry.texture) entry.texture->destroy(device, allocator);
|
||||
if (entry.normalHeightMap) entry.normalHeightMap->destroy(device, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
loadedModels.clear();
|
||||
textureCache.clear();
|
||||
textureCacheBytes_ = 0;
|
||||
textureCacheCounter_ = 0;
|
||||
failedTextureCache_.clear();
|
||||
loggedTextureLoadFails_.clear();
|
||||
textureBudgetRejectWarnings_ = 0;
|
||||
precomputedFloorGrid.clear();
|
||||
|
||||
LOG_WARNING("Cleared all WMO models, instances, and texture cache");
|
||||
}
|
||||
|
||||
void WMORenderer::setCollisionFocus(const glm::vec3& worldPos, float radius) {
|
||||
collisionFocusEnabled = (radius > 0.0f);
|
||||
collisionFocusPos = worldPos;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue