feat: add spell power and healing bonus to WotLK character stats

Tracks PLAYER_FIELD_MOD_DAMAGE_DONE_POS (7 schools at field 1171) and
PLAYER_FIELD_MOD_HEALING_DONE_POS (field 1192) from server update fields.

getSpellPower() returns the max damage bonus across magic schools 1-6.
getHealingPower() returns the raw healing bonus.

Both values displayed in the character screen Combat section alongside
the previously added attack power, dodge, parry, crit, and rating fields.
This commit is contained in:
Kelsi 2026-03-13 08:37:55 -07:00
parent c0ffca68f2
commit 2b79f9d121
6 changed files with 45 additions and 4 deletions

View file

@ -8197,6 +8197,8 @@ void GameHandler::selectCharacter(uint64_t characterGuid) {
std::fill(std::begin(playerStats_), std::end(playerStats_), -1);
playerMeleeAP_ = -1;
playerRangedAP_ = -1;
std::fill(std::begin(playerSpellDmgBonus_), std::end(playerSpellDmgBonus_), -1);
playerHealBonus_ = -1;
playerDodgePct_ = -1.0f;
playerParryPct_ = -1.0f;
playerBlockPct_ = -1.0f;
@ -10385,6 +10387,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
};
const uint16_t ufMeleeAP = fieldIndex(UF::UNIT_FIELD_ATTACK_POWER);
const uint16_t ufRangedAP = fieldIndex(UF::UNIT_FIELD_RANGED_ATTACK_POWER);
const uint16_t ufSpDmg1 = fieldIndex(UF::PLAYER_FIELD_MOD_DAMAGE_DONE_POS);
const uint16_t ufHealBonus = fieldIndex(UF::PLAYER_FIELD_MOD_HEALING_DONE_POS);
const uint16_t ufBlockPct = fieldIndex(UF::PLAYER_BLOCK_PERCENTAGE);
const uint16_t ufDodgePct = fieldIndex(UF::PLAYER_DODGE_PERCENTAGE);
const uint16_t ufParryPct = fieldIndex(UF::PLAYER_PARRY_PERCENTAGE);
@ -10429,6 +10433,10 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
}
else if (ufMeleeAP != 0xFFFF && key == ufMeleeAP) { playerMeleeAP_ = static_cast<int32_t>(val); }
else if (ufRangedAP != 0xFFFF && key == ufRangedAP) { playerRangedAP_ = static_cast<int32_t>(val); }
else if (ufSpDmg1 != 0xFFFF && key >= ufSpDmg1 && key < ufSpDmg1 + 7) {
playerSpellDmgBonus_[key - ufSpDmg1] = static_cast<int32_t>(val);
}
else if (ufHealBonus != 0xFFFF && key == ufHealBonus) { playerHealBonus_ = static_cast<int32_t>(val); }
else if (ufBlockPct != 0xFFFF && key == ufBlockPct) { std::memcpy(&playerBlockPct_, &val, 4); }
else if (ufDodgePct != 0xFFFF && key == ufDodgePct) { std::memcpy(&playerDodgePct_, &val, 4); }
else if (ufParryPct != 0xFFFF && key == ufParryPct) { std::memcpy(&playerParryPct_, &val, 4); }
@ -10799,6 +10807,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
};
const uint16_t ufMeleeAPV = fieldIndex(UF::UNIT_FIELD_ATTACK_POWER);
const uint16_t ufRangedAPV = fieldIndex(UF::UNIT_FIELD_RANGED_ATTACK_POWER);
const uint16_t ufSpDmg1V = fieldIndex(UF::PLAYER_FIELD_MOD_DAMAGE_DONE_POS);
const uint16_t ufHealBonusV= fieldIndex(UF::PLAYER_FIELD_MOD_HEALING_DONE_POS);
const uint16_t ufBlockPctV = fieldIndex(UF::PLAYER_BLOCK_PERCENTAGE);
const uint16_t ufDodgePctV = fieldIndex(UF::PLAYER_DODGE_PERCENTAGE);
const uint16_t ufParryPctV = fieldIndex(UF::PLAYER_PARRY_PERCENTAGE);
@ -10872,6 +10882,10 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
}
else if (ufMeleeAPV != 0xFFFF && key == ufMeleeAPV) { playerMeleeAP_ = static_cast<int32_t>(val); }
else if (ufRangedAPV != 0xFFFF && key == ufRangedAPV) { playerRangedAP_ = static_cast<int32_t>(val); }
else if (ufSpDmg1V != 0xFFFF && key >= ufSpDmg1V && key < ufSpDmg1V + 7) {
playerSpellDmgBonus_[key - ufSpDmg1V] = static_cast<int32_t>(val);
}
else if (ufHealBonusV != 0xFFFF && key == ufHealBonusV) { playerHealBonus_ = static_cast<int32_t>(val); }
else if (ufBlockPctV != 0xFFFF && key == ufBlockPctV) { std::memcpy(&playerBlockPct_, &val, 4); }
else if (ufDodgePctV != 0xFFFF && key == ufDodgePctV) { std::memcpy(&playerDodgePct_, &val, 4); }
else if (ufParryPctV != 0xFFFF && key == ufParryPctV) { std::memcpy(&playerParryPct_, &val, 4); }

View file

@ -64,6 +64,8 @@ static const UFNameEntry kUFNames[] = {
{"ITEM_FIELD_MAXDURABILITY", UF::ITEM_FIELD_MAXDURABILITY},
{"PLAYER_REST_STATE_EXPERIENCE", UF::PLAYER_REST_STATE_EXPERIENCE},
{"PLAYER_CHOSEN_TITLE", UF::PLAYER_CHOSEN_TITLE},
{"PLAYER_FIELD_MOD_DAMAGE_DONE_POS", UF::PLAYER_FIELD_MOD_DAMAGE_DONE_POS},
{"PLAYER_FIELD_MOD_HEALING_DONE_POS", UF::PLAYER_FIELD_MOD_HEALING_DONE_POS},
{"PLAYER_BLOCK_PERCENTAGE", UF::PLAYER_BLOCK_PERCENTAGE},
{"PLAYER_DODGE_PERCENTAGE", UF::PLAYER_DODGE_PERCENTAGE},
{"PLAYER_PARRY_PERCENTAGE", UF::PLAYER_PARRY_PERCENTAGE},

View file

@ -1780,9 +1780,11 @@ void InventoryScreen::renderStatsPanel(game::Inventory& inventory, uint32_t play
// Server-authoritative combat stats (WotLK update fields — only shown when received)
if (gh) {
int32_t meleeAP = gh->getMeleeAttackPower();
int32_t rangedAP = gh->getRangedAttackPower();
float dodgePct = gh->getDodgePct();
int32_t meleeAP = gh->getMeleeAttackPower();
int32_t rangedAP = gh->getRangedAttackPower();
int32_t spellPow = gh->getSpellPower();
int32_t healPow = gh->getHealingPower();
float dodgePct = gh->getDodgePct();
float parryPct = gh->getParryPct();
float blockPct = gh->getBlockPct();
float critPct = gh->getCritPct();
@ -1795,7 +1797,7 @@ void InventoryScreen::renderStatsPanel(game::Inventory& inventory, uint32_t play
int32_t armorPenR = gh->getCombatRating(24);
int32_t resilR = gh->getCombatRating(14); // CR_CRIT_TAKEN_MELEE = Resilience
bool hasAny = (meleeAP >= 0 || dodgePct >= 0.0f || parryPct >= 0.0f ||
bool hasAny = (meleeAP >= 0 || spellPow >= 0 || dodgePct >= 0.0f || parryPct >= 0.0f ||
blockPct >= 0.0f || critPct >= 0.0f || hitRating >= 0);
if (hasAny) {
ImGui::Spacing();
@ -1805,6 +1807,9 @@ void InventoryScreen::renderStatsPanel(game::Inventory& inventory, uint32_t play
if (meleeAP >= 0) ImGui::TextColored(cyan, "Attack Power: %d", meleeAP);
if (rangedAP >= 0 && rangedAP != meleeAP)
ImGui::TextColored(cyan, "Ranged Attack Power: %d", rangedAP);
if (spellPow >= 0) ImGui::TextColored(cyan, "Spell Power: %d", spellPow);
if (healPow >= 0 && healPow != spellPow)
ImGui::TextColored(cyan, "Healing Power: %d", healPow);
if (dodgePct >= 0.0f) ImGui::TextColored(cyan, "Dodge: %.2f%%", dodgePct);
if (parryPct >= 0.0f) ImGui::TextColored(cyan, "Parry: %.2f%%", parryPct);
if (blockPct >= 0.0f) ImGui::TextColored(cyan, "Block: %.2f%%", blockPct);