From 5d46028baf4464cd0c78d1ef75e8aef2c844b472 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 5 Feb 2026 22:54:47 -0800 Subject: [PATCH] Add hair textures and geosets for humanoid NPCs Set correct hair style and facial hair geosets based on CreatureDisplayInfoExtra. Load hair textures from CharSections.dbc using race, sex, hairStyleId, and hairColorId for proper hair coloring on humanoid NPCs. --- src/core/application.cpp | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/core/application.cpp b/src/core/application.cpp index b3199b24..97299942 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1777,6 +1777,7 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x if (itExtra != humanoidExtraMap_.end()) { const auto& extra = itExtra->second; LOG_DEBUG(" Found humanoid extra: raceId=", (int)extra.raceId, " sexId=", (int)extra.sexId, + " hairStyle=", (int)extra.hairStyleId, " hairColor=", (int)extra.hairColorId, " bakeName='", extra.bakeName, "'"); // Use baked texture if available (bakeName already includes .blp extension) if (!extra.bakeName.empty()) { @@ -1797,6 +1798,38 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x } else { LOG_DEBUG(" Humanoid extra has empty bakeName, trying CharSections fallback"); } + + // Load hair texture from CharSections.dbc (section 3) + auto charSectionsDbc = assetManager->loadDBC("CharSections.dbc"); + if (charSectionsDbc) { + for (uint32_t r = 0; r < charSectionsDbc->getRecordCount(); r++) { + uint32_t raceId = charSectionsDbc->getUInt32(r, 1); + uint32_t sexId = charSectionsDbc->getUInt32(r, 2); + uint32_t baseSection = charSectionsDbc->getUInt32(r, 3); + uint32_t variationIndex = charSectionsDbc->getUInt32(r, 8); + uint32_t colorIndex = charSectionsDbc->getUInt32(r, 9); + + // Section 3: Hair (variation = hair style, colorIndex = hair color) + if (baseSection == 3 && + raceId == extra.raceId && sexId == extra.sexId && + variationIndex == extra.hairStyleId && colorIndex == extra.hairColorId) { + std::string hairPath = charSectionsDbc->getString(r, 4); + if (!hairPath.empty()) { + GLuint hairTex = charRenderer->loadTexture(hairPath); + if (hairTex != 0) { + // Apply to type-6 texture slot (hair) + for (size_t ti = 0; ti < model.textures.size(); ti++) { + if (model.textures[ti].type == 6) { + charRenderer->setModelTexture(modelId, static_cast(ti), hairTex); + LOG_DEBUG("Applied hair texture: ", hairPath, " to slot ", ti); + } + } + } + } + break; + } + } + } } else { LOG_WARNING(" extraDisplayId ", dispData.extraDisplayId, " not found in humanoidExtraMap"); } @@ -1840,6 +1873,39 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x return; } + // Set geosets for humanoid NPCs based on CreatureDisplayInfoExtra + if (itDisplayData != displayDataMap_.end() && itDisplayData->second.extraDisplayId != 0) { + auto itExtra = humanoidExtraMap_.find(itDisplayData->second.extraDisplayId); + if (itExtra != humanoidExtraMap_.end()) { + const auto& extra = itExtra->second; + std::unordered_set activeGeosets; + + // Body parts (group 0: IDs 0-18) + for (uint16_t i = 0; i <= 18; i++) { + activeGeosets.insert(i); + } + + // Hair style geoset: 100 + hairStyleId + 1 (101 = style 0, 102 = style 1, etc.) + activeGeosets.insert(static_cast(101 + extra.hairStyleId)); + + // Facial hair geoset: 200 + facialHairId + 1 (201 = none/style 0, 202 = style 1, etc.) + activeGeosets.insert(static_cast(201 + extra.facialHairId)); + + // Equipment groups: bare (no armor) + activeGeosets.insert(301); // Gloves: bare hands + activeGeosets.insert(401); // Boots: bare feet + activeGeosets.insert(501); // Chest: bare + activeGeosets.insert(701); // Ears: default + activeGeosets.insert(1301); // Trousers: bare legs + activeGeosets.insert(1501); // Back body (cloak=none) + // Note: Do NOT add 1703 (DK eye glow) for normal NPCs + + charRenderer->setActiveGeosets(instanceId, activeGeosets); + LOG_DEBUG("Set humanoid geosets: hair=", 101 + extra.hairStyleId, + " facial=", 201 + extra.facialHairId); + } + } + // Play idle animation charRenderer->playAnimation(instanceId, 0, true);