Achievements: Cache latest/nearest achievement info

When using RAIntegration through rc_client, the rc_client_achievement_t
pointer does not persist after rc_client_destroy_achievement_list().
This commit is contained in:
Stenzek 2025-04-10 01:33:26 +10:00
parent a310d3a5e7
commit e41543c38a
No known key found for this signature in database

View File

@ -232,6 +232,15 @@ static void BuildProgressDatabase(const rc_client_all_progress_list_t* allprog);
static void UpdateProgressDatabase(bool force); static void UpdateProgressDatabase(bool force);
static void ClearProgressDatabase(); static void ClearProgressDatabase();
namespace {
struct PauseMenuAchievementInfo
{
std::string title;
std::string description;
std::string badge_path;
};
struct State struct State
{ {
rc_client_t* client = nullptr; rc_client_t* client = nullptr;
@ -270,8 +279,8 @@ struct State
rc_client_achievement_list_t* achievement_list = nullptr; rc_client_achievement_list_t* achievement_list = nullptr;
std::vector<std::tuple<const void*, int, std::string>> achievement_badge_paths; std::vector<std::tuple<const void*, int, std::string>> achievement_badge_paths;
const rc_client_achievement_t* most_recent_unlock = nullptr; std::optional<PauseMenuAchievementInfo> most_recent_unlock = {};
const rc_client_achievement_t* achievement_nearest_completion = nullptr; std::optional<PauseMenuAchievementInfo> achievement_nearest_completion = {};
rc_client_leaderboard_list_t* leaderboard_list = nullptr; rc_client_leaderboard_list_t* leaderboard_list = nullptr;
const rc_client_leaderboard_t* open_leaderboard = nullptr; const rc_client_leaderboard_t* open_leaderboard = nullptr;
@ -290,6 +299,8 @@ struct State
rc_client_all_progress_list_t* fetch_all_progress_result = nullptr; rc_client_all_progress_list_t* fetch_all_progress_result = nullptr;
}; };
} // namespace
ALIGN_TO_CACHE_LINE static State s_state; ALIGN_TO_CACHE_LINE static State s_state;
} // namespace Achievements } // namespace Achievements
@ -1041,15 +1052,23 @@ void Achievements::UpdateRecentUnlockAndAlmostThere()
{ {
const auto lock = GetLock(); const auto lock = GetLock();
if (!HasActiveGame()) if (!HasActiveGame())
{
s_state.most_recent_unlock.reset();
s_state.achievement_nearest_completion.reset();
return; return;
}
s_state.most_recent_unlock = nullptr;
s_state.achievement_nearest_completion = nullptr;
rc_client_achievement_list_t* const achievements = rc_client_create_achievement_list( rc_client_achievement_list_t* const achievements = rc_client_create_achievement_list(
s_state.client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS); s_state.client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
if (!achievements) if (!achievements)
{
s_state.most_recent_unlock.reset();
s_state.achievement_nearest_completion.reset();
return; return;
}
const rc_client_achievement_t* most_recent_unlock = nullptr;
const rc_client_achievement_t* nearest_completion = nullptr;
for (u32 i = 0; i < achievements->num_buckets; i++) for (u32 i = 0; i < achievements->num_buckets; i++)
{ {
@ -1060,8 +1079,8 @@ void Achievements::UpdateRecentUnlockAndAlmostThere()
if (achievement->state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) if (achievement->state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED)
{ {
if (!s_state.most_recent_unlock || achievement->unlock_time > s_state.most_recent_unlock->unlock_time) if (!most_recent_unlock || achievement->unlock_time > most_recent_unlock->unlock_time)
s_state.most_recent_unlock = achievement; most_recent_unlock = achievement;
} }
else else
{ {
@ -1069,14 +1088,34 @@ void Achievements::UpdateRecentUnlockAndAlmostThere()
// matching the rc_client definition of "almost there" // matching the rc_client definition of "almost there"
const float percent_cutoff = 80.0f; const float percent_cutoff = 80.0f;
if (achievement->measured_percent >= percent_cutoff && if (achievement->measured_percent >= percent_cutoff &&
(!s_state.achievement_nearest_completion || (!nearest_completion || achievement->measured_percent > nearest_completion->measured_percent))
achievement->measured_percent > s_state.achievement_nearest_completion->measured_percent))
{ {
s_state.achievement_nearest_completion = achievement; nearest_completion = achievement;
} }
} }
} }
} }
// have to take a copy because with RAIntegration the achievement pointer does not persist
static constexpr auto cache_info = [](const rc_client_achievement_t* achievement,
std::optional<PauseMenuAchievementInfo>& info) {
if (!achievement)
{
info.reset();
return;
}
if (!info.has_value())
info.emplace();
info->title = achievement->title;
info->description = achievement->description;
info->badge_path = GetAchievementBadgePath(achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED, true);
};
cache_info(most_recent_unlock, s_state.most_recent_unlock);
cache_info(nearest_completion, s_state.achievement_nearest_completion);
rc_client_destroy_achievement_list(achievements); rc_client_destroy_achievement_list(achievements);
} }
@ -2377,8 +2416,8 @@ void Achievements::ClearUIState()
s_state.achievement_list = nullptr; s_state.achievement_list = nullptr;
} }
s_state.most_recent_unlock = nullptr; s_state.most_recent_unlock.reset();
s_state.achievement_nearest_completion = nullptr; s_state.achievement_nearest_completion.reset();
} }
template<typename T> template<typename T>
@ -2584,10 +2623,13 @@ void Achievements::DrawPauseMenuOverlays(float start_pos_y)
ImDrawList* dl = ImGui::GetBackgroundDrawList(); ImDrawList* dl = ImGui::GetBackgroundDrawList();
const auto get_achievement_height = [&badge_size, &badge_text_width, const auto get_achievement_height = [&badge_size, &badge_text_width, &text_spacing](std::string_view description,
&text_spacing](const rc_client_achievement_t* achievement, bool show_measured) { bool show_measured) {
const ImVec2 description_size = UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, const ImVec2 description_size =
badge_text_width, achievement->description); description.empty() ?
ImVec2(0.0f, 0.0f) :
UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, badge_text_width, description.data(),
description.data() + description.length());
const float text_height = UIStyle.MediumFont->FontSize + text_spacing + description_size.y; const float text_height = UIStyle.MediumFont->FontSize + text_spacing + description_size.y;
return std::max(text_height, badge_size); return std::max(text_height, badge_size);
}; };
@ -2598,16 +2640,16 @@ void Achievements::DrawPauseMenuOverlays(float start_pos_y)
{ {
box_height += UIStyle.MediumFont->FontSize + paragraph_spacing; box_height += UIStyle.MediumFont->FontSize + paragraph_spacing;
} }
if (s_state.most_recent_unlock) if (s_state.most_recent_unlock.has_value())
{ {
box_height += UIStyle.MediumFont->FontSize + paragraph_spacing + box_height += UIStyle.MediumFont->FontSize + paragraph_spacing +
get_achievement_height(s_state.most_recent_unlock, false) + get_achievement_height(s_state.most_recent_unlock->description, false) +
(s_state.achievement_nearest_completion ? (paragraph_spacing + paragraph_spacing) : 0.0f); (s_state.achievement_nearest_completion ? (paragraph_spacing + paragraph_spacing) : 0.0f);
} }
if (s_state.achievement_nearest_completion) if (s_state.achievement_nearest_completion.has_value())
{ {
box_height += UIStyle.MediumFont->FontSize + paragraph_spacing + box_height += UIStyle.MediumFont->FontSize + paragraph_spacing +
get_achievement_height(s_state.achievement_nearest_completion, true); get_achievement_height(s_state.achievement_nearest_completion->description, true);
} }
ImVec2 box_min = ImVec2(display_size.x - box_width - box_margin, start_pos_y + box_margin); ImVec2 box_min = ImVec2(display_size.x - box_width - box_margin, start_pos_y + box_margin);
@ -2618,25 +2660,34 @@ void Achievements::DrawPauseMenuOverlays(float start_pos_y)
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding); dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
const auto draw_achievement_with_summary = [&box_max, &badge_text_width, &dl, &title_text_color, &text_color, const auto draw_achievement_with_summary = [&box_max, &badge_text_width, &dl, &title_text_color, &text_color,
&text_spacing, &text_pos, &badge_size]( &text_spacing, &text_pos,
const rc_client_achievement_t* achievement, bool show_measured) { &badge_size](std::string_view title, std::string_view description,
const std::string& badge_path, bool show_measured) {
const ImVec2 image_max = ImVec2(text_pos.x + badge_size, text_pos.y + badge_size); const ImVec2 image_max = ImVec2(text_pos.x + badge_size, text_pos.y + badge_size);
ImVec2 badge_text_pos = ImVec2(image_max.x + text_spacing + text_spacing, text_pos.y); ImVec2 badge_text_pos = ImVec2(image_max.x + text_spacing + text_spacing, text_pos.y);
const ImVec4 clip_rect = ImVec4(badge_text_pos.x, badge_text_pos.y, badge_text_pos.x + badge_text_width, box_max.y); const ImVec4 clip_rect = ImVec4(badge_text_pos.x, badge_text_pos.y, badge_text_pos.x + badge_text_width, box_max.y);
const ImVec2 description_size = UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, const ImVec2 description_size =
badge_text_width, achievement->description); description.empty() ?
ImVec2(0.0f, 0.0f) :
UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, badge_text_width, description.data(),
description.data() + description.length());
GPUTexture* badge_tex = ImGuiFullscreen::GetCachedTextureAsync( GPUTexture* badge_tex = ImGuiFullscreen::GetCachedTextureAsync(badge_path);
GetCachedAchievementBadgePath(achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED));
dl->AddImage(badge_tex, text_pos, image_max); dl->AddImage(badge_tex, text_pos, image_max);
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, badge_text_pos, title_text_color, achievement->title, if (!title.empty())
nullptr, 0.0f, &clip_rect); {
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, badge_text_pos, title_text_color, title.data(),
title.data() + title.length(), 0.0f, &clip_rect);
badge_text_pos.y += UIStyle.MediumFont->FontSize + text_spacing; badge_text_pos.y += UIStyle.MediumFont->FontSize + text_spacing;
}
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, badge_text_pos, text_color, achievement->description, if (!description.empty())
nullptr, badge_text_width, &clip_rect); {
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, badge_text_pos, text_color, description.data(),
description.data() + description.length(), badge_text_width, &clip_rect);
badge_text_pos.y += description_size.y; badge_text_pos.y += description_size.y;
}
text_pos.y = badge_text_pos.y; text_pos.y = badge_text_pos.y;
}; };
@ -2688,27 +2739,30 @@ void Achievements::DrawPauseMenuOverlays(float start_pos_y)
} }
} }
if (s_state.most_recent_unlock) if (s_state.most_recent_unlock.has_value())
{ {
buffer.format(ICON_FA_LOCK_OPEN " {}", TRANSLATE_DISAMBIG_SV("Achievements", "Most Recent", "Pause Menu")); buffer.format(ICON_FA_LOCK_OPEN " {}", TRANSLATE_DISAMBIG_SV("Achievements", "Most Recent", "Pause Menu"));
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_color, buffer.c_str(), dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_color, buffer.c_str(),
buffer.end_ptr()); buffer.end_ptr());
text_pos.y += UIStyle.MediumFont->FontSize + paragraph_spacing; text_pos.y += UIStyle.MediumFont->FontSize + paragraph_spacing;
draw_achievement_with_summary(s_state.most_recent_unlock, false); draw_achievement_with_summary(s_state.most_recent_unlock->title, s_state.most_recent_unlock->description,
s_state.most_recent_unlock->badge_path, false);
// extra spacing if we have two // extra spacing if we have two
text_pos.y += s_state.achievement_nearest_completion ? (paragraph_spacing + paragraph_spacing) : 0.0f; text_pos.y += s_state.achievement_nearest_completion ? (paragraph_spacing + paragraph_spacing) : 0.0f;
} }
if (s_state.achievement_nearest_completion) if (s_state.achievement_nearest_completion.has_value())
{ {
buffer.format(ICON_FA_LOCK " {}", TRANSLATE_DISAMBIG_SV("Achievements", "Nearest Completion", "Pause Menu")); buffer.format(ICON_FA_LOCK " {}", TRANSLATE_DISAMBIG_SV("Achievements", "Nearest Completion", "Pause Menu"));
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_color, buffer.c_str(), dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_color, buffer.c_str(),
buffer.end_ptr()); buffer.end_ptr());
text_pos.y += UIStyle.MediumFont->FontSize + paragraph_spacing; text_pos.y += UIStyle.MediumFont->FontSize + paragraph_spacing;
draw_achievement_with_summary(s_state.achievement_nearest_completion, true); draw_achievement_with_summary(s_state.achievement_nearest_completion->title,
s_state.achievement_nearest_completion->description,
s_state.achievement_nearest_completion->badge_path, true);
text_pos.y += paragraph_spacing; text_pos.y += paragraph_spacing;
} }
@ -2720,7 +2774,7 @@ void Achievements::DrawPauseMenuOverlays(float start_pos_y)
for (size_t i = 0; i < s_state.active_challenge_indicators.size(); i++) for (size_t i = 0; i < s_state.active_challenge_indicators.size(); i++)
{ {
const AchievementChallengeIndicator& indicator = s_state.active_challenge_indicators[i]; const AchievementChallengeIndicator& indicator = s_state.active_challenge_indicators[i];
box_height += paragraph_spacing + get_achievement_height(indicator.achievement, false) + box_height += paragraph_spacing + get_achievement_height(indicator.achievement->description, false) +
((i == s_state.active_challenge_indicators.size() - 1) ? paragraph_spacing : 0.0f); ((i == s_state.active_challenge_indicators.size() - 1) ? paragraph_spacing : 0.0f);
} }
@ -2739,7 +2793,8 @@ void Achievements::DrawPauseMenuOverlays(float start_pos_y)
for (const AchievementChallengeIndicator& indicator : s_state.active_challenge_indicators) for (const AchievementChallengeIndicator& indicator : s_state.active_challenge_indicators)
{ {
text_pos.y += paragraph_spacing; text_pos.y += paragraph_spacing;
draw_achievement_with_summary(indicator.achievement, false); draw_achievement_with_summary(indicator.achievement->title, indicator.achievement->description,
indicator.badge_path, false);
text_pos.y += paragraph_spacing; text_pos.y += paragraph_spacing;
} }
} }