mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-27 01:00:13 +00:00
fix: auto-detect CharSections.dbc layout and add Blood Elf/Draenei NPC voices
CharSections.dbc has different field layouts between stock WotLK (textures at field 4-6) and Classic/TBC/Turtle/HD-textured WotLK (VariationIndex at field 4). Add detectCharSectionsFields() that probes field-4 values at runtime to determine the correct layout, so both stock and modded clients work without JSON changes. Also add BLOODELF_MALE/FEMALE and DRAENEI_MALE/FEMALE voice types to the NPC voice system — previously all Blood Elf and Draenei NPCs fell through to GENERIC (random dwarf/gnome/night elf/orc mix).
This commit is contained in:
parent
d873f27070
commit
503f9ed650
7 changed files with 242 additions and 87 deletions
|
|
@ -38,6 +38,10 @@ enum class VoiceType {
|
|||
GNOME_FEMALE,
|
||||
GOBLIN_MALE,
|
||||
GOBLIN_FEMALE,
|
||||
BLOODELF_MALE,
|
||||
BLOODELF_FEMALE,
|
||||
DRAENEI_MALE,
|
||||
DRAENEI_FEMALE,
|
||||
GENERIC, // Fallback
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -57,5 +57,40 @@ inline uint32_t dbcField(const std::string& dbcName, const std::string& fieldNam
|
|||
return fm ? fm->field(fieldName) : 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
// Forward declaration
|
||||
class DBCFile;
|
||||
|
||||
/**
|
||||
* Resolved CharSections.dbc field indices.
|
||||
*
|
||||
* Stock WotLK 3.3.5a uses: Texture1=4, Texture2=5, Texture3=6, Flags=7,
|
||||
* VariationIndex=8, ColorIndex=9 (textures first).
|
||||
* Classic/TBC/Turtle and HD-texture WotLK use: VariationIndex=4, ColorIndex=5,
|
||||
* Texture1=6, Texture2=7, Texture3=8, Flags=9 (variation first).
|
||||
*
|
||||
* detectCharSectionsFields() auto-detects which layout the actual DBC uses
|
||||
* by sampling field-4 values: small integers (0-15) => variation-first,
|
||||
* large values (string offsets) => texture-first.
|
||||
*/
|
||||
struct CharSectionsFields {
|
||||
uint32_t raceId = 1;
|
||||
uint32_t sexId = 2;
|
||||
uint32_t baseSection = 3;
|
||||
uint32_t variationIndex = 4;
|
||||
uint32_t colorIndex = 5;
|
||||
uint32_t texture1 = 6;
|
||||
uint32_t texture2 = 7;
|
||||
uint32_t texture3 = 8;
|
||||
uint32_t flags = 9;
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect the actual CharSections.dbc field layout by probing record data.
|
||||
* @param dbc Loaded CharSections.dbc file (must not be null).
|
||||
* @param csL JSON-derived field map (may be null — defaults used).
|
||||
* @return Resolved field indices for this particular DBC binary.
|
||||
*/
|
||||
CharSectionsFields detectCharSectionsFields(const DBCFile* dbc, const DBCFieldMap* csL);
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -178,6 +178,30 @@ void NpcVoiceManager::loadVoiceSounds() {
|
|||
loadCategory(vendorLibrary_, VoiceType::UNDEAD_FEMALE, "UndeadFemaleStandardNPC", "Vendor", 2);
|
||||
loadCategory(pissedLibrary_, VoiceType::UNDEAD_FEMALE, "UndeadFemaleStandardNPC", "Pissed", 6);
|
||||
|
||||
// Blood Elf Male (TBC+ NPCBloodElfMaleStandard, sparse numbering up to 12)
|
||||
loadCategory(greetingLibrary_, VoiceType::BLOODELF_MALE, "NPCBloodElfMaleStandard", "Greeting", 12);
|
||||
loadCategory(farewellLibrary_, VoiceType::BLOODELF_MALE, "NPCBloodElfMaleStandard", "Farewell", 12);
|
||||
loadCategory(vendorLibrary_, VoiceType::BLOODELF_MALE, "NPCBloodElfMaleStandard", "Vendor", 6);
|
||||
loadCategory(pissedLibrary_, VoiceType::BLOODELF_MALE, "NPCBloodElfMaleStandard", "Pissed", 10);
|
||||
|
||||
// Blood Elf Female
|
||||
loadCategory(greetingLibrary_, VoiceType::BLOODELF_FEMALE, "NPCBloodElfFemaleStandard", "Greeting", 12);
|
||||
loadCategory(farewellLibrary_, VoiceType::BLOODELF_FEMALE, "NPCBloodElfFemaleStandard", "Farewell", 12);
|
||||
loadCategory(vendorLibrary_, VoiceType::BLOODELF_FEMALE, "NPCBloodElfFemaleStandard", "Vendor", 6);
|
||||
loadCategory(pissedLibrary_, VoiceType::BLOODELF_FEMALE, "NPCBloodElfFemaleStandard", "Pissed", 10);
|
||||
|
||||
// Draenei Male
|
||||
loadCategory(greetingLibrary_, VoiceType::DRAENEI_MALE, "NPCDraeneiMaleStandard", "Greeting", 12);
|
||||
loadCategory(farewellLibrary_, VoiceType::DRAENEI_MALE, "NPCDraeneiMaleStandard", "Farewell", 12);
|
||||
loadCategory(vendorLibrary_, VoiceType::DRAENEI_MALE, "NPCDraeneiMaleStandard", "Vendor", 6);
|
||||
loadCategory(pissedLibrary_, VoiceType::DRAENEI_MALE, "NPCDraeneiMaleStandard", "Pissed", 10);
|
||||
|
||||
// Draenei Female
|
||||
loadCategory(greetingLibrary_, VoiceType::DRAENEI_FEMALE, "NPCDraeneiFemaleStandard", "Greeting", 12);
|
||||
loadCategory(farewellLibrary_, VoiceType::DRAENEI_FEMALE, "NPCDraeneiFemaleStandard", "Farewell", 12);
|
||||
loadCategory(vendorLibrary_, VoiceType::DRAENEI_FEMALE, "NPCDraeneiFemaleStandard", "Vendor", 6);
|
||||
loadCategory(pissedLibrary_, VoiceType::DRAENEI_FEMALE, "NPCDraeneiFemaleStandard", "Pissed", 10);
|
||||
|
||||
// Load combat sounds from Character vocal files
|
||||
// These use a different path structure: Sound\Character\{Race}\{Race}Vocal{Gender}\{Race}{Gender}{Sound}.wav
|
||||
auto loadCombatCategory = [this](
|
||||
|
|
@ -251,6 +275,38 @@ void NpcVoiceManager::loadVoiceSounds() {
|
|||
|
||||
loadCombatCategory(aggroLibrary_, VoiceType::TROLL_FEMALE, "Troll", "TrollFemale", "AttackMyTarget", 3);
|
||||
loadCombatCategory(fleeLibrary_, VoiceType::TROLL_FEMALE, "Troll", "TrollFemale", "Flee", 2);
|
||||
|
||||
// Blood Elf and Draenei combat sounds (flat folder structure, no VocalMale/Female subfolder)
|
||||
auto loadCombatFlat = [this](
|
||||
std::unordered_map<VoiceType, std::vector<VoiceSample>>& library,
|
||||
VoiceType type,
|
||||
const std::string& raceFolder,
|
||||
const std::string& raceGender,
|
||||
const std::string& soundType,
|
||||
int count) {
|
||||
|
||||
auto& samples = library[type];
|
||||
for (int i = 1; i <= count; ++i) {
|
||||
std::string num = (i < 10) ? ("0" + std::to_string(i)) : std::to_string(i);
|
||||
std::string path = "Sound\\Character\\" + raceFolder + "\\" + raceGender + soundType + num + ".wav";
|
||||
VoiceSample sample;
|
||||
if (loadSound(path, sample)) samples.push_back(std::move(sample));
|
||||
}
|
||||
};
|
||||
|
||||
// Blood Elf combat sounds
|
||||
loadCombatFlat(aggroLibrary_, VoiceType::BLOODELF_MALE, "BloodElf", "BloodElfMale", "AttackMyTarget", 3);
|
||||
loadCombatFlat(fleeLibrary_, VoiceType::BLOODELF_MALE, "BloodElf", "BloodElfMale", "Flee", 3);
|
||||
|
||||
loadCombatFlat(aggroLibrary_, VoiceType::BLOODELF_FEMALE, "BloodElf", "BloodElfFemale", "AttackMyTarget", 3);
|
||||
loadCombatFlat(fleeLibrary_, VoiceType::BLOODELF_FEMALE, "BloodElf", "BloodElfFemale", "Flee", 3);
|
||||
|
||||
// Draenei combat sounds
|
||||
loadCombatFlat(aggroLibrary_, VoiceType::DRAENEI_MALE, "Draenei", "DraeneiMale", "AttackMyTarget", 3);
|
||||
loadCombatFlat(fleeLibrary_, VoiceType::DRAENEI_MALE, "Draenei", "DraeneiMale", "Flee", 3);
|
||||
|
||||
loadCombatFlat(aggroLibrary_, VoiceType::DRAENEI_FEMALE, "Draenei", "DraeneiFemale", "AttackMyTarget", 3);
|
||||
loadCombatFlat(fleeLibrary_, VoiceType::DRAENEI_FEMALE, "Draenei", "DraeneiFemale", "Flee", 3);
|
||||
}
|
||||
|
||||
bool NpcVoiceManager::loadSound(const std::string& path, VoiceSample& sample) {
|
||||
|
|
|
|||
|
|
@ -3666,23 +3666,23 @@ void Application::spawnPlayerCharacter() {
|
|||
if (charSectionsDbc) {
|
||||
LOG_INFO("CharSections.dbc loaded: ", charSectionsDbc->getRecordCount(), " records");
|
||||
const auto* csL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
auto csF = pipeline::detectCharSectionsFields(charSectionsDbc.get(), csL);
|
||||
bool foundSkin = false;
|
||||
bool foundUnderwear = false;
|
||||
bool foundFaceLower = false;
|
||||
bool foundHair = false;
|
||||
for (uint32_t r = 0; r < charSectionsDbc->getRecordCount(); r++) {
|
||||
uint32_t raceId = charSectionsDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t sexId = charSectionsDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t baseSection = charSectionsDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t raceId = charSectionsDbc->getUInt32(r, csF.raceId);
|
||||
uint32_t sexId = charSectionsDbc->getUInt32(r, csF.sexId);
|
||||
uint32_t baseSection = charSectionsDbc->getUInt32(r, csF.baseSection);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csF.colorIndex);
|
||||
|
||||
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
||||
|
||||
// Section 0 = skin: match by colorIndex = skin byte
|
||||
const uint32_t csTex1 = csL ? (*csL)["Texture1"] : 6;
|
||||
if (baseSection == 0 && !foundSkin && colorIndex == charSkinId) {
|
||||
std::string tex1 = charSectionsDbc->getString(r, csTex1);
|
||||
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
||||
if (!tex1.empty()) {
|
||||
bodySkinPath = tex1;
|
||||
foundSkin = true;
|
||||
|
|
@ -3692,7 +3692,7 @@ void Application::spawnPlayerCharacter() {
|
|||
// Section 3 = hair: match variation=hairStyle, color=hairColor
|
||||
else if (baseSection == 3 && !foundHair &&
|
||||
variationIndex == charHairStyleId && colorIndex == charHairColorId) {
|
||||
hairTexturePath = charSectionsDbc->getString(r, csTex1);
|
||||
hairTexturePath = charSectionsDbc->getString(r, csF.texture1);
|
||||
if (!hairTexturePath.empty()) {
|
||||
foundHair = true;
|
||||
LOG_INFO(" DBC hair texture: ", hairTexturePath,
|
||||
|
|
@ -3703,8 +3703,8 @@ void Application::spawnPlayerCharacter() {
|
|||
// Texture1 = face lower, Texture2 = face upper
|
||||
else if (baseSection == 1 && !foundFaceLower &&
|
||||
variationIndex == charFaceId && colorIndex == charSkinId) {
|
||||
std::string tex1 = charSectionsDbc->getString(r, csTex1);
|
||||
std::string tex2 = charSectionsDbc->getString(r, csTex1 + 1);
|
||||
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
||||
std::string tex2 = charSectionsDbc->getString(r, csF.texture2);
|
||||
if (!tex1.empty()) {
|
||||
faceLowerTexturePath = tex1;
|
||||
LOG_INFO(" DBC face lower: ", faceLowerTexturePath);
|
||||
|
|
@ -3717,7 +3717,7 @@ void Application::spawnPlayerCharacter() {
|
|||
}
|
||||
// Section 4 = underwear
|
||||
else if (baseSection == 4 && !foundUnderwear && colorIndex == charSkinId) {
|
||||
for (uint32_t f = csTex1; f <= csTex1 + 2; f++) {
|
||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||
std::string tex = charSectionsDbc->getString(r, f);
|
||||
if (!tex.empty()) {
|
||||
underwearPaths.push_back(tex);
|
||||
|
|
@ -5353,22 +5353,17 @@ void Application::buildCharSectionsCache() {
|
|||
if (!dbc) return;
|
||||
const auto* csL = pipeline::getActiveDBCLayout()
|
||||
? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
uint32_t raceF = csL ? (*csL)["RaceID"] : 1;
|
||||
uint32_t sexF = csL ? (*csL)["SexID"] : 2;
|
||||
uint32_t secF = csL ? (*csL)["BaseSection"] : 3;
|
||||
uint32_t varF = csL ? (*csL)["VariationIndex"] : 8;
|
||||
uint32_t colF = csL ? (*csL)["ColorIndex"] : 9;
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 4;
|
||||
auto csF = pipeline::detectCharSectionsFields(dbc.get(), csL);
|
||||
for (uint32_t r = 0; r < dbc->getRecordCount(); r++) {
|
||||
uint32_t race = dbc->getUInt32(r, raceF);
|
||||
uint32_t sex = dbc->getUInt32(r, sexF);
|
||||
uint32_t section = dbc->getUInt32(r, secF);
|
||||
uint32_t variation = dbc->getUInt32(r, varF);
|
||||
uint32_t color = dbc->getUInt32(r, colF);
|
||||
uint32_t race = dbc->getUInt32(r, csF.raceId);
|
||||
uint32_t sex = dbc->getUInt32(r, csF.sexId);
|
||||
uint32_t section = dbc->getUInt32(r, csF.baseSection);
|
||||
uint32_t variation = dbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t color = dbc->getUInt32(r, csF.colorIndex);
|
||||
// We only cache sections 0 (skin), 1 (face), 3 (hair), 4 (underwear)
|
||||
if (section != 0 && section != 1 && section != 3 && section != 4) continue;
|
||||
for (int ti = 0; ti < 3; ti++) {
|
||||
std::string tex = dbc->getString(r, tex1F + ti);
|
||||
std::string tex = dbc->getString(r, csF.texture1 + ti);
|
||||
if (tex.empty()) continue;
|
||||
// Key: race(8)|sex(4)|section(4)|variation(8)|color(8)|texIndex(2) packed into 64 bits
|
||||
uint64_t key = (static_cast<uint64_t>(race) << 26) |
|
||||
|
|
@ -5653,6 +5648,8 @@ audio::VoiceType Application::detectVoiceTypeFromDisplayId(uint32_t displayId) c
|
|||
case 6: raceName = "Tauren"; result = (sexId == 0) ? audio::VoiceType::TAUREN_MALE : audio::VoiceType::TAUREN_FEMALE; break;
|
||||
case 7: raceName = "Gnome"; result = (sexId == 0) ? audio::VoiceType::GNOME_MALE : audio::VoiceType::GNOME_FEMALE; break;
|
||||
case 8: raceName = "Troll"; result = (sexId == 0) ? audio::VoiceType::TROLL_MALE : audio::VoiceType::TROLL_FEMALE; break;
|
||||
case 10: raceName = "BloodElf"; result = (sexId == 0) ? audio::VoiceType::BLOODELF_MALE : audio::VoiceType::BLOODELF_FEMALE; break;
|
||||
case 11: raceName = "Draenei"; result = (sexId == 0) ? audio::VoiceType::DRAENEI_MALE : audio::VoiceType::DRAENEI_FEMALE; break;
|
||||
default: result = audio::VoiceType::GENERIC; break;
|
||||
}
|
||||
|
||||
|
|
@ -5952,6 +5949,7 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
|||
if (csDbc) {
|
||||
const auto* csL = pipeline::getActiveDBCLayout()
|
||||
? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
auto csF = pipeline::detectCharSectionsFields(csDbc.get(), csL);
|
||||
uint32_t npcRace = static_cast<uint32_t>(extraCopy.raceId);
|
||||
uint32_t npcSex = static_cast<uint32_t>(extraCopy.sexId);
|
||||
uint32_t npcSkin = static_cast<uint32_t>(extraCopy.skinId);
|
||||
|
|
@ -5960,23 +5958,22 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
|||
std::vector<std::string> npcUnderwear;
|
||||
|
||||
for (uint32_t r = 0; r < csDbc->getRecordCount(); r++) {
|
||||
uint32_t rId = csDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t sId = csDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t rId = csDbc->getUInt32(r, csF.raceId);
|
||||
uint32_t sId = csDbc->getUInt32(r, csF.sexId);
|
||||
if (rId != npcRace || sId != npcSex) continue;
|
||||
|
||||
uint32_t section = csDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t color = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 4;
|
||||
uint32_t section = csDbc->getUInt32(r, csF.baseSection);
|
||||
uint32_t variation = csDbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t color = csDbc->getUInt32(r, csF.colorIndex);
|
||||
|
||||
if (section == 0 && def.basePath.empty() && color == npcSkin) {
|
||||
def.basePath = csDbc->getString(r, tex1F);
|
||||
def.basePath = csDbc->getString(r, csF.texture1);
|
||||
} else if (section == 1 && npcFaceLower.empty() &&
|
||||
variation == npcFace && color == npcSkin) {
|
||||
npcFaceLower = csDbc->getString(r, tex1F);
|
||||
npcFaceUpper = csDbc->getString(r, tex1F + 1);
|
||||
npcFaceLower = csDbc->getString(r, csF.texture1);
|
||||
npcFaceUpper = csDbc->getString(r, csF.texture2);
|
||||
} else if (section == 4 && npcUnderwear.empty() && color == npcSkin) {
|
||||
for (uint32_t f = tex1F; f <= tex1F + 2; f++) {
|
||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||
std::string tex = csDbc->getString(r, f);
|
||||
if (!tex.empty()) npcUnderwear.push_back(tex);
|
||||
}
|
||||
|
|
@ -6074,20 +6071,21 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
|||
if (csDbc) {
|
||||
const auto* csL = pipeline::getActiveDBCLayout()
|
||||
? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
auto csF = pipeline::detectCharSectionsFields(csDbc.get(), csL);
|
||||
uint32_t targetRace = static_cast<uint32_t>(extraCopy.raceId);
|
||||
uint32_t targetSex = static_cast<uint32_t>(extraCopy.sexId);
|
||||
|
||||
for (uint32_t r = 0; r < csDbc->getRecordCount(); r++) {
|
||||
uint32_t raceId = csDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t sexId = csDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t raceId = csDbc->getUInt32(r, csF.raceId);
|
||||
uint32_t sexId = csDbc->getUInt32(r, csF.sexId);
|
||||
if (raceId != targetRace || sexId != targetSex) continue;
|
||||
uint32_t section = csDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t section = csDbc->getUInt32(r, csF.baseSection);
|
||||
if (section != 3) continue;
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t colorIdx = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
uint32_t variation = csDbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t colorIdx = csDbc->getUInt32(r, csF.colorIndex);
|
||||
if (variation != static_cast<uint32_t>(extraCopy.hairStyleId)) continue;
|
||||
if (colorIdx != static_cast<uint32_t>(extraCopy.hairColorId)) continue;
|
||||
def.hairTexturePath = csDbc->getString(r, csL ? (*csL)["Texture1"] : 4);
|
||||
def.hairTexturePath = csDbc->getString(r, csF.texture1);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -7194,9 +7192,9 @@ void Application::spawnOnlinePlayer(uint64_t guid,
|
|||
|
||||
if (auto charSectionsDbc = assetManager->loadDBC("CharSections.dbc"); charSectionsDbc && charSectionsDbc->isLoaded()) {
|
||||
const auto* csL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
auto csF = pipeline::detectCharSectionsFields(charSectionsDbc.get(), csL);
|
||||
uint32_t targetRaceId = raceId;
|
||||
uint32_t targetSexId = genderId;
|
||||
const uint32_t csTex1 = csL ? (*csL)["Texture1"] : 4;
|
||||
|
||||
bool foundSkin = false;
|
||||
bool foundUnderwear = false;
|
||||
|
|
@ -7204,31 +7202,31 @@ void Application::spawnOnlinePlayer(uint64_t guid,
|
|||
bool foundFaceLower = false;
|
||||
|
||||
for (uint32_t r = 0; r < charSectionsDbc->getRecordCount(); r++) {
|
||||
uint32_t rRace = charSectionsDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t rSex = charSectionsDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t baseSection = charSectionsDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
uint32_t rRace = charSectionsDbc->getUInt32(r, csF.raceId);
|
||||
uint32_t rSex = charSectionsDbc->getUInt32(r, csF.sexId);
|
||||
uint32_t baseSection = charSectionsDbc->getUInt32(r, csF.baseSection);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csF.colorIndex);
|
||||
|
||||
if (rRace != targetRaceId || rSex != targetSexId) continue;
|
||||
|
||||
if (baseSection == 0 && !foundSkin && colorIndex == skinId) {
|
||||
std::string tex1 = charSectionsDbc->getString(r, csTex1);
|
||||
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
||||
if (!tex1.empty()) { bodySkinPath = tex1; foundSkin = true; }
|
||||
} else if (baseSection == 3 && !foundHair &&
|
||||
variationIndex == hairStyleId && colorIndex == hairColorId) {
|
||||
hairTexturePath = charSectionsDbc->getString(r, csTex1);
|
||||
hairTexturePath = charSectionsDbc->getString(r, csF.texture1);
|
||||
if (!hairTexturePath.empty()) foundHair = true;
|
||||
} else if (baseSection == 4 && !foundUnderwear && colorIndex == skinId) {
|
||||
for (uint32_t f = csTex1; f <= csTex1 + 2; f++) {
|
||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||
std::string tex = charSectionsDbc->getString(r, f);
|
||||
if (!tex.empty()) underwearPaths.push_back(tex);
|
||||
}
|
||||
foundUnderwear = true;
|
||||
} else if (baseSection == 1 && !foundFaceLower &&
|
||||
variationIndex == faceId && colorIndex == skinId) {
|
||||
std::string tex1 = charSectionsDbc->getString(r, csTex1);
|
||||
std::string tex2 = charSectionsDbc->getString(r, csTex1 + 1);
|
||||
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
||||
std::string tex2 = charSectionsDbc->getString(r, csF.texture2);
|
||||
if (!tex1.empty()) faceLowerPath = tex1;
|
||||
if (!tex2.empty()) faceUpperPath = tex2;
|
||||
foundFaceLower = true;
|
||||
|
|
@ -8183,32 +8181,32 @@ void Application::processCreatureSpawnQueue(bool unlimited) {
|
|||
if (csDbc) {
|
||||
const auto* csL = pipeline::getActiveDBCLayout()
|
||||
? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
auto csF = pipeline::detectCharSectionsFields(csDbc.get(), csL);
|
||||
uint32_t nRace = static_cast<uint32_t>(he.raceId);
|
||||
uint32_t nSex = static_cast<uint32_t>(he.sexId);
|
||||
uint32_t nSkin = static_cast<uint32_t>(he.skinId);
|
||||
uint32_t nFace = static_cast<uint32_t>(he.faceId);
|
||||
for (uint32_t r = 0; r < csDbc->getRecordCount(); r++) {
|
||||
uint32_t rId = csDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t sId = csDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t rId = csDbc->getUInt32(r, csF.raceId);
|
||||
uint32_t sId = csDbc->getUInt32(r, csF.sexId);
|
||||
if (rId != nRace || sId != nSex) continue;
|
||||
uint32_t section = csDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t color = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 4;
|
||||
uint32_t section = csDbc->getUInt32(r, csF.baseSection);
|
||||
uint32_t variation = csDbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t color = csDbc->getUInt32(r, csF.colorIndex);
|
||||
if (section == 0 && color == nSkin) {
|
||||
std::string t = csDbc->getString(r, tex1F);
|
||||
std::string t = csDbc->getString(r, csF.texture1);
|
||||
if (!t.empty()) displaySkinPaths.push_back(t);
|
||||
} else if (section == 1 && variation == nFace && color == nSkin) {
|
||||
std::string t1 = csDbc->getString(r, tex1F);
|
||||
std::string t2 = csDbc->getString(r, tex1F + 1);
|
||||
std::string t1 = csDbc->getString(r, csF.texture1);
|
||||
std::string t2 = csDbc->getString(r, csF.texture2);
|
||||
if (!t1.empty()) displaySkinPaths.push_back(t1);
|
||||
if (!t2.empty()) displaySkinPaths.push_back(t2);
|
||||
} else if (section == 3 && variation == static_cast<uint32_t>(he.hairStyleId)
|
||||
&& color == static_cast<uint32_t>(he.hairColorId)) {
|
||||
std::string t = csDbc->getString(r, tex1F);
|
||||
std::string t = csDbc->getString(r, csF.texture1);
|
||||
if (!t.empty()) displaySkinPaths.push_back(t);
|
||||
} else if (section == 4 && color == nSkin) {
|
||||
for (uint32_t f = tex1F; f <= tex1F + 2; f++) {
|
||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||
std::string t = csDbc->getString(r, f);
|
||||
if (!t.empty()) displaySkinPaths.push_back(t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#include "pipeline/dbc_layout.hpp"
|
||||
#include "pipeline/dbc_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
|
@ -94,5 +96,69 @@ const DBCFieldMap* DBCLayout::getLayout(const std::string& dbcName) const {
|
|||
return (it != layouts_.end()) ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
CharSectionsFields detectCharSectionsFields(const DBCFile* dbc, const DBCFieldMap* csL) {
|
||||
// Cache: avoid re-probing the same DBC on every call.
|
||||
static const DBCFile* s_cachedDbc = nullptr;
|
||||
static CharSectionsFields s_cachedResult;
|
||||
if (dbc && dbc == s_cachedDbc) return s_cachedResult;
|
||||
|
||||
CharSectionsFields f;
|
||||
if (!dbc || dbc->getRecordCount() == 0) return f;
|
||||
|
||||
// Start from the JSON layout (or defaults matching Classic-style: variation-first)
|
||||
f.raceId = csL ? (*csL)["RaceID"] : 1;
|
||||
f.sexId = csL ? (*csL)["SexID"] : 2;
|
||||
f.baseSection = csL ? (*csL)["BaseSection"] : 3;
|
||||
f.variationIndex = csL ? (*csL)["VariationIndex"] : 4;
|
||||
f.colorIndex = csL ? (*csL)["ColorIndex"] : 5;
|
||||
f.texture1 = csL ? (*csL)["Texture1"] : 6;
|
||||
f.texture2 = csL ? (*csL)["Texture2"] : 7;
|
||||
f.texture3 = csL ? (*csL)["Texture3"] : 8;
|
||||
f.flags = csL ? (*csL)["Flags"] : 9;
|
||||
|
||||
// Auto-detect: probe the field that the JSON layout says is VariationIndex.
|
||||
// In Classic-style layout, VariationIndex (field 4) holds small integers 0-15.
|
||||
// In stock WotLK layout, field 4 is actually Texture1 (a string block offset, typically > 100).
|
||||
// Sample up to 20 records and check if all field-4 values are small integers.
|
||||
uint32_t probeField = f.variationIndex;
|
||||
if (probeField >= dbc->getFieldCount()) {
|
||||
s_cachedDbc = dbc;
|
||||
s_cachedResult = f;
|
||||
return f; // safety
|
||||
}
|
||||
|
||||
uint32_t sampleCount = std::min(dbc->getRecordCount(), 20u);
|
||||
uint32_t largeCount = 0;
|
||||
uint32_t smallCount = 0;
|
||||
for (uint32_t r = 0; r < sampleCount; r++) {
|
||||
uint32_t val = dbc->getUInt32(r, probeField);
|
||||
if (val > 50) {
|
||||
++largeCount;
|
||||
} else {
|
||||
++smallCount;
|
||||
}
|
||||
}
|
||||
|
||||
// If most sampled values are large, the JSON layout's VariationIndex field
|
||||
// actually contains string offsets => this is stock WotLK (texture-first).
|
||||
// Swap to texture-first layout: Tex1=4, Tex2=5, Tex3=6, Flags=7, Var=8, Color=9.
|
||||
if (largeCount > smallCount) {
|
||||
uint32_t base = probeField; // the field index the JSON calls VariationIndex (typically 4)
|
||||
f.texture1 = base;
|
||||
f.texture2 = base + 1;
|
||||
f.texture3 = base + 2;
|
||||
f.flags = base + 3;
|
||||
f.variationIndex = base + 4;
|
||||
f.colorIndex = base + 5;
|
||||
LOG_INFO("CharSections.dbc: detected stock WotLK layout (textures-first at field ", base, ")");
|
||||
} else {
|
||||
LOG_INFO("CharSections.dbc: detected Classic-style layout (variation-first at field ", probeField, ")");
|
||||
}
|
||||
|
||||
s_cachedDbc = dbc;
|
||||
s_cachedResult = f;
|
||||
return f;
|
||||
}
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -332,25 +332,21 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
|
|||
bool foundUnderwear = false;
|
||||
|
||||
const auto* csL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
auto csF = pipeline::detectCharSectionsFields(charSectionsDbc.get(), csL);
|
||||
|
||||
uint32_t fRace = csL ? (*csL)["RaceID"] : 1;
|
||||
uint32_t fSex = csL ? (*csL)["SexID"] : 2;
|
||||
uint32_t fBase = csL ? (*csL)["BaseSection"] : 3;
|
||||
uint32_t fVar = csL ? (*csL)["VariationIndex"] : 4;
|
||||
uint32_t fColor = csL ? (*csL)["ColorIndex"] : 5;
|
||||
for (uint32_t r = 0; r < charSectionsDbc->getRecordCount(); r++) {
|
||||
uint32_t raceId = charSectionsDbc->getUInt32(r, fRace);
|
||||
uint32_t sexId = charSectionsDbc->getUInt32(r, fSex);
|
||||
uint32_t baseSection = charSectionsDbc->getUInt32(r, fBase);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, fVar);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, fColor);
|
||||
uint32_t raceId = charSectionsDbc->getUInt32(r, csF.raceId);
|
||||
uint32_t sexId = charSectionsDbc->getUInt32(r, csF.sexId);
|
||||
uint32_t baseSection = charSectionsDbc->getUInt32(r, csF.baseSection);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csF.colorIndex);
|
||||
|
||||
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
||||
|
||||
// Section 0: Body skin (variation=0, colorIndex = skin color)
|
||||
if (baseSection == 0 && !foundSkin &&
|
||||
variationIndex == 0 && colorIndex == static_cast<uint32_t>(skin)) {
|
||||
std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 6);
|
||||
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
||||
if (!tex1.empty()) {
|
||||
bodySkinPath_ = tex1;
|
||||
foundSkin = true;
|
||||
|
|
@ -360,8 +356,8 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
|
|||
else if (baseSection == 1 && !foundFace &&
|
||||
variationIndex == static_cast<uint32_t>(face) &&
|
||||
colorIndex == static_cast<uint32_t>(skin)) {
|
||||
std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 6);
|
||||
std::string tex2 = charSectionsDbc->getString(r, csL ? (*csL)["Texture2"] : 7);
|
||||
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
||||
std::string tex2 = charSectionsDbc->getString(r, csF.texture2);
|
||||
if (!tex1.empty()) faceLowerPath = tex1;
|
||||
if (!tex2.empty()) faceUpperPath = tex2;
|
||||
foundFace = true;
|
||||
|
|
@ -370,7 +366,7 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
|
|||
else if (baseSection == 3 && !foundHair &&
|
||||
variationIndex == static_cast<uint32_t>(hairStyle) &&
|
||||
colorIndex == static_cast<uint32_t>(hairColor)) {
|
||||
std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 6);
|
||||
std::string tex1 = charSectionsDbc->getString(r, csF.texture1);
|
||||
if (!tex1.empty()) {
|
||||
hairScalpPath = tex1;
|
||||
foundHair = true;
|
||||
|
|
@ -379,8 +375,7 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
|
|||
// Section 4: Underwear (variation=0, colorIndex = skin color)
|
||||
else if (baseSection == 4 && !foundUnderwear &&
|
||||
variationIndex == 0 && colorIndex == static_cast<uint32_t>(skin)) {
|
||||
uint32_t texBase = csL ? (*csL)["Texture1"] : 6;
|
||||
for (uint32_t f = texBase; f <= texBase + 2; f++) {
|
||||
for (uint32_t f = csF.texture1; f <= csF.texture1 + 2; f++) {
|
||||
std::string tex = charSectionsDbc->getString(r, f);
|
||||
if (!tex.empty()) {
|
||||
underwearPaths.push_back(tex);
|
||||
|
|
|
|||
|
|
@ -249,16 +249,17 @@ void CharacterCreateScreen::updateAppearanceRanges() {
|
|||
uint32_t targetSexId = (genderIndex == 1) ? 1u : 0u;
|
||||
|
||||
const auto* csL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
auto csF = pipeline::detectCharSectionsFields(dbc.get(), csL);
|
||||
int skinMax = -1;
|
||||
int hairStyleMax = -1;
|
||||
for (uint32_t r = 0; r < dbc->getRecordCount(); r++) {
|
||||
uint32_t raceId = dbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t sexId = dbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t raceId = dbc->getUInt32(r, csF.raceId);
|
||||
uint32_t sexId = dbc->getUInt32(r, csF.sexId);
|
||||
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
||||
|
||||
uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t baseSection = dbc->getUInt32(r, csF.baseSection);
|
||||
uint32_t variationIndex = dbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t colorIndex = dbc->getUInt32(r, csF.colorIndex);
|
||||
|
||||
if (baseSection == 0 && variationIndex == 0) {
|
||||
skinMax = std::max(skinMax, static_cast<int>(colorIndex));
|
||||
|
|
@ -279,13 +280,13 @@ void CharacterCreateScreen::updateAppearanceRanges() {
|
|||
int faceMax = -1;
|
||||
std::vector<uint8_t> hairColorIds;
|
||||
for (uint32_t r = 0; r < dbc->getRecordCount(); r++) {
|
||||
uint32_t raceId = dbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t sexId = dbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t raceId = dbc->getUInt32(r, csF.raceId);
|
||||
uint32_t sexId = dbc->getUInt32(r, csF.sexId);
|
||||
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
||||
|
||||
uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t baseSection = dbc->getUInt32(r, csF.baseSection);
|
||||
uint32_t variationIndex = dbc->getUInt32(r, csF.variationIndex);
|
||||
uint32_t colorIndex = dbc->getUInt32(r, csF.colorIndex);
|
||||
|
||||
if (baseSection == 1 && colorIndex == static_cast<uint32_t>(skin)) {
|
||||
faceMax = std::max(faceMax, static_cast<int>(variationIndex));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue