feat: show achievement description and point value in tooltip

Hovering an earned achievement now shows its point value (gold badge),
description text from Achievement.dbc field 21, and the earn date.
loadAchievementNameCache() also populates achievementDescCache_ and
achievementPointsCache_ in a single DBC pass; Points field (39) added
to the WotLK Achievement DBC layout.
This commit is contained in:
Kelsi 2026-03-12 12:49:38 -07:00
parent bc0d98adae
commit d44f5e6560
4 changed files with 44 additions and 4 deletions

View file

@ -31,7 +31,7 @@
"ReputationBase0": 10, "ReputationBase1": 11,
"ReputationBase2": 12, "ReputationBase3": 13
},
"Achievement": { "ID": 0, "Title": 4, "Description": 21 },
"Achievement": { "ID": 0, "Title": 4, "Description": 21, "Points": 39 },
"AreaTable": { "ID": 0, "MapID": 1, "ParentAreaNum": 2, "ExploreFlag": 3 },
"CreatureDisplayInfoExtra": {
"ID": 0, "RaceID": 1, "SexID": 2, "SkinID": 3, "FaceID": 4,

View file

@ -1429,6 +1429,18 @@ public:
static const std::string kEmpty;
return kEmpty;
}
/// Returns the description of an achievement by ID, or empty string if unknown.
const std::string& getAchievementDescription(uint32_t id) const {
auto it = achievementDescCache_.find(id);
if (it != achievementDescCache_.end()) return it->second;
static const std::string kEmpty;
return kEmpty;
}
/// Returns the point value of an achievement by ID, or 0 if unknown.
uint32_t getAchievementPoints(uint32_t id) const {
auto it = achievementPointsCache_.find(id);
return (it != achievementPointsCache_.end()) ? it->second : 0u;
}
// Server-triggered music callback — fires when SMSG_PLAY_MUSIC is received.
// The soundId corresponds to a SoundEntries.dbc record. The receiver is
@ -2590,8 +2602,10 @@ private:
std::unordered_map<uint32_t, SpellNameEntry> spellNameCache_;
bool spellNameCacheLoaded_ = false;
// Achievement name cache (lazy-loaded from Achievement.dbc on first earned event)
// Achievement caches (lazy-loaded from Achievement.dbc on first earned event)
std::unordered_map<uint32_t, std::string> achievementNameCache_;
std::unordered_map<uint32_t, std::string> achievementDescCache_;
std::unordered_map<uint32_t, uint32_t> achievementPointsCache_;
bool achievementNameCacheLoaded_ = false;
void loadAchievementNameCache();
// Set of achievement IDs earned by the player (populated from SMSG_ALL_ACHIEVEMENT_DATA)

View file

@ -20303,12 +20303,23 @@ void GameHandler::loadAchievementNameCache() {
? pipeline::getActiveDBCLayout()->getLayout("Achievement") : nullptr;
uint32_t titleField = achL ? achL->field("Title") : 4;
if (titleField == 0xFFFFFFFF) titleField = 4;
uint32_t descField = achL ? achL->field("Description") : 0xFFFFFFFF;
uint32_t ptsField = achL ? achL->field("Points") : 0xFFFFFFFF;
uint32_t fieldCount = dbc->getFieldCount();
for (uint32_t i = 0; i < dbc->getRecordCount(); ++i) {
uint32_t id = dbc->getUInt32(i, 0);
if (id == 0) continue;
std::string title = dbc->getString(i, titleField);
if (!title.empty()) achievementNameCache_[id] = std::move(title);
if (descField != 0xFFFFFFFF && descField < fieldCount) {
std::string desc = dbc->getString(i, descField);
if (!desc.empty()) achievementDescCache_[id] = std::move(desc);
}
if (ptsField != 0xFFFFFFFF && ptsField < fieldCount) {
uint32_t pts = dbc->getUInt32(i, ptsField);
if (pts > 0) achievementPointsCache_[id] = pts;
}
}
LOG_INFO("Achievement: loaded ", achievementNameCache_.size(), " names from Achievement.dbc");
}

View file

@ -17559,7 +17559,22 @@ void GameScreen::renderAchievementWindow(game::GameHandler& gameHandler) {
ImGui::TextUnformatted(display.c_str());
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("Achievement ID: %u", id);
// Points badge
uint32_t pts = gameHandler.getAchievementPoints(id);
if (pts > 0) {
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, 1.0f),
"%u Achievement Point%s", pts, pts == 1 ? "" : "s");
ImGui::Separator();
}
// Description
const std::string& desc = gameHandler.getAchievementDescription(id);
if (!desc.empty()) {
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 320.0f);
ImGui::TextUnformatted(desc.c_str());
ImGui::PopTextWrapPos();
ImGui::Spacing();
}
// Earn date
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]
@ -17573,7 +17588,7 @@ void GameScreen::renderAchievementWindow(game::GameHandler& gameHandler) {
"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::TextDisabled("Earned: %s %d, %d %02d:%02d", mname, day, year, hour, minute);
}
ImGui::EndTooltip();
}