From e68ffbc711b7c1a9f7654d665f18e8791c00515a Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 21:19:17 -0700 Subject: [PATCH] feat: populate Classic playerAuras from UNIT_FIELD_AURAS update fields Classic WoW (1.12) does not use SMSG_AURA_UPDATE like WotLK or TBC. Instead, active aura spell IDs are sent via 48 consecutive UNIT_FIELD_AURAS slots in SMSG_UPDATE_OBJECT CREATE_OBJECT and VALUES blocks. Previously these fields were only used for mount spell ID detection. Now on CREATE_OBJECT and VALUES updates for the player entity (Classic only), any changed UNIT_FIELD_AURAS slot triggers a full rebuild of playerAuras from the entity's accumulated field state, enabling the buff/debuff bar to display active auras for Classic players. --- src/game/game_handler.cpp | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 450c6f27..6ade8017 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -8924,6 +8924,37 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { if (ghostStateCallback_) ghostStateCallback_(true); } } + // Classic: rebuild playerAuras from UNIT_FIELD_AURAS on initial object create + if (block.guid == playerGuid && isClassicLikeExpansion()) { + const uint16_t ufAuras = fieldIndex(UF::UNIT_FIELD_AURAS); + if (ufAuras != 0xFFFF) { + bool hasAuraField = false; + for (const auto& [fk, fv] : block.fields) { + if (fk >= ufAuras && fk < ufAuras + 48) { hasAuraField = true; break; } + } + if (hasAuraField) { + playerAuras.clear(); + playerAuras.resize(48); + uint64_t nowMs = static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); + const auto& allFields = entity->getFields(); + for (int slot = 0; slot < 48; ++slot) { + auto it = allFields.find(static_cast(ufAuras + slot)); + if (it != allFields.end() && it->second != 0) { + AuraSlot& a = playerAuras[slot]; + a.spellId = it->second; + a.flags = 0; + a.durationMs = -1; + a.maxDurationMs = -1; + a.casterGuid = playerGuid; + a.receivedAtMs = nowMs; + } + } + LOG_DEBUG("[Classic] Rebuilt playerAuras from UNIT_FIELD_AURAS (CREATE_OBJECT)"); + } + } + } // Determine hostility from faction template for online creatures. // Always call isHostileFaction — factionTemplate=0 defaults to hostile // in the lookup rather than silently staying at the struct default (false). @@ -9337,6 +9368,38 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { } } + // Classic: sync playerAuras from UNIT_FIELD_AURAS when those fields are updated + if (block.guid == playerGuid && isClassicLikeExpansion()) { + const uint16_t ufAuras = fieldIndex(UF::UNIT_FIELD_AURAS); + if (ufAuras != 0xFFFF) { + bool hasAuraUpdate = false; + for (const auto& [fk, fv] : block.fields) { + if (fk >= ufAuras && fk < ufAuras + 48) { hasAuraUpdate = true; break; } + } + if (hasAuraUpdate) { + playerAuras.clear(); + playerAuras.resize(48); + uint64_t nowMs = static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); + const auto& allFields = entity->getFields(); + for (int slot = 0; slot < 48; ++slot) { + auto it = allFields.find(static_cast(ufAuras + slot)); + if (it != allFields.end() && it->second != 0) { + AuraSlot& a = playerAuras[slot]; + a.spellId = it->second; + a.flags = 0; + a.durationMs = -1; + a.maxDurationMs = -1; + a.casterGuid = playerGuid; + a.receivedAtMs = nowMs; + } + } + LOG_DEBUG("[Classic] Rebuilt playerAuras from UNIT_FIELD_AURAS (VALUES)"); + } + } + } + // Some units/players are created without displayId and get it later via VALUES. if ((entity->getType() == ObjectType::UNIT || entity->getType() == ObjectType::PLAYER) && displayIdChanged &&