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:
Kelsi 2026-03-23 11:00:49 -07:00
parent d873f27070
commit 503f9ed650
7 changed files with 242 additions and 87 deletions

View file

@ -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);