diff --git a/Data/expansions/classic/update_fields.json b/Data/expansions/classic/update_fields.json index 5f97f29f..4549a48c 100644 --- a/Data/expansions/classic/update_fields.json +++ b/Data/expansions/classic/update_fields.json @@ -22,6 +22,7 @@ "PLAYER_BYTES_2": 192, "PLAYER_XP": 716, "PLAYER_NEXT_LEVEL_XP": 717, + "PLAYER_REST_STATE_EXPERIENCE": 718, "PLAYER_FIELD_COINAGE": 1176, "PLAYER_QUEST_LOG_START": 198, "PLAYER_FIELD_INV_SLOT_HEAD": 486, diff --git a/Data/expansions/tbc/update_fields.json b/Data/expansions/tbc/update_fields.json index bbcedec5..bee972ca 100644 --- a/Data/expansions/tbc/update_fields.json +++ b/Data/expansions/tbc/update_fields.json @@ -22,6 +22,7 @@ "PLAYER_BYTES_2": 238, "PLAYER_XP": 926, "PLAYER_NEXT_LEVEL_XP": 927, + "PLAYER_REST_STATE_EXPERIENCE": 928, "PLAYER_FIELD_COINAGE": 1441, "PLAYER_QUEST_LOG_START": 244, "PLAYER_FIELD_INV_SLOT_HEAD": 650, diff --git a/Data/expansions/turtle/update_fields.json b/Data/expansions/turtle/update_fields.json index 5f97f29f..393694a0 100644 --- a/Data/expansions/turtle/update_fields.json +++ b/Data/expansions/turtle/update_fields.json @@ -22,6 +22,7 @@ "PLAYER_BYTES_2": 192, "PLAYER_XP": 716, "PLAYER_NEXT_LEVEL_XP": 717, + "PLAYER_REST_STATE_EXPERIENCE": 718, "PLAYER_FIELD_COINAGE": 1176, "PLAYER_QUEST_LOG_START": 198, "PLAYER_FIELD_INV_SLOT_HEAD": 486, @@ -35,4 +36,4 @@ "ITEM_FIELD_STACK_COUNT": 14, "CONTAINER_FIELD_NUM_SLOTS": 48, "CONTAINER_FIELD_SLOT_1": 50 -} +} \ No newline at end of file diff --git a/Data/expansions/wotlk/update_fields.json b/Data/expansions/wotlk/update_fields.json index f308cf0d..fa4b9ada 100644 --- a/Data/expansions/wotlk/update_fields.json +++ b/Data/expansions/wotlk/update_fields.json @@ -22,6 +22,7 @@ "PLAYER_BYTES_2": 154, "PLAYER_XP": 634, "PLAYER_NEXT_LEVEL_XP": 635, + "PLAYER_REST_STATE_EXPERIENCE": 636, "PLAYER_FIELD_COINAGE": 1170, "PLAYER_QUEST_LOG_START": 158, "PLAYER_FIELD_INV_SLOT_HEAD": 324, diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 42481376..dc6c06e6 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -640,6 +640,8 @@ public: // XP tracking uint32_t getPlayerXp() const { return playerXp_; } uint32_t getPlayerNextLevelXp() const { return playerNextLevelXp_; } + uint32_t getPlayerRestedXp() const { return playerRestedXp_; } + bool isPlayerResting() const { return isResting_; } uint32_t getPlayerLevel() const { return serverPlayerLevel_; } const std::vector& getPlayerExploredZoneMasks() const { return playerExploredZones_; } bool hasPlayerExploredZoneMasks() const { return hasPlayerExploredZones_; } @@ -2199,6 +2201,8 @@ private: // ---- XP tracking ---- uint32_t playerXp_ = 0; uint32_t playerNextLevelXp_ = 0; + uint32_t playerRestedXp_ = 0; + bool isResting_ = false; uint32_t serverPlayerLevel_ = 1; static uint32_t xpForLevel(uint32_t level); diff --git a/include/game/update_field_table.hpp b/include/game/update_field_table.hpp index b841925e..fd208554 100644 --- a/include/game/update_field_table.hpp +++ b/include/game/update_field_table.hpp @@ -41,6 +41,7 @@ enum class UF : uint16_t { PLAYER_BYTES_2, PLAYER_XP, PLAYER_NEXT_LEVEL_XP, + PLAYER_REST_STATE_EXPERIENCE, PLAYER_FIELD_COINAGE, PLAYER_QUEST_LOG_START, PLAYER_FIELD_INV_SLOT_HEAD, diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index c6355e9c..877afa64 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4644,8 +4644,9 @@ void GameHandler::handlePacket(network::Packet& packet) { case Opcode::SMSG_SET_REST_START: { if (packet.getSize() - packet.getReadPos() >= 4) { uint32_t restTrigger = packet.readUInt32(); - addSystemChatMessage(restTrigger > 0 ? "You are now resting." - : "You are no longer resting."); + isResting_ = (restTrigger > 0); + addSystemChatMessage(isResting_ ? "You are now resting." + : "You are no longer resting."); } break; } @@ -7835,6 +7836,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { bool slotsChanged = false; const uint16_t ufPlayerXp = fieldIndex(UF::PLAYER_XP); const uint16_t ufPlayerNextXp = fieldIndex(UF::PLAYER_NEXT_LEVEL_XP); + const uint16_t ufPlayerRestedXp = fieldIndex(UF::PLAYER_REST_STATE_EXPERIENCE); 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); @@ -7842,6 +7844,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { for (const auto& [key, val] : block.fields) { if (key == ufPlayerXp) { playerXp_ = val; } else if (key == ufPlayerNextXp) { playerNextLevelXp_ = val; } + else if (ufPlayerRestedXp != 0xFFFF && key == ufPlayerRestedXp) { playerRestedXp_ = val; } else if (key == ufPlayerLevel) { serverPlayerLevel_ = val; for (auto& ch : characters) { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index f5a1bc1d..dffffbdc 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -4409,7 +4409,9 @@ void GameScreen::renderXpBar(game::GameHandler& gameHandler) { uint32_t nextLevelXp = gameHandler.getPlayerNextLevelXp(); if (nextLevelXp == 0) return; // No XP data yet (level 80 or not initialized) - uint32_t currentXp = gameHandler.getPlayerXp(); + uint32_t currentXp = gameHandler.getPlayerXp(); + uint32_t restedXp = gameHandler.getPlayerRestedXp(); + bool isResting = gameHandler.isPlayerResting(); auto* window = core::Application::getInstance().getWindow(); float screenW = window ? static_cast(window->getWidth()) : 1280.0f; float screenH = window ? static_cast(window->getHeight()) : 720.0f; @@ -4449,9 +4451,10 @@ void GameScreen::renderXpBar(game::GameHandler& gameHandler) { ImVec2 barMax = ImVec2(barMin.x + barSize.x, barMin.y + barSize.y); auto* drawList = ImGui::GetWindowDrawList(); - ImU32 bg = IM_COL32(15, 15, 20, 220); - ImU32 fg = IM_COL32(148, 51, 238, 255); - ImU32 seg = IM_COL32(35, 35, 45, 255); + ImU32 bg = IM_COL32(15, 15, 20, 220); + ImU32 fg = IM_COL32(148, 51, 238, 255); + ImU32 fgRest = IM_COL32(200, 170, 255, 220); // lighter purple for rested portion + ImU32 seg = IM_COL32(35, 35, 45, 255); drawList->AddRectFilled(barMin, barMax, bg, 2.0f); drawList->AddRect(barMin, barMax, IM_COL32(80, 80, 90, 220), 2.0f); @@ -4460,6 +4463,19 @@ void GameScreen::renderXpBar(game::GameHandler& gameHandler) { drawList->AddRectFilled(barMin, ImVec2(barMin.x + fillW, barMax.y), fg, 2.0f); } + // Rested XP overlay: draw from current XP fill to (currentXp + restedXp) fill + if (restedXp > 0) { + float restedEndPct = std::min(1.0f, static_cast(currentXp + restedXp) + / static_cast(nextLevelXp)); + float restedStartX = barMin.x + fillW; + float restedEndX = barMin.x + barSize.x * restedEndPct; + if (restedEndX > restedStartX) { + drawList->AddRectFilled(ImVec2(restedStartX, barMin.y), + ImVec2(restedEndX, barMax.y), + fgRest, 2.0f); + } + } + const int segments = 20; float segW = barSize.x / static_cast(segments); for (int i = 1; i < segments; ++i) { @@ -4467,8 +4483,21 @@ void GameScreen::renderXpBar(game::GameHandler& gameHandler) { drawList->AddLine(ImVec2(x, barMin.y + 1.0f), ImVec2(x, barMax.y - 1.0f), seg, 1.0f); } + // Rest indicator "zzz" to the right of the bar when resting + if (isResting) { + const char* zzz = "zzz"; + ImVec2 zSize = ImGui::CalcTextSize(zzz); + float zx = barMax.x - zSize.x - 4.0f; + float zy = barMin.y + (barSize.y - zSize.y) * 0.5f; + drawList->AddText(ImVec2(zx, zy), IM_COL32(180, 150, 255, 220), zzz); + } + char overlay[96]; - snprintf(overlay, sizeof(overlay), "%u / %u XP", currentXp, nextLevelXp); + if (restedXp > 0) { + snprintf(overlay, sizeof(overlay), "%u / %u XP (+%u rested)", currentXp, nextLevelXp, restedXp); + } else { + snprintf(overlay, sizeof(overlay), "%u / %u XP", currentXp, nextLevelXp); + } ImVec2 textSize = ImGui::CalcTextSize(overlay); float tx = barMin.x + (barSize.x - textSize.x) * 0.5f; float ty = barMin.y + (barSize.y - textSize.y) * 0.5f;