mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 00:03:50 +00:00
Compare commits
2 commits
d0e8b44866
...
0e1241ca60
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e1241ca60 | ||
|
|
16d88f19fc |
7 changed files with 132 additions and 45 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;
|
||||
|
|
|
|||
|
|
@ -1349,16 +1349,18 @@ void Application::setupUICallbacks() {
|
|||
}
|
||||
|
||||
uint32_t realmId = 0;
|
||||
uint16_t realmBuild = 0;
|
||||
{
|
||||
// WotLK AUTH_SESSION includes a RealmID field; some servers reject if it's wrong/zero.
|
||||
const auto& realms = authHandler->getRealms();
|
||||
for (const auto& r : realms) {
|
||||
if (r.name == realmName && r.address == realmAddress) {
|
||||
realmId = r.id;
|
||||
realmBuild = r.build;
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG_INFO("Selected realmId=", realmId);
|
||||
LOG_INFO("Selected realmId=", realmId, " realmBuild=", realmBuild);
|
||||
}
|
||||
|
||||
uint32_t clientBuild = 12340; // default WotLK
|
||||
|
|
@ -1366,6 +1368,12 @@ void Application::setupUICallbacks() {
|
|||
auto* profile = expansionRegistry_->getActive();
|
||||
if (profile) clientBuild = profile->worldBuild;
|
||||
}
|
||||
// Prefer realm-reported build when available (e.g. vanilla servers
|
||||
// that report build 5875 in the realm list)
|
||||
if (realmBuild != 0) {
|
||||
clientBuild = realmBuild;
|
||||
LOG_INFO("Using realm-reported build: ", clientBuild);
|
||||
}
|
||||
if (gameHandler->connect(host, port, sessionKey, accountName, clientBuild, realmId)) {
|
||||
LOG_INFO("Connected to world server, transitioning to character selection");
|
||||
setState(AppState::CHARACTER_SELECTION);
|
||||
|
|
@ -3218,9 +3226,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 +3392,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 +3400,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 +3425,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;
|
||||
|
|
|
|||
|
|
@ -179,10 +179,10 @@ void RealmScreen::render(auth::AuthHandler& authHandler) {
|
|||
ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f),
|
||||
" - %d character%s", realm.characters, realm.characters > 1 ? "s" : "");
|
||||
}
|
||||
if (realm.hasVersionInfo()) {
|
||||
if (realm.hasVersionInfo() && (realm.majorVersion || realm.build)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled(" v%d.%d.%d",
|
||||
realm.majorVersion, realm.minorVersion, realm.patchVersion);
|
||||
ImGui::TextDisabled(" v%d.%d.%d (build %d)",
|
||||
realm.majorVersion, realm.minorVersion, realm.patchVersion, realm.build);
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue