From fd50b1fda67dec87326164c4d9a2b1c867305b55 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 15 Jun 2023 03:24:34 -0400 Subject: [PATCH] Added FetchBadges to AchievementManager FetchBadges fetches all available badges (player, game, achievements) and stores them in AchievementManager's memory. New fields and accessors have been added as necessary. Badges for individual achievements are stored in the UnlockStatus map. The method is public so that the settings dialog can call it if badges are turned on after a game is started. Badges are deleted at game close and logout. --- Source/Core/Core/AchievementManager.cpp | 277 +++++++++++++++++- Source/Core/Core/AchievementManager.h | 16 +- .../Core/Core/Config/AchievementSettings.cpp | 1 + Source/Core/Core/Config/AchievementSettings.h | 1 + .../Core/ConfigLoaders/IsSettingSaveable.cpp | 1 + 5 files changed, 292 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 5ea4f253b9..35287d3560 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -35,6 +35,8 @@ void AchievementManager::Init() rc_runtime_init(&m_runtime); m_is_runtime_initialized = true; m_queue.Reset("AchievementManagerQueue", [](const std::function& func) { func(); }); + m_image_queue.Reset("AchievementManagerImageQueue", + [](const std::function& func) { func(); }); LoginAsync("", [](ResponseType r_type) {}); INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager Initialized"); } @@ -55,6 +57,7 @@ AchievementManager::ResponseType AchievementManager::Login(const std::string& pa return AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED; } AchievementManager::ResponseType r_type = VerifyCredentials(password); + FetchBadges(); if (m_update_callback) m_update_callback(); return r_type; @@ -71,6 +74,7 @@ void AchievementManager::LoginAsync(const std::string& password, const ResponseC } m_queue.EmplaceItem([this, password, callback] { callback(VerifyCredentials(password)); + FetchBadges(); if (m_update_callback) m_update_callback(); }); @@ -207,6 +211,7 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, } ActivateDeactivateLeaderboards(); ActivateDeactivateRichPresence(); + FetchBadges(); // Reset this to zero so that RP immediately triggers on the first frame m_last_ping_time = 0; INFO_LOG_FMT(ACHIEVEMENTS, "RetroAchievements successfully loaded for {}.", m_game_data.title); @@ -286,6 +291,256 @@ void AchievementManager::ActivateDeactivateRichPresence() INFO_LOG_FMT(ACHIEVEMENTS, "Rich presence (de)activated."); } +void AchievementManager::FetchBadges() +{ + if (!m_is_runtime_initialized || !IsLoggedIn() || !Config::Get(Config::RA_BADGES_ENABLED)) + { + if (m_update_callback) + m_update_callback(); + return; + } + m_image_queue.Cancel(); + + if (m_player_badge.name != m_display_name) + { + m_image_queue.EmplaceItem([this] { + std::string name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_display_name == m_player_badge.name) + return; + name_to_fetch = m_display_name; + } + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_USER}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded player badge id {}.", name_to_fetch); + std::lock_guard lg{m_lock}; + if (name_to_fetch != m_display_name) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {} for player id {}.", + name_to_fetch, m_display_name); + return; + } + m_player_badge.badge = std::move(fetched_badge); + m_player_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download player badge id {}.", name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + + if (!IsGameLoaded()) + { + if (m_update_callback) + m_update_callback(); + return; + } + + int badgematch = 0; + { + std::lock_guard lg{m_lock}; + badgematch = m_game_badge.name.compare(m_game_data.image_name); + } + if (badgematch != 0) + { + m_image_queue.EmplaceItem([this] { + std::string name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_badge.name.compare(m_game_data.image_name) == 0) + return; + name_to_fetch.assign(m_game_data.image_name); + } + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_GAME}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded game badge id {}.", name_to_fetch); + std::lock_guard lg{m_lock}; + if (name_to_fetch.compare(m_game_data.image_name) != 0) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {} for game id {}.", + name_to_fetch, m_game_data.image_name); + return; + } + m_game_badge.badge = std::move(fetched_badge); + m_game_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download game badge id {}.", name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + + unsigned num_achievements = m_game_data.num_achievements; + for (size_t index = 0; index < num_achievements; index++) + { + std::lock_guard lg{m_lock}; + // In case the number of achievements changes since the loop started; I just don't want + // to lock for the ENTIRE loop so instead I reclaim the lock each cycle + if (num_achievements != m_game_data.num_achievements) + break; + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + std::string name_to_fetch(achievement.badge_name); + const UnlockStatus& unlock_status = m_unlock_map[achievement.id]; + if (unlock_status.unlocked_badge.name != name_to_fetch) + { + m_image_queue.EmplaceItem([this, index] { + std::string current_name, name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch unlocked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch unlocked badge for achievement id {} not in unlock map.", + index); + return; + } + name_to_fetch.assign(achievement.badge_name); + current_name = unlock_itr->second.unlocked_badge.name; + } + if (current_name == name_to_fetch) + return; + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_ACHIEVEMENT}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded unlocked achievement badge id {}.", + name_to_fetch); + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Fetched unlocked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT(ACHIEVEMENTS, + "Fetched unlocked badge for achievement id {} not in unlock map.", index); + return; + } + if (name_to_fetch.compare(achievement.badge_name) != 0) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Requested outdated unlocked achievement badge id {} for achievement id {}.", + name_to_fetch, current_name); + return; + } + unlock_itr->second.unlocked_badge.badge = std::move(fetched_badge); + unlock_itr->second.unlocked_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download unlocked achievement badge id {}.", + name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + if (unlock_status.locked_badge.name != name_to_fetch) + { + m_image_queue.EmplaceItem([this, index] { + std::string current_name, name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch locked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch locked badge for achievement id {} not in unlock map.", index); + return; + } + name_to_fetch.assign(achievement.badge_name); + current_name = unlock_itr->second.locked_badge.name; + } + if (current_name == name_to_fetch) + return; + rc_api_fetch_image_request_t icon_request = { + .image_name = name_to_fetch.c_str(), .image_type = RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded locked achievement badge id {}.", + name_to_fetch); + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Fetched locked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT(ACHIEVEMENTS, + "Fetched locked badge for achievement id {} not in unlock map.", index); + return; + } + if (name_to_fetch.compare(achievement.badge_name) != 0) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Requested outdated locked achievement badge id {} for achievement id {}.", + name_to_fetch, current_name); + return; + } + unlock_itr->second.locked_badge.badge = std::move(fetched_badge); + unlock_itr->second.locked_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download locked achievement badge id {}.", + name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + } + if (m_update_callback) + m_update_callback(); +} + void AchievementManager::DoFrame() { if (!m_is_game_loaded) @@ -381,6 +636,11 @@ u32 AchievementManager::GetPlayerScore() const return IsLoggedIn() ? m_player_score : 0; } +const AchievementManager::BadgeStatus& AchievementManager::GetPlayerBadge() const +{ + return m_player_badge; +} + std::string AchievementManager::GetGameDisplayName() const { return IsGameLoaded() ? m_game_data.title : ""; @@ -417,7 +677,12 @@ rc_api_fetch_game_data_response_t* AchievementManager::GetGameData() return &m_game_data; } -AchievementManager::UnlockStatus +const AchievementManager::BadgeStatus& AchievementManager::GetGameBadge() const +{ + return m_game_badge; +} + +const AchievementManager::UnlockStatus& AchievementManager::GetUnlockStatus(AchievementId achievement_id) const { return m_unlock_map.at(achievement_id); @@ -447,10 +712,12 @@ void AchievementManager::CloseGame() ActivateDeactivateLeaderboards(); ActivateDeactivateRichPresence(); m_game_id = 0; + m_game_badge.name = ""; m_unlock_map.clear(); rc_api_destroy_fetch_game_data_response(&m_game_data); std::memset(&m_game_data, 0, sizeof(m_game_data)); m_queue.Cancel(); + m_image_queue.Cancel(); m_system = nullptr; } } @@ -461,8 +728,12 @@ void AchievementManager::CloseGame() void AchievementManager::Logout() { - CloseGame(); - Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); + { + std::lock_guard lg{m_lock}; + CloseGame(); + m_player_badge.name = ""; + Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); + } if (m_update_callback) m_update_callback(); INFO_LOG_FMT(ACHIEVEMENTS, "Logged out from server."); diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 9b67c1e018..6379cee94a 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -58,6 +58,12 @@ public: using RichPresence = std::array; using Badge = std::vector; + struct BadgeStatus + { + std::string name = ""; + Badge badge{}; + }; + struct UnlockStatus { AchievementId game_data_index = 0; @@ -69,6 +75,8 @@ public: } remote_unlock_status = UnlockType::LOCKED; u32 session_unlock_count = 0; u32 points = 0; + BadgeStatus locked_badge; + BadgeStatus unlocked_badge; }; static AchievementManager* GetInstance(); @@ -84,6 +92,7 @@ public: void ActivateDeactivateAchievements(); void ActivateDeactivateLeaderboards(); void ActivateDeactivateRichPresence(); + void FetchBadges(); void DoFrame(); u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud); @@ -92,10 +101,12 @@ public: std::recursive_mutex* GetLock(); std::string GetPlayerDisplayName() const; u32 GetPlayerScore() const; + const BadgeStatus& GetPlayerBadge() const; std::string GetGameDisplayName() const; PointSpread TallyScore() const; rc_api_fetch_game_data_response_t* GetGameData(); - UnlockStatus GetUnlockStatus(AchievementId achievement_id) const; + const BadgeStatus& GetGameBadge() const; + const UnlockStatus& GetUnlockStatus(AchievementId achievement_id) const; void GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target); RichPresence GetRichPresence(); @@ -138,16 +149,19 @@ private: UpdateCallback m_update_callback; std::string m_display_name; u32 m_player_score = 0; + BadgeStatus m_player_badge; std::array m_game_hash{}; u32 m_game_id = 0; rc_api_fetch_game_data_response_t m_game_data{}; bool m_is_game_loaded = false; + BadgeStatus m_game_badge; RichPresence m_rich_presence; time_t m_last_ping_time = 0; std::unordered_map m_unlock_map; Common::WorkQueueThread> m_queue; + Common::WorkQueueThread> m_image_queue; std::recursive_mutex m_lock; }; // class AchievementManager diff --git a/Source/Core/Core/Config/AchievementSettings.cpp b/Source/Core/Core/Config/AchievementSettings.cpp index 3499fcc791..9211fb712e 100644 --- a/Source/Core/Core/Config/AchievementSettings.cpp +++ b/Source/Core/Core/Config/AchievementSettings.cpp @@ -19,6 +19,7 @@ const Info RA_LEADERBOARDS_ENABLED{ {System::Achievements, "Achievements", "LeaderboardsEnabled"}, false}; const Info RA_RICH_PRESENCE_ENABLED{ {System::Achievements, "Achievements", "RichPresenceEnabled"}, false}; +const Info RA_BADGES_ENABLED{{System::Achievements, "Achievements", "BadgesEnabled"}, false}; const Info RA_UNOFFICIAL_ENABLED{{System::Achievements, "Achievements", "UnofficialEnabled"}, false}; const Info RA_ENCORE_ENABLED{{System::Achievements, "Achievements", "EncoreEnabled"}, false}; diff --git a/Source/Core/Core/Config/AchievementSettings.h b/Source/Core/Core/Config/AchievementSettings.h index 33318f7400..c3dec06d01 100644 --- a/Source/Core/Core/Config/AchievementSettings.h +++ b/Source/Core/Core/Config/AchievementSettings.h @@ -14,6 +14,7 @@ extern const Info RA_API_TOKEN; extern const Info RA_ACHIEVEMENTS_ENABLED; extern const Info RA_LEADERBOARDS_ENABLED; extern const Info RA_RICH_PRESENCE_ENABLED; +extern const Info RA_BADGES_ENABLED; extern const Info RA_UNOFFICIAL_ENABLED; extern const Info RA_ENCORE_ENABLED; } // namespace Config diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 8ec6278aae..7d0b63f9bb 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -47,6 +47,7 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::RA_ACHIEVEMENTS_ENABLED.GetLocation(), &Config::RA_LEADERBOARDS_ENABLED.GetLocation(), &Config::RA_RICH_PRESENCE_ENABLED.GetLocation(), + &Config::RA_BADGES_ENABLED.GetLocation(), &Config::RA_UNOFFICIAL_ENABLED.GetLocation(), &Config::RA_ENCORE_ENABLED.GetLocation(), };