From 0d002c9070aa9dc2d229e32e1f74fdc1627ce782 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 11 Mar 2026 09:56:04 -0700 Subject: [PATCH] feat: enable NPC helmet attachments with fallback logic for missing attachment points Add fallback logic to use bone 0 for head attachment point (ID 11) when models don't have it explicitly defined. This improves helmet rendering compatibility on humanoid NPC models that lack explicit attachment 11 definitions. Re-enable helmet attachments now that the fallback logic is in place. --- src/core/application.cpp | 7 ++++--- src/rendering/character_renderer.cpp | 10 +++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/core/application.cpp b/src/core/application.cpp index 3b8e9cbe..9c4e879d 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -6142,9 +6142,10 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x " sleeves=", geosetSleeves, " pants=", geosetPants, " boots=", geosetBoots, " gloves=", geosetGloves); - // TODO(#helmet-attach): NPC helmet attachment anchors are currently unreliable - // on some humanoid models (floating/incorrect bone bind). Keep hidden for now. - static constexpr bool kEnableNpcHelmetAttachmentsMainPath = false; + // NOTE: NPC helmet attachment with fallback logic to use bone 0 if attachment + // point 11 is missing. This improves compatibility with models that don't have + // attachment 11 explicitly defined. + static constexpr bool kEnableNpcHelmetAttachmentsMainPath = true; // Load and attach helmet model if equipped if (kEnableNpcHelmetAttachmentsMainPath && extra.equipDisplayId[0] != 0 && itemDisplayDbc) { int32_t helmIdx = itemDisplayDbc->findRecordById(extra.equipDisplayId[0]); diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 82e4ff89..17330fe5 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -3072,7 +3072,7 @@ bool CharacterRenderer::attachWeapon(uint32_t charInstanceId, uint32_t attachmen } } } - // Fallback to key-bone lookup only for weapon hand attachment IDs. + // Fallback to key-bone lookup for weapon hand attachment IDs. if (!found && (attachmentId == 1 || attachmentId == 2)) { int32_t targetKeyBone = (attachmentId == 1) ? 26 : 27; for (size_t i = 0; i < charModel.bones.size(); i++) { @@ -3084,6 +3084,14 @@ bool CharacterRenderer::attachWeapon(uint32_t charInstanceId, uint32_t attachmen } } + // Fallback for head attachment (ID 11): try common head bone indices + // Some models may not have attachment 11 defined, but have bone 0 or 1 as head + if (!found && attachmentId == 11 && charModel.bones.size() > 0) { + // Try bone 0 first (common for head in many humanoid models) + boneIndex = 0; + found = true; + } + // Validate bone index (bad attachment tables should not silently bind to origin) if (found && boneIndex >= charModel.bones.size()) { found = false;