diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index e3840fe2..97a023ab 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -1340,6 +1340,11 @@ public: 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 WoW PackedTime earn date for an achievement, or 0 if unknown. + uint32_t getAchievementDate(uint32_t id) const { + auto it = achievementDates_.find(id); + return (it != achievementDates_.end()) ? it->second : 0u; + } /// 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); @@ -2498,6 +2503,8 @@ private: void loadAchievementNameCache(); // Set of achievement IDs earned by the player (populated from SMSG_ALL_ACHIEVEMENT_DATA) std::unordered_set earnedAchievements_; + // Earn dates: achievementId → WoW PackedTime (from SMSG_ACHIEVEMENT_EARNED / SMSG_ALL_ACHIEVEMENT_DATA) + std::unordered_map achievementDates_; // Criteria progress: criteriaId → current value (from SMSG_CRITERIA_UPDATE) std::unordered_map criteriaProgress_; void handleAllAchievementData(network::Packet& packet); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index cd373518..b8205e8a 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -20127,7 +20127,7 @@ void GameHandler::handleAchievementEarned(network::Packet& packet) { uint64_t guid = packet.readUInt64(); uint32_t achievementId = packet.readUInt32(); - /*uint32_t date =*/ packet.readUInt32(); // PackedTime — not displayed + uint32_t earnDate = packet.readUInt32(); // WoW PackedTime bitfield loadAchievementNameCache(); auto nameIt = achievementNameCache_.find(achievementId); @@ -20146,6 +20146,7 @@ void GameHandler::handleAchievementEarned(network::Packet& packet) { addSystemChatMessage(buf); earnedAchievements_.insert(achievementId); + achievementDates_[achievementId] = earnDate; if (achievementEarnedCallback_) { achievementEarnedCallback_(achievementId, achName); } @@ -20186,14 +20187,16 @@ void GameHandler::handleAchievementEarned(network::Packet& packet) { void GameHandler::handleAllAchievementData(network::Packet& packet) { loadAchievementNameCache(); earnedAchievements_.clear(); + achievementDates_.clear(); // Parse achievement entries (id + packedDate pairs, sentinel 0xFFFFFFFF) while (packet.getSize() - packet.getReadPos() >= 4) { uint32_t id = packet.readUInt32(); if (id == 0xFFFFFFFF) break; if (packet.getSize() - packet.getReadPos() < 4) break; - /*uint32_t date =*/ packet.readUInt32(); + uint32_t date = packet.readUInt32(); earnedAchievements_.insert(id); + achievementDates_[id] = date; } // Parse criteria block: id + uint64 counter + uint32 date + uint32 flags, sentinel 0xFFFFFFFF diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 30ff4cba..415fab37 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -16038,7 +16038,22 @@ void GameScreen::renderAchievementWindow(game::GameHandler& gameHandler) { ImGui::TextUnformatted(display.c_str()); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - ImGui::Text("ID: %u", id); + ImGui::Text("Achievement ID: %u", id); + uint32_t packed = gameHandler.getAchievementDate(id); + if (packed != 0) { + // WoW PackedTime: year[31:25] month[24:21] day[20:17] weekday[16:14] hour[13:9] minute[8:3] + int minute = (packed >> 3) & 0x3F; + int hour = (packed >> 9) & 0x1F; + int day = (packed >> 17) & 0x1F; + int month = (packed >> 21) & 0x0F; + int year = ((packed >> 25) & 0x7F) + 2000; + static const char* kMonths[12] = { + "Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec" + }; + const char* mname = (month >= 1 && month <= 12) ? kMonths[month - 1] : "?"; + ImGui::Text("Earned: %s %d, %d %02d:%02d", mname, day, year, hour, minute); + } ImGui::EndTooltip(); } ImGui::PopID();