Fix armor stat in character stats panel via UNIT_FIELD_RESISTANCES

The character stats panel was showing Armor: 0 because summing armor
from item query responses is fragile (depends on correct BuyCount/damage
block parsing). Instead, read the server-authoritative total armor
directly from UNIT_FIELD_RESISTANCES (physical resistance, index 0)
in the player entity update fields.

Added UNIT_FIELD_RESISTANCES to the UF enum and all four expansion
JSON files with correct wire indices:
  WotLK 3.3.5a: 99   (NPC_FLAGS=82 + emotestate + stat×5 + posstat×5 + negstat×5)
  TBC 2.4.3:   185   (NPC_FLAGS=168 + same relative layout)
  Classic 1.12: 154  (NPC_FLAGS=147 + emotestate + stat×5, no posstat/negstat)
  Turtle WoW:  154   (same as Classic)

Stats panel uses server armor when > 0, falls back to summed item-query
armor otherwise. Armor rating resets to 0 on world entry and is updated
from both CREATE_OBJECT and VALUES update blocks.
This commit is contained in:
Kelsi 2026-02-19 17:45:09 -08:00
parent 05e2b37894
commit 20cdff0790
10 changed files with 33 additions and 5 deletions

View file

@ -2772,6 +2772,7 @@ void GameHandler::selectCharacter(uint64_t characterGuid) {
lastPlayerFields_.clear();
onlineEquipDirty_ = false;
playerMoneyCopper_ = 0;
playerArmorRating_ = 0;
knownSpells.clear();
spellCooldowns.clear();
actionBar = {};
@ -4427,6 +4428,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
const uint16_t ufPlayerNextXp = fieldIndex(UF::PLAYER_NEXT_LEVEL_XP);
const uint16_t ufPlayerLevel = fieldIndex(UF::UNIT_FIELD_LEVEL);
const uint16_t ufCoinage = fieldIndex(UF::PLAYER_FIELD_COINAGE);
const uint16_t ufArmor = fieldIndex(UF::UNIT_FIELD_RESISTANCES);
for (const auto& [key, val] : block.fields) {
if (key == ufPlayerXp) { playerXp_ = val; }
else if (key == ufPlayerNextXp) { playerNextLevelXp_ = val; }
@ -4440,6 +4442,10 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
playerMoneyCopper_ = val;
LOG_INFO("Money set from update fields: ", val, " copper");
}
else if (ufArmor != 0xFFFF && key == ufArmor) {
playerArmorRating_ = static_cast<int32_t>(val);
LOG_INFO("Armor rating from update fields: ", playerArmorRating_);
}
// Do not synthesize quest-log entries from raw update-field slots.
// Slot layouts differ on some classic-family realms and can produce
// phantom "already accepted" quests that block quest acceptance.
@ -4684,6 +4690,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
const uint16_t ufPlayerLevel = fieldIndex(UF::UNIT_FIELD_LEVEL);
const uint16_t ufCoinage = fieldIndex(UF::PLAYER_FIELD_COINAGE);
const uint16_t ufPlayerFlags = fieldIndex(UF::PLAYER_FLAGS);
const uint16_t ufArmor = fieldIndex(UF::UNIT_FIELD_RESISTANCES);
for (const auto& [key, val] : block.fields) {
if (key == ufPlayerXp) {
playerXp_ = val;
@ -4711,6 +4718,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
playerMoneyCopper_ = val;
LOG_INFO("Money updated via VALUES: ", val, " copper");
}
else if (ufArmor != 0xFFFF && key == ufArmor) {
playerArmorRating_ = static_cast<int32_t>(val);
}
else if (key == ufPlayerFlags) {
constexpr uint32_t PLAYER_FLAGS_GHOST = 0x00000010;
bool wasGhost = releasedSpirit_;