From 04df930e0d0e3b9cec91664350ae63848cad8e41 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 17 Jun 2023 11:19:16 -0400 Subject: [PATCH] Added FetchBoardInfo to AchievementManager FetchBoardInfo is called (via the work queue asynchronously) on a leaderboard every time it is activated or submitted to. It makes two calls to the RetroAchievements API for fetching leaderboard info, one that requests the top four entries in the leaderboard and another that requests the player's entry, the two entries above the player and the two entries below. All of these are inserted into a single map (resolving any overlaps) so the result can be exposed to the UI. --- Source/Core/Core/AchievementManager.cpp | 102 +++++++++++++++++++++++- Source/Core/Core/AchievementManager.h | 1 + 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 030257228a..d5acdc84e9 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -7,6 +7,7 @@ #include +#include #include #include "Common/HttpRequest.h" @@ -268,9 +269,15 @@ void AchievementManager::ActivateDeactivateLeaderboards() for (u32 ix = 0; ix < m_game_data.num_leaderboards; ix++) { auto leaderboard = m_game_data.leaderboards[ix]; + u32 leaderboard_id = leaderboard.id; if (m_is_game_loaded && leaderboards_enabled && hardcore_mode_enabled) { - rc_runtime_activate_lboard(&m_runtime, leaderboard.id, leaderboard.definition, nullptr, 0); + rc_runtime_activate_lboard(&m_runtime, leaderboard_id, leaderboard.definition, nullptr, 0); + m_queue.EmplaceItem([this, leaderboard_id] { + FetchBoardInfo(leaderboard_id); + if (m_update_callback) + m_update_callback(); + }); } else { @@ -712,7 +719,7 @@ AchievementManager::GetAchievementProgress(AchievementId achievement_id, u32* va return ResponseType::SUCCESS; } -const std::unordered_map& +const std::unordered_map& AchievementManager::GetLeaderboardsInfo() const { return m_leaderboard_map; @@ -962,6 +969,90 @@ AchievementManager::ResponseType AchievementManager::FetchUnlockData(bool hardco return r_type; } +AchievementManager::ResponseType AchievementManager::FetchBoardInfo(AchievementId leaderboard_id) +{ + std::string username = Config::Get(Config::RA_USERNAME); + LeaderboardStatus lboard{}; + + { + rc_api_fetch_leaderboard_info_response_t board_info{}; + const rc_api_fetch_leaderboard_info_request_t fetch_board_request = { + .leaderboard_id = leaderboard_id, .count = 4, .first_entry = 1, .username = nullptr}; + const ResponseType r_type = + Request( + fetch_board_request, &board_info, rc_api_init_fetch_leaderboard_info_request, + rc_api_process_fetch_leaderboard_info_response); + if (r_type != ResponseType::SUCCESS) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to fetch info for leaderboard ID {}.", leaderboard_id); + rc_api_destroy_fetch_leaderboard_info_response(&board_info); + return r_type; + } + lboard.name = board_info.title; + lboard.description = board_info.description; + lboard.entries.clear(); + for (u32 i = 0; i < board_info.num_entries; ++i) + { + const auto& org_entry = board_info.entries[i]; + LeaderboardEntry dest_entry = + LeaderboardEntry{.username = org_entry.username, .rank = org_entry.rank}; + if (rc_runtime_format_lboard_value(dest_entry.score.data(), FORMAT_SIZE, org_entry.score, + board_info.format) == 0) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to format leaderboard score {}.", org_entry.score); + strncpy(dest_entry.score.data(), fmt::format("{}", org_entry.score).c_str(), FORMAT_SIZE); + } + lboard.entries[org_entry.index] = dest_entry; + } + rc_api_destroy_fetch_leaderboard_info_response(&board_info); + } + + { + // Retrieve, if exists, the player's entry, the two entries above the player, and the two + // entries below the player, for a total of five entries. Technically I only need one entry + // below, but the API is ambiguous what happens if an even number and a username are provided. + rc_api_fetch_leaderboard_info_response_t board_info{}; + const rc_api_fetch_leaderboard_info_request_t fetch_board_request = { + .leaderboard_id = leaderboard_id, + .count = 5, + .first_entry = 0, + .username = username.c_str()}; + const ResponseType r_type = + Request( + fetch_board_request, &board_info, rc_api_init_fetch_leaderboard_info_request, + rc_api_process_fetch_leaderboard_info_response); + if (r_type != ResponseType::SUCCESS) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to fetch info for leaderboard ID {}.", leaderboard_id); + rc_api_destroy_fetch_leaderboard_info_response(&board_info); + return r_type; + } + for (u32 i = 0; i < board_info.num_entries; ++i) + { + const auto& org_entry = board_info.entries[i]; + LeaderboardEntry dest_entry = + LeaderboardEntry{.username = org_entry.username, .rank = org_entry.rank}; + if (rc_runtime_format_lboard_value(dest_entry.score.data(), FORMAT_SIZE, org_entry.score, + board_info.format) == 0) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to format leaderboard score {}.", org_entry.score); + strncpy(dest_entry.score.data(), fmt::format("{}", org_entry.score).c_str(), FORMAT_SIZE); + } + lboard.entries[org_entry.index] = dest_entry; + if (org_entry.username == username) + lboard.player_index = org_entry.index; + } + rc_api_destroy_fetch_leaderboard_info_response(&board_info); + } + + { + std::lock_guard lg{m_lock}; + m_leaderboard_map[leaderboard_id] = lboard; + } + + return ResponseType::SUCCESS; +} + void AchievementManager::ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore) { @@ -1205,7 +1296,12 @@ void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_ m_game_data.leaderboards[ix].title), OSD::Duration::VERY_LONG, OSD::Color::YELLOW); } - return; + m_queue.EmplaceItem([this, event_id] { + FetchBoardInfo(event_id); + if (m_update_callback) + m_update_callback(); + }); + break; } } ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid leaderboard triggered event with id {}.", event_id); diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index d12abf2167..b5c1eee200 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -146,6 +146,7 @@ private: ResponseType StartRASession(); ResponseType FetchGameData(); ResponseType FetchUnlockData(bool hardcore); + ResponseType FetchBoardInfo(AchievementId leaderboard_id); void ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore); void GenerateRichPresence();