diff --git a/Data/expansions/wotlk/update_fields.json b/Data/expansions/wotlk/update_fields.json index 2f0a50a5..1628b94c 100644 --- a/Data/expansions/wotlk/update_fields.json +++ b/Data/expansions/wotlk/update_fields.json @@ -40,6 +40,8 @@ "PLAYER_SKILL_INFO_START": 636, "PLAYER_EXPLORED_ZONES_START": 1041, "PLAYER_CHOSEN_TITLE": 1349, + "PLAYER_FIELD_MOD_DAMAGE_DONE_POS": 1171, + "PLAYER_FIELD_MOD_HEALING_DONE_POS": 1192, "PLAYER_BLOCK_PERCENTAGE": 1024, "PLAYER_DODGE_PERCENTAGE": 1025, "PLAYER_PARRY_PERCENTAGE": 1026, diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index d9f5d0a8..86c8a43a 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -314,6 +314,18 @@ public: int32_t getMeleeAttackPower() const { return playerMeleeAP_; } int32_t getRangedAttackPower() const { return playerRangedAP_; } + // Server-authoritative spell damage / healing bonus (WotLK: PLAYER_FIELD_MOD_*). + // getSpellPower returns the max damage bonus across magic schools 1-6 (Holy/Fire/Nature/Frost/Shadow/Arcane). + // Returns -1 if not yet received. + int32_t getSpellPower() const { + int32_t sp = -1; + for (int i = 1; i <= 6; ++i) { + if (playerSpellDmgBonus_[i] > sp) sp = playerSpellDmgBonus_[i]; + } + return sp; + } + int32_t getHealingPower() const { return playerHealBonus_; } + // Server-authoritative combat chance percentages (WotLK: PLAYER_* float fields). // Returns -1.0f if not yet received. float getDodgePct() const { return playerDodgePct_; } @@ -2820,6 +2832,8 @@ private: // WotLK secondary combat stats (-1 = not yet received) int32_t playerMeleeAP_ = -1; int32_t playerRangedAP_ = -1; + int32_t playerSpellDmgBonus_[7] = {-1,-1,-1,-1,-1,-1,-1}; // per school 0-6 + int32_t playerHealBonus_ = -1; float playerDodgePct_ = -1.0f; float playerParryPct_ = -1.0f; float playerBlockPct_ = -1.0f; diff --git a/include/game/update_field_table.hpp b/include/game/update_field_table.hpp index 20a17016..5e42a049 100644 --- a/include/game/update_field_table.hpp +++ b/include/game/update_field_table.hpp @@ -63,6 +63,10 @@ enum class UF : uint16_t { PLAYER_EXPLORED_ZONES_START, PLAYER_CHOSEN_TITLE, // Active title index (-1 = no title) + // Player spell power / healing bonus (WotLK: PRIVATE — int32 per school) + PLAYER_FIELD_MOD_DAMAGE_DONE_POS, // Spell damage bonus (first of 7 schools) + PLAYER_FIELD_MOD_HEALING_DONE_POS, // Healing bonus + // Player combat stats (WotLK: PRIVATE — float values) PLAYER_BLOCK_PERCENTAGE, // Block chance % PLAYER_DODGE_PERCENTAGE, // Dodge chance % diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 720f2f96..cd6f58c1 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -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(val); } else if (ufRangedAP != 0xFFFF && key == ufRangedAP) { playerRangedAP_ = static_cast(val); } + else if (ufSpDmg1 != 0xFFFF && key >= ufSpDmg1 && key < ufSpDmg1 + 7) { + playerSpellDmgBonus_[key - ufSpDmg1] = static_cast(val); + } + else if (ufHealBonus != 0xFFFF && key == ufHealBonus) { playerHealBonus_ = static_cast(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(val); } else if (ufRangedAPV != 0xFFFF && key == ufRangedAPV) { playerRangedAP_ = static_cast(val); } + else if (ufSpDmg1V != 0xFFFF && key >= ufSpDmg1V && key < ufSpDmg1V + 7) { + playerSpellDmgBonus_[key - ufSpDmg1V] = static_cast(val); + } + else if (ufHealBonusV != 0xFFFF && key == ufHealBonusV) { playerHealBonus_ = static_cast(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); } diff --git a/src/game/update_field_table.cpp b/src/game/update_field_table.cpp index 4d9d8c66..ae45deb7 100644 --- a/src/game/update_field_table.cpp +++ b/src/game/update_field_table.cpp @@ -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}, diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index 98cf3482..247fa1cf 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -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);