feat: track and display elemental resistances in character stats panel

This commit is contained in:
Kelsi 2026-03-12 12:24:15 -07:00
parent 0a2cd213dc
commit 60794c6e0f
4 changed files with 43 additions and 3 deletions

View file

@ -295,6 +295,13 @@ public:
// Server-authoritative armor (UNIT_FIELD_RESISTANCES[0])
int32_t getArmorRating() const { return playerArmorRating_; }
// Server-authoritative elemental resistances (UNIT_FIELD_RESISTANCES[1-6]).
// school: 1=Holy, 2=Fire, 3=Nature, 4=Frost, 5=Shadow, 6=Arcane. Returns 0 if not received.
int32_t getResistance(int school) const {
if (school < 1 || school > 6) return 0;
return playerResistances_[school - 1];
}
// Server-authoritative primary stats (UNIT_FIELD_STAT0-4: STR, AGI, STA, INT, SPI).
// Returns -1 if the server hasn't sent the value yet.
int32_t getPlayerStat(int idx) const {
@ -2436,6 +2443,7 @@ private:
std::unordered_map<uint64_t, float> recentLootMoneyAnnounceCooldowns_;
uint64_t playerMoneyCopper_ = 0;
int32_t playerArmorRating_ = 0;
int32_t playerResistances_[6] = {}; // [0]=Holy,[1]=Fire,[2]=Nature,[3]=Frost,[4]=Shadow,[5]=Arcane
// Server-authoritative primary stats: [0]=STR [1]=AGI [2]=STA [3]=INT [4]=SPI; -1 = not received yet
int32_t playerStats_[5] = {-1, -1, -1, -1, -1};
// Some servers/custom clients shift update field indices. We can auto-detect coinage by correlating

View file

@ -149,7 +149,7 @@ private:
void renderEquipmentPanel(game::Inventory& inventory);
void renderBackpackPanel(game::Inventory& inventory, bool collapseEmptySections = false);
void renderStatsPanel(game::Inventory& inventory, uint32_t playerLevel, int32_t serverArmor = 0,
const int32_t* serverStats = nullptr);
const int32_t* serverStats = nullptr, const int32_t* serverResists = nullptr);
void renderReputationPanel(game::GameHandler& gameHandler);
void renderItemSlot(game::Inventory& inventory, const game::ItemSlot& slot,

View file

@ -6703,6 +6703,7 @@ void GameHandler::selectCharacter(uint64_t characterGuid) {
onlineEquipDirty_ = false;
playerMoneyCopper_ = 0;
playerArmorRating_ = 0;
std::fill(std::begin(playerResistances_), std::end(playerResistances_), 0);
std::fill(std::begin(playerStats_), std::end(playerStats_), -1);
knownSpells.clear();
spellCooldowns.clear();
@ -8807,6 +8808,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
playerArmorRating_ = static_cast<int32_t>(val);
LOG_DEBUG("Armor rating from update fields: ", playerArmorRating_);
}
else if (ufArmor != 0xFFFF && key > ufArmor && key <= ufArmor + 6) {
playerResistances_[key - ufArmor - 1] = static_cast<int32_t>(val);
}
else if (ufPBytes2 != 0xFFFF && key == ufPBytes2) {
uint8_t bankBagSlots = static_cast<uint8_t>((val >> 16) & 0xFF);
LOG_WARNING("PLAYER_BYTES_2 (CREATE): raw=0x", std::hex, val, std::dec,
@ -9147,6 +9151,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
else if (ufArmor != 0xFFFF && key == ufArmor) {
playerArmorRating_ = static_cast<int32_t>(val);
}
else if (ufArmor != 0xFFFF && key > ufArmor && key <= ufArmor + 6) {
playerResistances_[key - ufArmor - 1] = static_cast<int32_t>(val);
}
else if (ufPBytes2v != 0xFFFF && key == ufPBytes2v) {
uint8_t bankBagSlots = static_cast<uint8_t>((val >> 16) & 0xFF);
LOG_WARNING("PLAYER_BYTES_2 (VALUES): raw=0x", std::hex, val, std::dec,

View file

@ -1159,7 +1159,9 @@ void InventoryScreen::renderCharacterScreen(game::GameHandler& gameHandler) {
int32_t stats[5];
for (int i = 0; i < 5; ++i) stats[i] = gameHandler.getPlayerStat(i);
const int32_t* serverStats = (stats[0] >= 0) ? stats : nullptr;
renderStatsPanel(inventory, gameHandler.getPlayerLevel(), gameHandler.getArmorRating(), serverStats);
int32_t resists[6];
for (int i = 0; i < 6; ++i) resists[i] = gameHandler.getResistance(i + 1);
renderStatsPanel(inventory, gameHandler.getPlayerLevel(), gameHandler.getArmorRating(), serverStats, resists);
// Played time (shown if available, fetched on character screen open)
uint32_t totalSec = gameHandler.getTotalTimePlayed();
@ -1557,7 +1559,8 @@ void InventoryScreen::renderEquipmentPanel(game::Inventory& inventory) {
// ============================================================
void InventoryScreen::renderStatsPanel(game::Inventory& inventory, uint32_t playerLevel,
int32_t serverArmor, const int32_t* serverStats) {
int32_t serverArmor, const int32_t* serverStats,
const int32_t* serverResists) {
// Sum equipment stats for item-query bonus display
int32_t itemStr = 0, itemAgi = 0, itemSta = 0, itemInt = 0, itemSpi = 0;
// Secondary stat sums from extraStats
@ -1665,6 +1668,28 @@ void InventoryScreen::renderStatsPanel(game::Inventory& inventory, uint32_t play
renderSecondary("Mana per 5 sec", itemMp5);
renderSecondary("Health per 5 sec",itemHp5);
}
// Elemental resistances from server update fields
if (serverResists) {
static const char* kResistNames[6] = {
"Holy Resistance", "Fire Resistance", "Nature Resistance",
"Frost Resistance", "Shadow Resistance", "Arcane Resistance"
};
bool hasResist = false;
for (int i = 0; i < 6; ++i) {
if (serverResists[i] > 0) { hasResist = true; break; }
}
if (hasResist) {
ImGui::Spacing();
ImGui::Separator();
for (int i = 0; i < 6; ++i) {
if (serverResists[i] > 0) {
ImGui::TextColored(ImVec4(0.7f, 0.85f, 1.0f, 1.0f),
"%s: %d", kResistNames[i], serverResists[i]);
}
}
}
}
}
void InventoryScreen::renderBackpackPanel(game::Inventory& inventory, bool collapseEmptySections) {