mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
fix(parsing): correct UPDATE_OBJECT PackedGuid, cape textures, and missing asset guards
Some checks failed
Build / Build (arm64) (push) Has been cancelled
Build / Build (x86-64) (push) Has been cancelled
Build / Build (macOS arm64) (push) Has been cancelled
Build / Build (windows-arm64) (push) Has been cancelled
Build / Build (windows-x86-64) (push) Has been cancelled
Security / CodeQL (C/C++) (push) Has been cancelled
Security / Semgrep (push) Has been cancelled
Security / Sanitizer Build (ASan/UBSan) (push) Has been cancelled
Some checks failed
Build / Build (arm64) (push) Has been cancelled
Build / Build (x86-64) (push) Has been cancelled
Build / Build (macOS arm64) (push) Has been cancelled
Build / Build (windows-arm64) (push) Has been cancelled
Build / Build (windows-x86-64) (push) Has been cancelled
Security / CodeQL (C/C++) (push) Has been cancelled
Security / Semgrep (push) Has been cancelled
Security / Sanitizer Build (ASan/UBSan) (push) Has been cancelled
- Fix MOVEMENT update type to use readPackedGuid() instead of readUInt64() (WotLK 3.3.5a) - Add desync diagnostic logging to UPDATE_OBJECT parser for future debugging - Register MSG_MOVE_SET_COLLISION_HGT (0x518) as skip handler - Fix cape texture lookup to only try .blp extension variants (4 files) - Add fileExists() guards for underwear textures referencing missing BLP files (4 files) - Add spell visual impact→cast M2 path fallback - Skip WMO doodad instance creation when model load fails - Demote spell caster position warning to debug level
This commit is contained in:
parent
83eef878fb
commit
01fecbf3e0
9 changed files with 105 additions and 38 deletions
|
|
@ -123,12 +123,12 @@ PlayerTextureInfo AppearanceComposer::resolvePlayerTextures(pipeline::M2Model& m
|
||||||
else if (baseSection == 4 && !foundUnderwear && colorIndex == charSkinId) {
|
else if (baseSection == 4 && !foundUnderwear && colorIndex == charSkinId) {
|
||||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||||
std::string tex = charSectionsDbc->getString(r, f);
|
std::string tex = charSectionsDbc->getString(r, f);
|
||||||
if (!tex.empty()) {
|
if (!tex.empty() && assetManager_->fileExists(tex)) {
|
||||||
result.underwearPaths.push_back(tex);
|
result.underwearPaths.push_back(tex);
|
||||||
LOG_INFO(" DBC underwear texture: ", tex);
|
LOG_INFO(" DBC underwear texture: ", tex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foundUnderwear = true;
|
foundUnderwear = !result.underwearPaths.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundSkin && foundHair && foundFaceLower && foundUnderwear) break;
|
if (foundSkin && foundHair && foundFaceLower && foundUnderwear) break;
|
||||||
|
|
|
||||||
|
|
@ -1069,7 +1069,8 @@ void EntitySpawner::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float
|
||||||
} else if (section == 4 && npcUnderwear.empty() && color == npcSkin) {
|
} else if (section == 4 && npcUnderwear.empty() && color == npcSkin) {
|
||||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||||
std::string tex = csDbc->getString(r, f);
|
std::string tex = csDbc->getString(r, f);
|
||||||
if (!tex.empty()) npcUnderwear.push_back(tex);
|
if (!tex.empty() && am->fileExists(tex))
|
||||||
|
npcUnderwear.push_back(tex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1659,14 +1660,15 @@ void EntitySpawner::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float
|
||||||
const bool hasDir = (name.find('\\') != std::string::npos);
|
const bool hasDir = (name.find('\\') != std::string::npos);
|
||||||
const bool hasExt = hasBlpExt(name);
|
const bool hasExt = hasBlpExt(name);
|
||||||
if (hasDir) {
|
if (hasDir) {
|
||||||
addCapeCandidate(name);
|
if (hasExt) addCapeCandidate(name);
|
||||||
if (!hasExt) addCapeCandidate(name + ".blp");
|
else addCapeCandidate(name + ".blp");
|
||||||
} else {
|
} else {
|
||||||
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + name;
|
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + name;
|
||||||
std::string baseTex = "Item\\TextureComponents\\Cape\\" + name;
|
std::string baseTex = "Item\\TextureComponents\\Cape\\" + name;
|
||||||
addCapeCandidate(baseObj);
|
if (hasExt) {
|
||||||
addCapeCandidate(baseTex);
|
addCapeCandidate(baseObj);
|
||||||
if (!hasExt) {
|
addCapeCandidate(baseTex);
|
||||||
|
} else {
|
||||||
addCapeCandidate(baseObj + ".blp");
|
addCapeCandidate(baseObj + ".blp");
|
||||||
addCapeCandidate(baseTex + ".blp");
|
addCapeCandidate(baseTex + ".blp");
|
||||||
}
|
}
|
||||||
|
|
@ -2055,14 +2057,15 @@ void EntitySpawner::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float
|
||||||
const bool hasDir = (name.find('\\') != std::string::npos);
|
const bool hasDir = (name.find('\\') != std::string::npos);
|
||||||
const bool hasExt = hasBlpExt(name);
|
const bool hasExt = hasBlpExt(name);
|
||||||
if (hasDir) {
|
if (hasDir) {
|
||||||
addCandidate(name);
|
if (hasExt) addCandidate(name);
|
||||||
if (!hasExt) addCandidate(name + ".blp");
|
else addCandidate(name + ".blp");
|
||||||
} else {
|
} else {
|
||||||
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + name;
|
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + name;
|
||||||
std::string baseTex = "Item\\TextureComponents\\Cape\\" + name;
|
std::string baseTex = "Item\\TextureComponents\\Cape\\" + name;
|
||||||
addCandidate(baseObj);
|
if (hasExt) {
|
||||||
addCandidate(baseTex);
|
addCandidate(baseObj);
|
||||||
if (!hasExt) {
|
addCandidate(baseTex);
|
||||||
|
} else {
|
||||||
addCandidate(baseObj + ".blp");
|
addCandidate(baseObj + ".blp");
|
||||||
addCandidate(baseTex + ".blp");
|
addCandidate(baseTex + ".blp");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -228,11 +228,23 @@ void EntitySpawner::spawnOnlinePlayer(uint64_t guid,
|
||||||
hairTexturePath = charSectionsDbc->getString(r, csF.texture1);
|
hairTexturePath = charSectionsDbc->getString(r, csF.texture1);
|
||||||
if (!hairTexturePath.empty()) foundHair = true;
|
if (!hairTexturePath.empty()) foundHair = true;
|
||||||
} else if (baseSection == 4 && !foundUnderwear && colorIndex == skinId) {
|
} else if (baseSection == 4 && !foundUnderwear && colorIndex == skinId) {
|
||||||
|
// Verify textures exist — some DBC entries reference BLPs
|
||||||
|
// that were never shipped (e.g. Draenei skin colors 10-16).
|
||||||
|
bool allExist = true;
|
||||||
|
std::vector<std::string> candidateUW;
|
||||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||||
std::string tex = charSectionsDbc->getString(r, f);
|
std::string tex = charSectionsDbc->getString(r, f);
|
||||||
if (!tex.empty()) underwearPaths.push_back(tex);
|
if (!tex.empty()) {
|
||||||
|
if (assetManager_->fileExists(tex))
|
||||||
|
candidateUW.push_back(tex);
|
||||||
|
else
|
||||||
|
allExist = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allExist || !candidateUW.empty()) {
|
||||||
|
underwearPaths = std::move(candidateUW);
|
||||||
|
foundUnderwear = true;
|
||||||
}
|
}
|
||||||
foundUnderwear = true;
|
|
||||||
} else if (baseSection == 1 && !foundFaceLower &&
|
} else if (baseSection == 1 && !foundFaceLower &&
|
||||||
variationIndex == faceId && colorIndex == skinId) {
|
variationIndex == faceId && colorIndex == skinId) {
|
||||||
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
||||||
|
|
@ -694,14 +706,15 @@ void EntitySpawner::setOnlinePlayerEquipment(uint64_t guid,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hasDir) {
|
if (hasDir) {
|
||||||
addCapeCandidate(capeName);
|
if (hasExt) addCapeCandidate(capeName);
|
||||||
if (!hasExt) addCapeCandidate(capeName + ".blp");
|
else addCapeCandidate(capeName + ".blp");
|
||||||
} else {
|
} else {
|
||||||
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + capeName;
|
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + capeName;
|
||||||
std::string baseTex = "Item\\TextureComponents\\Cape\\" + capeName;
|
std::string baseTex = "Item\\TextureComponents\\Cape\\" + capeName;
|
||||||
addCapeCandidate(baseObj);
|
if (hasExt) {
|
||||||
addCapeCandidate(baseTex);
|
addCapeCandidate(baseObj);
|
||||||
if (!hasExt) {
|
addCapeCandidate(baseTex);
|
||||||
|
} else {
|
||||||
addCapeCandidate(baseObj + ".blp");
|
addCapeCandidate(baseObj + ".blp");
|
||||||
addCapeCandidate(baseTex + ".blp");
|
addCapeCandidate(baseTex + ".blp");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2523,6 +2523,8 @@ void GameHandler::registerOpcodeHandlers() {
|
||||||
};
|
};
|
||||||
// GM ticket status (new/updated); no ticket UI yet
|
// GM ticket status (new/updated); no ticket UI yet
|
||||||
registerSkipHandler(Opcode::SMSG_GM_TICKET_STATUS_UPDATE);
|
registerSkipHandler(Opcode::SMSG_GM_TICKET_STATUS_UPDATE);
|
||||||
|
// Broadcast of another player's collision height change — cosmetic only.
|
||||||
|
registerSkipHandler(Opcode::MSG_MOVE_SET_COLLISION_HGT);
|
||||||
// Client uses this outbound; treat inbound variant as no-op for robustness.
|
// Client uses this outbound; treat inbound variant as no-op for robustness.
|
||||||
registerSkipHandler(Opcode::MSG_MOVE_WORLDPORT_ACK);
|
registerSkipHandler(Opcode::MSG_MOVE_WORLDPORT_ACK);
|
||||||
// Observed custom server packet (8 bytes). Safe-consume for now.
|
// Observed custom server packet (8 bytes). Safe-consume for now.
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ void SpellHandler::triggerCastVisual(uint32_t spellId, uint64_t casterGuid, uint
|
||||||
uint32_t visualId = resolveSpellVisualId(spellId);
|
uint32_t visualId = resolveSpellVisualId(spellId);
|
||||||
if (visualId == 0) { LOG_WARNING("SpellVisual: triggerCastVisual — visualId=0 for spellId=", spellId); return; }
|
if (visualId == 0) { LOG_WARNING("SpellVisual: triggerCastVisual — visualId=0 for spellId=", spellId); return; }
|
||||||
glm::vec3 casterPos;
|
glm::vec3 casterPos;
|
||||||
if (!resolveUnitPosition(casterGuid, casterPos)) { LOG_WARNING("SpellVisual: triggerCastVisual — cannot resolve caster position"); return; }
|
if (!resolveUnitPosition(casterGuid, casterPos)) { LOG_DEBUG("SpellVisual: triggerCastVisual — cannot resolve caster position for guid=0x", std::hex, casterGuid, std::dec); return; }
|
||||||
LOG_INFO("SpellVisual: triggerCastVisual visualId=", visualId, " pos=(", casterPos.x, ",", casterPos.y, ",", casterPos.z, ") castTimeMs=", castTimeMs);
|
LOG_INFO("SpellVisual: triggerCastVisual visualId=", visualId, " pos=(", casterPos.x, ",", casterPos.y, ",", casterPos.z, ") castTimeMs=", castTimeMs);
|
||||||
svs->playSpellVisualPrecast(visualId, casterPos, castTimeMs);
|
svs->playSpellVisualPrecast(visualId, casterPos, castTimeMs);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1183,9 +1183,9 @@ bool UpdateObjectParser::parseUpdateBlock(network::Packet& packet, UpdateBlock&
|
||||||
}
|
}
|
||||||
|
|
||||||
case UpdateType::MOVEMENT: {
|
case UpdateType::MOVEMENT: {
|
||||||
// Movement update
|
// Movement update — WotLK 3.3.5a uses PackedGuid (NOT full uint64)
|
||||||
if (!packet.hasRemaining(8)) return false;
|
if (!packet.hasData()) return false;
|
||||||
block.guid = packet.readUInt64();
|
block.guid = packet.readPackedGuid();
|
||||||
LOG_DEBUG(" MOVEMENT update for GUID: 0x", std::hex, block.guid, std::dec);
|
LOG_DEBUG(" MOVEMENT update for GUID: 0x", std::hex, block.guid, std::dec);
|
||||||
|
|
||||||
return parseMovementBlock(packet, block);
|
return parseMovementBlock(packet, block);
|
||||||
|
|
@ -1288,9 +1288,18 @@ bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data)
|
||||||
data.blockCount = remainingBlockCount;
|
data.blockCount = remainingBlockCount;
|
||||||
data.blocks.reserve(data.blockCount);
|
data.blocks.reserve(data.blockCount);
|
||||||
|
|
||||||
|
// Track last block state for desync diagnostics
|
||||||
|
uint8_t prevUpdateType = 0;
|
||||||
|
uint8_t prevObjectType = 0;
|
||||||
|
uint16_t prevUpdateFlags = 0;
|
||||||
|
uint32_t prevMoveFlags = 0;
|
||||||
|
uint64_t prevGuid = 0;
|
||||||
|
size_t prevReadPos = packet.getReadPos();
|
||||||
|
|
||||||
for (uint32_t i = 0; i < data.blockCount; ++i) {
|
for (uint32_t i = 0; i < data.blockCount; ++i) {
|
||||||
LOG_DEBUG("Parsing block ", i + 1, " / ", data.blockCount);
|
LOG_DEBUG("Parsing block ", i + 1, " / ", data.blockCount);
|
||||||
|
|
||||||
|
size_t blockStartPos = packet.getReadPos();
|
||||||
UpdateBlock block;
|
UpdateBlock block;
|
||||||
if (!parseUpdateBlock(packet, block)) {
|
if (!parseUpdateBlock(packet, block)) {
|
||||||
static int parseBlockErrors = 0;
|
static int parseBlockErrors = 0;
|
||||||
|
|
@ -1299,6 +1308,31 @@ bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data)
|
||||||
LOG_ERROR("Failed to parse update block ", i + 1, " of ", data.blockCount,
|
LOG_ERROR("Failed to parse update block ", i + 1, " of ", data.blockCount,
|
||||||
" (", i, " blocks parsed, ", lostBlocks, " blocks LOST",
|
" (", i, " blocks parsed, ", lostBlocks, " blocks LOST",
|
||||||
", remaining=", packet.getRemainingSize(), " bytes)");
|
", remaining=", packet.getRemainingSize(), " bytes)");
|
||||||
|
LOG_ERROR(" blockStartPos=", blockStartPos, " packetSize=", packet.getSize());
|
||||||
|
if (i > 0) {
|
||||||
|
LOG_ERROR(" prevBlock: type=", static_cast<int>(prevUpdateType),
|
||||||
|
" objType=", static_cast<int>(prevObjectType),
|
||||||
|
" updateFlags=0x", std::hex, prevUpdateFlags,
|
||||||
|
" moveFlags=0x", prevMoveFlags,
|
||||||
|
" guid=0x", prevGuid, std::dec,
|
||||||
|
" startPos=", prevReadPos,
|
||||||
|
" consumed=", blockStartPos - prevReadPos, " bytes");
|
||||||
|
}
|
||||||
|
// Peek at the failing byte(s) for format diagnosis
|
||||||
|
packet.setReadPos(blockStartPos);
|
||||||
|
uint8_t peekBytes[8] = {};
|
||||||
|
size_t peekCount = std::min<size_t>(8, packet.getRemainingSize());
|
||||||
|
for (size_t p = 0; p < peekCount; ++p)
|
||||||
|
peekBytes[p] = packet.readUInt8();
|
||||||
|
LOG_ERROR(" failBytes: ",
|
||||||
|
std::hex, static_cast<int>(peekBytes[0]), " ",
|
||||||
|
static_cast<int>(peekBytes[1]), " ",
|
||||||
|
static_cast<int>(peekBytes[2]), " ",
|
||||||
|
static_cast<int>(peekBytes[3]), " ",
|
||||||
|
static_cast<int>(peekBytes[4]), " ",
|
||||||
|
static_cast<int>(peekBytes[5]), " ",
|
||||||
|
static_cast<int>(peekBytes[6]), " ",
|
||||||
|
static_cast<int>(peekBytes[7]), std::dec);
|
||||||
if (parseBlockErrors == 10)
|
if (parseBlockErrors == 10)
|
||||||
LOG_ERROR("(suppressing further update block parse errors)");
|
LOG_ERROR("(suppressing further update block parse errors)");
|
||||||
}
|
}
|
||||||
|
|
@ -1307,6 +1341,12 @@ bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevUpdateType = static_cast<uint8_t>(block.updateType);
|
||||||
|
prevObjectType = static_cast<uint8_t>(block.objectType);
|
||||||
|
prevUpdateFlags = block.updateFlags;
|
||||||
|
prevMoveFlags = block.moveFlags;
|
||||||
|
prevGuid = block.guid;
|
||||||
|
prevReadPos = blockStartPos;
|
||||||
data.blocks.emplace_back(std::move(block));
|
data.blocks.emplace_back(std::move(block));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -443,11 +443,11 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
|
||||||
variationIndex == 0 && colorIndex == static_cast<uint32_t>(skin)) {
|
variationIndex == 0 && colorIndex == static_cast<uint32_t>(skin)) {
|
||||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||||
std::string tex = charSectionsDbc->getString(r, f);
|
std::string tex = charSectionsDbc->getString(r, f);
|
||||||
if (!tex.empty()) {
|
if (!tex.empty() && assetManager_->fileExists(tex)) {
|
||||||
underwearPaths.push_back(tex);
|
underwearPaths.push_back(tex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foundUnderwear = true;
|
foundUnderwear = !underwearPaths.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -824,14 +824,15 @@ bool CharacterPreview::applyEquipment(const std::vector<game::EquipmentItem>& eq
|
||||||
bool hasDir = (name.find('\\') != std::string::npos);
|
bool hasDir = (name.find('\\') != std::string::npos);
|
||||||
bool hasExt = hasBlpExt(name);
|
bool hasExt = hasBlpExt(name);
|
||||||
if (hasDir) {
|
if (hasDir) {
|
||||||
addCandidate(name);
|
if (hasExt) addCandidate(name);
|
||||||
if (!hasExt) addCandidate(name + ".blp");
|
else addCandidate(name + ".blp");
|
||||||
} else {
|
} else {
|
||||||
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + name;
|
std::string baseObj = "Item\\ObjectComponents\\Cape\\" + name;
|
||||||
std::string baseTex = "Item\\TextureComponents\\Cape\\" + name;
|
std::string baseTex = "Item\\TextureComponents\\Cape\\" + name;
|
||||||
addCandidate(baseObj);
|
if (hasExt) {
|
||||||
addCandidate(baseTex);
|
addCandidate(baseObj);
|
||||||
if (!hasExt) {
|
addCandidate(baseTex);
|
||||||
|
} else {
|
||||||
addCandidate(baseObj + ".blp");
|
addCandidate(baseObj + ".blp");
|
||||||
addCandidate(baseTex + ".blp");
|
addCandidate(baseTex + ".blp");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -370,12 +370,15 @@ void SpellVisualSystem::playSpellVisual(uint32_t visualId, const glm::vec3& worl
|
||||||
|
|
||||||
if (!spellVisualDbcLoaded_) loadSpellVisualDbc();
|
if (!spellVisualDbcLoaded_) loadSpellVisualDbc();
|
||||||
|
|
||||||
// Select cast or impact path map
|
// Select cast or impact path map; fall back to the other if missing
|
||||||
auto& pathMap = useImpactKit ? spellVisualImpactPath_ : spellVisualCastPath_;
|
auto& primaryMap = useImpactKit ? spellVisualImpactPath_ : spellVisualCastPath_;
|
||||||
auto pathIt = pathMap.find(visualId);
|
auto& fallbackMap = useImpactKit ? spellVisualCastPath_ : spellVisualImpactPath_;
|
||||||
if (pathIt == pathMap.end()) {
|
auto pathIt = primaryMap.find(visualId);
|
||||||
LOG_WARNING("SpellVisual: no ", (useImpactKit ? "impact" : "cast"), " path for visualId=", visualId);
|
if (pathIt == primaryMap.end()) {
|
||||||
return;
|
pathIt = fallbackMap.find(visualId);
|
||||||
|
if (pathIt == fallbackMap.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& modelPath = pathIt->second;
|
const std::string& modelPath = pathIt->second;
|
||||||
|
|
|
||||||
|
|
@ -1055,7 +1055,12 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
|
||||||
size_t uploaded = 0;
|
size_t uploaded = 0;
|
||||||
while (ft.wmoDoodadIndex < pending->wmoDoodads.size() && uploaded < kDoodadsPerStep) {
|
while (ft.wmoDoodadIndex < pending->wmoDoodads.size() && uploaded < kDoodadsPerStep) {
|
||||||
auto& doodad = pending->wmoDoodads[ft.wmoDoodadIndex];
|
auto& doodad = pending->wmoDoodads[ft.wmoDoodadIndex];
|
||||||
if (m2Renderer->loadModel(doodad.model, doodad.modelId)) {
|
if (!m2Renderer->loadModel(doodad.model, doodad.modelId)) {
|
||||||
|
ft.wmoDoodadIndex++;
|
||||||
|
uploaded++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
{
|
||||||
std::lock_guard<std::mutex> lock(uploadedM2IdsMutex_);
|
std::lock_guard<std::mutex> lock(uploadedM2IdsMutex_);
|
||||||
uploadedM2Ids_.insert(doodad.modelId);
|
uploadedM2Ids_.insert(doodad.modelId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue