diff --git a/src/core/application.cpp b/src/core/application.cpp index f047cc72..368a8a25 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -834,11 +834,38 @@ void Application::spawnPlayerCharacter() { LOG_WARNING("Failed to load CharSections.dbc, using hardcoded textures"); } + // Look up hair texture from CharSections.dbc section 3 + std::string hairTexturePath; + if (gameHandler) { + const game::Character* activeChar = gameHandler->getActiveCharacter(); + if (activeChar) { + uint8_t hairStyleId = (activeChar->appearanceBytes >> 16) & 0xFF; + uint8_t hairColorId = (activeChar->appearanceBytes >> 24) & 0xFF; + 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 section = charSectionsDbc->getUInt32(r, 3); + uint32_t variation = charSectionsDbc->getUInt32(r, 8); + uint32_t colorIdx = charSectionsDbc->getUInt32(r, 9); + if (raceId != targetRaceId || sexId != targetSexId) continue; + if (section != 3) continue; + if (variation != hairStyleId) continue; + if (colorIdx != hairColorId) continue; + hairTexturePath = charSectionsDbc->getString(r, 4); + LOG_INFO(" DBC hair texture: ", hairTexturePath, + " (style=", (int)hairStyleId, " color=", (int)hairColorId, ")"); + break; + } + } + } + for (auto& tex : model.textures) { if (tex.type == 1 && tex.filename.empty()) { tex.filename = bodySkinPath; } else if (tex.type == 6 && tex.filename.empty()) { - tex.filename = "Character\\Human\\Hair00_00.blp"; + tex.filename = hairTexturePath.empty() + ? "Character\\Human\\Hair00_00.blp" + : hairTexturePath; } else if (tex.type == 8 && tex.filename.empty()) { if (!underwearPaths.empty()) { tex.filename = underwearPaths[0]; diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 8215121e..409064db 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -714,16 +714,16 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock // Spline data if (moveFlags & 0x08000000) { // MOVEMENTFLAG_SPLINE_ENABLED - // Skip spline data for now - complex structure uint32_t splineFlags = packet.readUInt32(); + LOG_DEBUG(" Spline: flags=0x", std::hex, splineFlags, std::dec); - if (splineFlags & 0x00010000) { // has final point + if (splineFlags & 0x00010000) { // SPLINEFLAG_FINAL_POINT /*float finalX =*/ packet.readFloat(); /*float finalY =*/ packet.readFloat(); /*float finalZ =*/ packet.readFloat(); - } else if (splineFlags & 0x00020000) { // has final target + } else if (splineFlags & 0x00020000) { // SPLINEFLAG_FINAL_TARGET /*uint64_t finalTarget =*/ packet.readUInt64(); - } else if (splineFlags & 0x00040000) { // has final angle + } else if (splineFlags & 0x00040000) { // SPLINEFLAG_FINAL_ANGLE /*float finalAngle =*/ packet.readFloat(); } @@ -735,10 +735,16 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock /*float durationModNext =*/ packet.readFloat(); /*float verticalAccel =*/ packet.readFloat(); - /*uint32_t effectStartTime =*/ packet.readUInt32(); uint32_t pointCount = packet.readUInt32(); + if (pointCount > 256) { + LOG_WARNING(" Spline pointCount=", pointCount, " exceeds maximum, capping at 0 (readPos=", + packet.getReadPos(), "/", packet.getSize(), ")"); + pointCount = 0; + } else { + LOG_DEBUG(" Spline pointCount=", pointCount); + } for (uint32_t i = 0; i < pointCount; i++) { /*float px =*/ packet.readFloat(); /*float py =*/ packet.readFloat(); diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index cd34f8d6..46de569a 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -1272,17 +1272,33 @@ void CharacterRenderer::render(const Camera& camera, const glm::mat4& view, cons } } - // For body parts with white/fallback texture, use first valid texture + // For body parts with white/fallback texture, use skin (type 1) texture // This handles humanoid models where some body parts use different texture slots // that may not be set (e.g., baked NPC textures only set slot 0) + // Only apply to body skin slots (type 1), NOT hair (type 6) or other types if (texId == whiteTexture) { uint16_t group = batch.submeshId / 100; if (group == 0) { - // Find first non-white texture in the model - for (GLuint tid : gpuModel.textureIds) { - if (tid != whiteTexture && tid != 0) { - texId = tid; - break; + // Check if this batch's texture slot is a body skin type + uint32_t texType = 0; + if (batch.textureIndex < gpuModel.data.textureLookup.size()) { + uint16_t lk = gpuModel.data.textureLookup[batch.textureIndex]; + if (lk < gpuModel.data.textures.size()) { + texType = gpuModel.data.textures[lk].type; + } + } + // Only fall back for body skin (type 1), underwear (type 8), or cloak (type 2) + // Do NOT apply skin composite to hair (type 6) batches + if (texType != 6) { + for (size_t ti = 0; ti < gpuModel.textureIds.size(); ti++) { + if (gpuModel.textureIds[ti] != whiteTexture && gpuModel.textureIds[ti] != 0) { + // Only use type 1 (skin) textures as fallback + if (ti < gpuModel.data.textures.size() && + (gpuModel.data.textures[ti].type == 1 || gpuModel.data.textures[ti].type == 11)) { + texId = gpuModel.textureIds[ti]; + break; + } + } } } } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 473a6a1d..e240913e 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -1709,7 +1709,7 @@ void GameScreen::renderLootWindow(game::GameHandler& gameHandler) { auto* window = core::Application::getInstance().getWindow(); float screenW = window ? static_cast(window->getWidth()) : 1280.0f; - ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 200), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 150, 200), ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_Always); bool open = true; @@ -1760,7 +1760,7 @@ void GameScreen::renderGossipWindow(game::GameHandler& gameHandler) { auto* window = core::Application::getInstance().getWindow(); float screenW = window ? static_cast(window->getWidth()) : 1280.0f; - ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 150), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 150), ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2(400, 0), ImGuiCond_Always); bool open = true; @@ -1909,8 +1909,8 @@ void GameScreen::renderVendorWindow(game::GameHandler& gameHandler) { auto* window = core::Application::getInstance().getWindow(); float screenW = window ? static_cast(window->getWidth()) : 1280.0f; - ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 100), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(450, 400), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(screenW / 2 - 200, 100), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(450, 400), ImGuiCond_Appearing); bool open = true; if (ImGui::Begin("Vendor", &open)) {