Fix transport/WMO diagnostics and terrain WMO dedup lifecycle

This commit is contained in:
Kelsi 2026-02-18 22:36:34 -08:00
parent 514b914068
commit ff8ffc3bfb
5 changed files with 59 additions and 26 deletions

View file

@ -60,6 +60,7 @@ struct TerrainTile {
// Instance IDs for cleanup on unload
std::vector<uint32_t> wmoInstanceIds;
std::vector<uint32_t> wmoUniqueIds; // For WMO dedup cleanup on unload
std::vector<uint32_t> m2InstanceIds;
std::vector<uint32_t> doodadUniqueIds; // For dedup cleanup on unload
};
@ -93,6 +94,7 @@ struct PendingTile {
// Pre-loaded WMO data
struct WMOReady {
uint32_t modelId;
uint32_t uniqueId;
pipeline::WMOModel model;
glm::vec3 position;
glm::vec3 rotation;

View file

@ -1659,14 +1659,14 @@ void Application::setupUICallbacks() {
transportManager->registerTransport(guid, wmoInstanceId, pathId, canonicalSpawnPos, entry);
} else {
pendingTransportMoves_[guid] = PendingTransportMove{x, y, z, orientation};
LOG_WARNING("Cannot auto-spawn transport 0x", std::hex, guid, std::dec,
" - WMO instance not found (queued move for replay)");
LOG_DEBUG("Cannot auto-spawn transport 0x", std::hex, guid, std::dec,
" - WMO instance not found yet (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 (queued move for replay)");
LOG_DEBUG("Cannot auto-spawn transport 0x", std::hex, guid, std::dec,
" - entity not found in EntityManager (queued move for replay)");
return;
}
}
@ -5137,24 +5137,35 @@ void Application::setupTestTransport() {
transportManager->loadPathFromNodes(pathId, harborPath, true, speed);
LOG_INFO("Registered transport path ", pathId, " with ", harborPath.size(), " waypoints, speed=", speed);
// Try to load a transport WMO model
// Common transport WMOs: Transportship.wmo (generic ship)
std::string transportWmoPath = "Transports\\Transportship\\Transportship.wmo";
// Try transport WMOs in manifest-backed paths first.
std::vector<std::string> transportCandidates = {
"World\\wmo\\transports\\transport_ship\\transportship.wmo",
"World\\wmo\\transports\\transport_zeppelin\\transport_zeppelin.wmo",
"World\\wmo\\transports\\transport_horde_zeppelin\\Transport_Horde_Zeppelin.wmo",
"World\\wmo\\transports\\icebreaker\\Transport_Icebreaker_ship.wmo",
// Legacy fallbacks
"Transports\\Transportship\\Transportship.wmo",
"Transports\\Boat\\Boat.wmo",
};
auto wmoData = assetManager->readFile(transportWmoPath);
if (wmoData.empty()) {
LOG_WARNING("Could not load transport WMO: ", transportWmoPath);
LOG_INFO("Trying alternative: Boat transport");
transportWmoPath = "Transports\\Boat\\Boat.wmo";
wmoData = assetManager->readFile(transportWmoPath);
std::string transportWmoPath;
std::vector<uint8_t> wmoData;
for (const auto& candidate : transportCandidates) {
wmoData = assetManager->readFile(candidate);
if (!wmoData.empty()) {
transportWmoPath = candidate;
break;
}
}
if (wmoData.empty()) {
LOG_WARNING("No transport WMO found - test transport disabled");
LOG_INFO("Available transport WMOs are typically in Transports\\ directory");
LOG_INFO("Expected under World\\wmo\\transports\\...");
return;
}
LOG_INFO("Using transport WMO: ", transportWmoPath);
// Load WMO model
pipeline::WMOModel wmoModel = pipeline::WMOLoader::load(wmoData);
LOG_INFO("Transport WMO root loaded: ", transportWmoPath, " nGroups=", wmoModel.nGroups);

View file

@ -10,10 +10,11 @@ void EntityManager::addEntity(uint64_t guid, std::shared_ptr<Entity> entity) {
return;
}
const int type = static_cast<int>(entity->getType());
entities[guid] = std::move(entity);
LOG_DEBUG("Added entity: GUID=0x", std::hex, guid, std::dec,
", Type=", static_cast<int>(entity->getType()));
", Type=", type);
}
void EntityManager::removeEntity(uint64_t guid) {

View file

@ -3307,7 +3307,6 @@ void GameHandler::setOrientation(float orientation) {
}
void GameHandler::handleUpdateObject(network::Packet& packet) {
UpdateObjectData data;
if (!packetParsers_->parseUpdateObject(packet, data)) {
LOG_WARNING("Failed to parse SMSG_UPDATE_OBJECT");
@ -10588,7 +10587,10 @@ void GameHandler::saveCharacterConfig() {
out << "character_guid=" << playerGuid << "\n";
out << "gender=" << static_cast<int>(ch->gender) << "\n";
out << "use_female_model=" << (ch->useFemaleModel ? 1 : 0) << "\n";
// For male/female, derive from gender; only nonbinary has a meaningful separate choice
bool saveUseFemaleModel = (ch->gender == Gender::NONBINARY) ? ch->useFemaleModel
: (ch->gender == Gender::FEMALE);
out << "use_female_model=" << (saveUseFemaleModel ? 1 : 0) << "\n";
for (int i = 0; i < ACTION_BAR_SLOTS; i++) {
out << "action_bar_" << i << "_type=" << static_cast<int>(actionBar[i].type) << "\n";
out << "action_bar_" << i << "_id=" << actionBar[i].id << "\n";
@ -10666,8 +10668,14 @@ void GameHandler::loadCharacterConfig() {
for (auto& character : characters) {
if (character.guid == playerGuid) {
character.gender = static_cast<Gender>(savedGender);
if (savedUseFemaleModel >= 0) {
character.useFemaleModel = (savedUseFemaleModel != 0);
if (character.gender == Gender::NONBINARY) {
// Only nonbinary characters have a meaningful body type choice
if (savedUseFemaleModel >= 0) {
character.useFemaleModel = (savedUseFemaleModel != 0);
}
} else {
// Male/female always use the model matching their gender
character.useFemaleModel = (character.gender == Gender::FEMALE);
}
LOG_INFO("Applied saved gender: ", getGenderName(character.gender),
", body type: ", (character.useFemaleModel ? "feminine" : "masculine"));

View file

@ -521,7 +521,10 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
}
PendingTile::WMOReady ready;
ready.modelId = placement.uniqueId;
// Cache WMO model uploads by path; placement dedup uses uniqueId separately.
ready.modelId = static_cast<uint32_t>(std::hash<std::string>{}(wmoPath));
if (ready.modelId == 0) ready.modelId = 1;
ready.uniqueId = placement.uniqueId;
ready.model = std::move(wmoModel);
ready.position = pos;
ready.rotation = rot;
@ -647,10 +650,11 @@ void TerrainManager::finalizeTile(const std::shared_ptr<PendingTile>& pending) {
std::vector<uint32_t> m2InstanceIds;
std::vector<uint32_t> wmoInstanceIds;
std::vector<uint32_t> tileUniqueIds;
std::vector<uint32_t> tileWmoUniqueIds;
// Upload M2 models to GPU and create instances
if (m2Renderer && assetManager) {
if (!m2Renderer->getModelCount()) {
if (!m2Renderer->isInitialized()) {
m2Renderer->initialize(assetManager);
}
@ -691,7 +695,7 @@ void TerrainManager::finalizeTile(const std::shared_ptr<PendingTile>& pending) {
// Upload WMO models to GPU and create instances
if (wmoRenderer && assetManager) {
if (!wmoRenderer->getModelCount()) {
if (!wmoRenderer->isInitialized()) {
wmoRenderer->initialize(assetManager);
}
@ -699,9 +703,9 @@ void TerrainManager::finalizeTile(const std::shared_ptr<PendingTile>& pending) {
int loadedLiquids = 0;
int skippedWmoDedup = 0;
for (auto& wmoReady : pending->wmoModels) {
// Deduplicate WMO instances by uniqueId (prevents Stormwind from rendering 16x)
// uniqueId is stored in modelId field (see line 522 in prepareTile)
if (placedWmoIds.count(wmoReady.modelId)) {
// Deduplicate by placement uniqueId when available.
// Some ADTs use uniqueId=0, which is not safe for dedup.
if (wmoReady.uniqueId != 0 && placedWmoIds.count(wmoReady.uniqueId)) {
skippedWmoDedup++;
continue;
}
@ -710,7 +714,10 @@ void TerrainManager::finalizeTile(const std::shared_ptr<PendingTile>& pending) {
uint32_t wmoInstId = wmoRenderer->createInstance(wmoReady.modelId, wmoReady.position, wmoReady.rotation);
if (wmoInstId) {
wmoInstanceIds.push_back(wmoInstId);
placedWmoIds.insert(wmoReady.modelId);
if (wmoReady.uniqueId != 0) {
placedWmoIds.insert(wmoReady.uniqueId);
tileWmoUniqueIds.push_back(wmoReady.uniqueId);
}
loadedWMOs++;
// Load WMO liquids (canals, pools, etc.)
@ -773,6 +780,7 @@ void TerrainManager::finalizeTile(const std::shared_ptr<PendingTile>& pending) {
tile->loaded = true;
tile->m2InstanceIds = std::move(m2InstanceIds);
tile->wmoInstanceIds = std::move(wmoInstanceIds);
tile->wmoUniqueIds = std::move(tileWmoUniqueIds);
tile->doodadUniqueIds = std::move(tileUniqueIds);
// Calculate world bounds
@ -1018,6 +1026,9 @@ void TerrainManager::unloadTile(int x, int y) {
for (uint32_t uid : tile->doodadUniqueIds) {
placedDoodadIds.erase(uid);
}
for (uint32_t uid : tile->wmoUniqueIds) {
placedWmoIds.erase(uid);
}
// Remove M2 doodad instances
if (m2Renderer) {