Add Achievements tab to character screen with search filter

This commit is contained in:
Kelsi 2026-03-11 22:07:44 -07:00
parent c9ea61aba7
commit c9cfa864bf
2 changed files with 58 additions and 0 deletions

View file

@ -1215,6 +1215,13 @@ public:
using AchievementEarnedCallback = std::function<void(uint32_t achievementId, const std::string& name)>;
void setAchievementEarnedCallback(AchievementEarnedCallback cb) { achievementEarnedCallback_ = std::move(cb); }
const std::unordered_set<uint32_t>& getEarnedAchievements() const { return earnedAchievements_; }
/// 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);
if (it != achievementNameCache_.end()) return it->second;
static const std::string kEmpty;
return kEmpty;
}
// Server-triggered music callback — fires when SMSG_PLAY_MUSIC is received.
// The soundId corresponds to a SoundEntries.dbc record. The receiver is

View file

@ -1180,6 +1180,57 @@ void InventoryScreen::renderCharacterScreen(game::GameHandler& gameHandler) {
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Achievements")) {
const auto& earned = gameHandler.getEarnedAchievements();
if (earned.empty()) {
ImGui::Spacing();
ImGui::TextDisabled("No achievements earned yet.");
} else {
static char achieveFilter[128] = {};
ImGui::SetNextItemWidth(-1.0f);
ImGui::InputTextWithHint("##achsearch", "Search achievements...",
achieveFilter, sizeof(achieveFilter));
ImGui::Separator();
char filterLower[128];
for (size_t i = 0; i < sizeof(achieveFilter); ++i)
filterLower[i] = static_cast<char>(tolower(static_cast<unsigned char>(achieveFilter[i])));
ImGui::BeginChild("##AchList", ImVec2(0, 0), false);
// Sort by ID for stable ordering
std::vector<uint32_t> sortedIds(earned.begin(), earned.end());
std::sort(sortedIds.begin(), sortedIds.end());
int shown = 0;
for (uint32_t id : sortedIds) {
const std::string& name = gameHandler.getAchievementName(id);
const char* displayName = name.empty() ? nullptr : name.c_str();
if (displayName == nullptr) continue; // skip unknown achievements
// Apply filter
if (filterLower[0] != '\0') {
// simple case-insensitive substring match
std::string lower;
lower.reserve(name.size());
for (char c : name) lower += static_cast<char>(tolower(static_cast<unsigned char>(c)));
if (lower.find(filterLower) == std::string::npos) continue;
}
ImGui::PushID(static_cast<int>(id));
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "[Achievement]");
ImGui::SameLine();
ImGui::Text("%s", displayName);
ImGui::PopID();
++shown;
}
if (shown == 0 && filterLower[0] != '\0') {
ImGui::TextDisabled("No achievements match the filter.");
}
ImGui::Text("Total: %d", static_cast<int>(earned.size()));
ImGui::EndChild();
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}