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.
This commit is contained in:
Kelsi 2026-03-12 21:19:17 -07:00
parent 470421879a
commit e68ffbc711

View file

@ -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<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
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<uint16_t>(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<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
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<uint16_t>(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 &&