From 46eb66b77f2e554cb3af93f694fa8faafa622c88 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 03:03:02 -0700 Subject: [PATCH] Store and display achievement criteria progress from SMSG_CRITERIA_UPDATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Track criteria progress (criteriaId → counter) from SMSG_CRITERIA_UPDATE and SMSG_ALL_ACHIEVEMENT_DATA. Add a Criteria tab to the achievement window showing live progress values alongside the existing Earned achievements tab. --- include/game/game_handler.hpp | 3 ++ src/game/game_handler.cpp | 15 +++--- src/ui/game_screen.cpp | 97 +++++++++++++++++++++++------------ 3 files changed, 75 insertions(+), 40 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 495e31ed..c708c2be 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1294,6 +1294,7 @@ public: using AchievementEarnedCallback = std::function; void setAchievementEarnedCallback(AchievementEarnedCallback cb) { achievementEarnedCallback_ = std::move(cb); } const std::unordered_set& getEarnedAchievements() const { return earnedAchievements_; } + const std::unordered_map& getCriteriaProgress() const { return criteriaProgress_; } /// Returns the name of an achievement by ID, or empty string if unknown. const std::string& getAchievementName(uint32_t id) const { auto it = achievementNameCache_.find(id); @@ -2439,6 +2440,8 @@ private: void loadAchievementNameCache(); // Set of achievement IDs earned by the player (populated from SMSG_ALL_ACHIEVEMENT_DATA) std::unordered_set earnedAchievements_; + // Criteria progress: criteriaId → current value (from SMSG_CRITERIA_UPDATE) + std::unordered_map criteriaProgress_; void handleAllAchievementData(network::Packet& packet); // Area name cache (lazy-loaded from WorldMapArea.dbc; maps AreaTable ID → display name) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index f7d8350c..0f53c4af 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4153,12 +4153,12 @@ void GameHandler::handlePacket(network::Packet& packet) { } case Opcode::SMSG_CRITERIA_UPDATE: { // uint32 criteriaId + uint64 progress + uint32 elapsedTime + uint32 creationTime - // Achievement criteria progress (informational — no criteria UI yet). if (packet.getSize() - packet.getReadPos() >= 20) { uint32_t criteriaId = packet.readUInt32(); uint64_t progress = packet.readUInt64(); - /*uint32_t elapsedTime =*/ packet.readUInt32(); - /*uint32_t createTime =*/ packet.readUInt32(); + packet.readUInt32(); // elapsedTime + packet.readUInt32(); // creationTime + criteriaProgress_[criteriaId] = progress; LOG_DEBUG("SMSG_CRITERIA_UPDATE: id=", criteriaId, " progress=", progress); } break; @@ -20080,18 +20080,21 @@ void GameHandler::handleAllAchievementData(network::Packet& packet) { earnedAchievements_.insert(id); } - // Skip criteria block (id + uint64 counter + uint32 date + uint32 flags until 0xFFFFFFFF) + // Parse criteria block: id + uint64 counter + uint32 date + uint32 flags, sentinel 0xFFFFFFFF + criteriaProgress_.clear(); while (packet.getSize() - packet.getReadPos() >= 4) { uint32_t id = packet.readUInt32(); if (id == 0xFFFFFFFF) break; // counter(8) + date(4) + unknown(4) = 16 bytes if (packet.getSize() - packet.getReadPos() < 16) break; - packet.readUInt64(); // counter + uint64_t counter = packet.readUInt64(); packet.readUInt32(); // date packet.readUInt32(); // unknown / flags + criteriaProgress_[id] = counter; } - LOG_INFO("SMSG_ALL_ACHIEVEMENT_DATA: loaded ", earnedAchievements_.size(), " earned achievements"); + LOG_INFO("SMSG_ALL_ACHIEVEMENT_DATA: loaded ", earnedAchievements_.size(), + " achievements, ", criteriaProgress_.size(), " criteria"); } // --------------------------------------------------------------------------- diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 40d330e6..94a9d05a 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -14416,54 +14416,83 @@ void GameScreen::renderAchievementWindow(game::GameHandler& gameHandler) { } const auto& earned = gameHandler.getEarnedAchievements(); - ImGui::Text("Earned: %u", static_cast(earned.size())); - ImGui::SameLine(); + const auto& criteria = gameHandler.getCriteriaProgress(); + ImGui::SetNextItemWidth(180.0f); ImGui::InputText("##achsearch", achievementSearchBuf_, sizeof(achievementSearchBuf_)); ImGui::SameLine(); if (ImGui::Button("Clear")) achievementSearchBuf_[0] = '\0'; ImGui::Separator(); - if (earned.empty()) { - ImGui::TextDisabled("No achievements earned yet."); - ImGui::End(); - return; - } - - ImGui::BeginChild("##achlist", ImVec2(0, 0), false); - std::string filter(achievementSearchBuf_); - // lower-case filter for case-insensitive matching for (char& c : filter) c = static_cast(tolower(static_cast(c))); - // Collect and sort ids for stable display - std::vector ids(earned.begin(), earned.end()); - std::sort(ids.begin(), ids.end()); - - for (uint32_t id : ids) { - const std::string& name = gameHandler.getAchievementName(id); - const std::string& display = name.empty() ? std::to_string(id) : name; - - if (!filter.empty()) { - std::string lower = display; - for (char& c : lower) c = static_cast(tolower(static_cast(c))); - if (lower.find(filter) == std::string::npos) continue; + if (ImGui::BeginTabBar("##achtabs")) { + // --- Earned tab --- + char earnedLabel[32]; + snprintf(earnedLabel, sizeof(earnedLabel), "Earned (%u)###earned", (unsigned)earned.size()); + if (ImGui::BeginTabItem(earnedLabel)) { + if (earned.empty()) { + ImGui::TextDisabled("No achievements earned yet."); + } else { + ImGui::BeginChild("##achlist", ImVec2(0, 0), false); + std::vector ids(earned.begin(), earned.end()); + std::sort(ids.begin(), ids.end()); + for (uint32_t id : ids) { + const std::string& name = gameHandler.getAchievementName(id); + const std::string& display = name.empty() ? std::to_string(id) : name; + if (!filter.empty()) { + std::string lower = display; + for (char& c : lower) c = static_cast(tolower(static_cast(c))); + if (lower.find(filter) == std::string::npos) continue; + } + ImGui::PushID(static_cast(id)); + ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, 1.0f), "\xE2\x98\x85"); + ImGui::SameLine(); + ImGui::TextUnformatted(display.c_str()); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("ID: %u", id); + ImGui::EndTooltip(); + } + ImGui::PopID(); + } + ImGui::EndChild(); + } + ImGui::EndTabItem(); } - ImGui::PushID(static_cast(id)); - ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, 1.0f), "[Achievement]"); - ImGui::SameLine(); - ImGui::TextUnformatted(display.c_str()); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("ID: %u", id); - if (!name.empty()) ImGui::TextDisabled("%s", name.c_str()); - ImGui::EndTooltip(); + // --- Criteria progress tab --- + char critLabel[32]; + snprintf(critLabel, sizeof(critLabel), "Criteria (%u)###crit", (unsigned)criteria.size()); + if (ImGui::BeginTabItem(critLabel)) { + if (criteria.empty()) { + ImGui::TextDisabled("No criteria progress received yet."); + } else { + ImGui::BeginChild("##critlist", ImVec2(0, 0), false); + // Sort criteria by id for stable display + std::vector> clist(criteria.begin(), criteria.end()); + std::sort(clist.begin(), clist.end()); + for (const auto& [cid, cval] : clist) { + std::string label = std::to_string(cid); + if (!filter.empty()) { + std::string lower = label; + for (char& c : lower) c = static_cast(tolower(static_cast(c))); + if (lower.find(filter) == std::string::npos) continue; + } + ImGui::PushID(static_cast(cid)); + ImGui::TextDisabled("Criteria %u:", cid); + ImGui::SameLine(); + ImGui::Text("%llu", static_cast(cval)); + ImGui::PopID(); + } + ImGui::EndChild(); + } + ImGui::EndTabItem(); } - ImGui::PopID(); + ImGui::EndTabBar(); } - ImGui::EndChild(); ImGui::End(); }