From a7b09413f9aa8a76c06f3371ca692ccac173f974 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Mon, 22 May 2023 22:21:56 -0400 Subject: [PATCH 1/8] Added Notification Popup for Achievement Unlocked Added an OnScreenDisplay message to HandleAchievementTriggeredEvent to display a message when a player has unlocked an achievement. The message includes the title of the achievement and its point value based on the game data. To match RetroAchievements' website interface, the message is blue if the unlock is casual and gold for challenge. --- Source/Core/Core/AchievementManager.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 9c8b3d1a4a..bcd275b10c 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -5,6 +5,8 @@ #include "Core/AchievementManager.h" +#include + #include #include "Common/HttpRequest.h" @@ -14,6 +16,7 @@ #include "Core/PowerPC/MMU.h" #include "Core/System.h" #include "DiscIO/Volume.h" +#include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoEvents.h" static constexpr bool hardcore_mode_enabled = false; @@ -506,6 +509,11 @@ void AchievementManager::HandleAchievementTriggeredEvent(const rc_runtime_event_ return; it->second.session_unlock_count++; m_queue.EmplaceItem([this, runtime_event] { AwardAchievement(runtime_event->id); }); + AchievementId game_data_index = it->second.game_data_index; + OSD::AddMessage(fmt::format("Unlocked: {} ({})", m_game_data.achievements[game_data_index].title, + m_game_data.achievements[game_data_index].points), + OSD::Duration::VERY_LONG, + (hardcore_mode_enabled) ? OSD::Color::YELLOW : OSD::Color::CYAN); ActivateDeactivateAchievement(runtime_event->id, Config::Get(Config::RA_ACHIEVEMENTS_ENABLED), Config::Get(Config::RA_UNOFFICIAL_ENABLED), Config::Get(Config::RA_ENCORE_ENABLED)); From c20d0ae9e12d69b0b36f5d378979e4b1df7e27f2 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Mon, 22 May 2023 22:41:32 -0400 Subject: [PATCH 2/8] Added Notification Popup for Leaderboard Scored Added an OnScreenDisplay message to HandleLeaderboardTriggeredEvent to display a message when a player has completed a leaderboard. The message includes the title of the achievement and the player's score/time. --- Source/Core/Core/AchievementManager.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index bcd275b10c..9599ebe619 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -523,6 +523,16 @@ void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_ { m_queue.EmplaceItem( [this, runtime_event] { SubmitLeaderboard(runtime_event->id, runtime_event->value); }); + for (u32 ix = 0; ix < m_game_data.num_leaderboards; ix++) + { + if (m_game_data.leaderboards[ix].id == runtime_event->id) + { + OSD::AddMessage(fmt::format("Scored {} on leaderboard: {}", runtime_event->value, + m_game_data.leaderboards[ix].title), + OSD::Duration::VERY_LONG, OSD::Color::YELLOW); + break; + } + } } // Every RetroAchievements API call, with only a partial exception for fetch_image, follows From a4b7f43f2126ea82abc1548c7849b839208abec7 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 23 May 2023 00:10:57 -0400 Subject: [PATCH 3/8] Added Notification Popup for Leaderboard Started Added HandleLeaderboardStartedEvent with an OnScreenDisplay message when a player has triggered a leaderboard's start conditions. The message includes the title of the achievement. --- Source/Core/Core/AchievementManager.cpp | 16 ++++++++++++++++ Source/Core/Core/AchievementManager.h | 1 + 2 files changed, 17 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 9599ebe619..abf6d89367 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -266,6 +266,9 @@ void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runti case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED: HandleAchievementTriggeredEvent(runtime_event); break; + case RC_RUNTIME_EVENT_LBOARD_STARTED: + HandleLeaderboardStartedEvent(runtime_event); + break; case RC_RUNTIME_EVENT_LBOARD_TRIGGERED: HandleLeaderboardTriggeredEvent(runtime_event); break; @@ -519,6 +522,19 @@ void AchievementManager::HandleAchievementTriggeredEvent(const rc_runtime_event_ Config::Get(Config::RA_ENCORE_ENABLED)); } +void AchievementManager::HandleLeaderboardStartedEvent(const rc_runtime_event_t* runtime_event) +{ + for (u32 ix = 0; ix < m_game_data.num_leaderboards; ix++) + { + if (m_game_data.leaderboards[ix].id == runtime_event->id) + { + OSD::AddMessage(fmt::format("Attempting leaderboard: {}", m_game_data.leaderboards[ix].title), + OSD::Duration::VERY_LONG, OSD::Color::GREEN); + break; + } + } +} + void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event) { m_queue.EmplaceItem( diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index b6cbcde732..3c5855c7c5 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -80,6 +80,7 @@ private: ResponseType PingRichPresence(const RichPresence& rich_presence); void HandleAchievementTriggeredEvent(const rc_runtime_event_t* runtime_event); + void HandleLeaderboardStartedEvent(const rc_runtime_event_t* runtime_event); void HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event); template From b0eb4ccb8012c308941ef1eba279f73622d29d60 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 23 May 2023 20:45:04 -0400 Subject: [PATCH 4/8] Added Notification Popup for Leaderboard Canceled Added HandleLeaderboardStartedEvent with an OnScreenDisplay message when a player has triggered a leaderboard's failure conditions. The message includes the title of the achievement. --- Source/Core/Core/AchievementManager.cpp | 13 +++++++++++++ Source/Core/Core/AchievementManager.h | 1 + 2 files changed, 14 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index abf6d89367..2b6aa0b9a4 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -535,6 +535,19 @@ void AchievementManager::HandleLeaderboardStartedEvent(const rc_runtime_event_t* } } +void AchievementManager::HandleLeaderboardCanceledEvent(const rc_runtime_event_t* runtime_event) +{ + for (u32 ix = 0; ix < m_game_data.num_leaderboards; ix++) + { + if (m_game_data.leaderboards[ix].id == runtime_event->id) + { + OSD::AddMessage(fmt::format("Failed leaderboard: {}", m_game_data.leaderboards[ix].title), + OSD::Duration::VERY_LONG, OSD::Color::RED); + break; + } + } +} + void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event) { m_queue.EmplaceItem( diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 3c5855c7c5..7c89be7c59 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -81,6 +81,7 @@ private: void HandleAchievementTriggeredEvent(const rc_runtime_event_t* runtime_event); void HandleLeaderboardStartedEvent(const rc_runtime_event_t* runtime_event); + void HandleLeaderboardCanceledEvent(const rc_runtime_event_t* runtime_event); void HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event); template From 22af13f9e02f0177bbca560ea56bcd7d08346cfe Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 23 May 2023 20:47:38 -0400 Subject: [PATCH 5/8] Added Notification Popups for No Achievement Data Added OnScreenDisplay messages to LoadGameByFilenameAsync to notify the player when any of the potential failures occur. --- Source/Core/Core/AchievementManager.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 2b6aa0b9a4..4420bec0cf 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -126,6 +126,8 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, if (resolve_hash_response != ResponseType::SUCCESS || m_game_id == 0) { callback(resolve_hash_response); + OSD::AddMessage("No RetroAchievements data found for this game.", OSD::Duration::VERY_LONG, + OSD::Color::RED); return; } @@ -133,11 +135,18 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, if (start_session_response != ResponseType::SUCCESS) { callback(start_session_response); + OSD::AddMessage("Failed to connect to RetroAchievements server.", OSD::Duration::VERY_LONG, + OSD::Color::RED); return; } const auto fetch_game_data_response = FetchGameData(); m_is_game_loaded = fetch_game_data_response == ResponseType::SUCCESS; + if (!m_is_game_loaded) + { + OSD::AddMessage("Unable to retrieve data from RetroAchievements server.", + OSD::Duration::VERY_LONG, OSD::Color::RED); + } // Claim the lock, then queue the fetch unlock data calls, then initialize the unlock map in // ActivateDeactiveAchievements. This allows the calls to process while initializing the From aa0224a8ab3f5e450de0fcc1e53906029e6b93b1 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 23 May 2023 08:38:53 -0400 Subject: [PATCH 6/8] Added TallyScore method to AchievementManager Added a TallyScore method to AchievementManager to calculate the player's current scores (soft and hard) against the total number of points across all achievements. Includes a PointSpread structure to hold all points and counts. The Unlock Map now includes point values for each achievement to aid in this calculation; this is populated at insert time, and Unofficial achievements are tallied as zero. --- Source/Core/Core/AchievementManager.cpp | 31 +++++++++++++++++++++++-- Source/Core/Core/AchievementManager.h | 15 +++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 4420bec0cf..b6a1879642 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -185,8 +185,11 @@ void AchievementManager::ActivateDeactivateAchievements() bool encore = Config::Get(Config::RA_ENCORE_ENABLED); for (u32 ix = 0; ix < m_game_data.num_achievements; ix++) { - auto iter = - m_unlock_map.insert({m_game_data.achievements[ix].id, UnlockStatus{.game_data_index = ix}}); + u32 points = (m_game_data.achievements[ix].category == RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL) ? + 0 : + m_game_data.achievements[ix].points; + auto iter = m_unlock_map.insert( + {m_game_data.achievements[ix].id, UnlockStatus{.game_data_index = ix, .points = points}}); ActivateDeactivateAchievement(iter.first->first, enabled, unofficial, encore); } } @@ -573,6 +576,30 @@ void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_ } } +AchievementManager::PointSpread AchievementManager::TallyScore() const +{ + PointSpread spread{}; + for (const auto& entry : m_unlock_map) + { + u32 points = entry.second.points; + spread.total_count++; + spread.total_points += points; + if (entry.second.remote_unlock_status == UnlockStatus::UnlockType::HARDCORE || + (hardcore_mode_enabled && entry.second.session_unlock_count > 0)) + { + spread.hard_unlocks++; + spread.hard_points += points; + } + else if (entry.second.remote_unlock_status == UnlockStatus::UnlockType::SOFTCORE || + entry.second.session_unlock_count > 0) + { + spread.soft_unlocks++; + spread.soft_points += points; + } + } + return spread; +} + // Every RetroAchievements API call, with only a partial exception for fetch_image, follows // the same design pattern (here, X is the name of the call): // Create a specific rc_api_X_request_t struct and populate with the necessary values diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 7c89be7c59..da11f930ce 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -41,6 +41,16 @@ public: }; using ResponseCallback = std::function; + struct PointSpread + { + u32 total_count; + u32 total_points; + u32 hard_unlocks; + u32 hard_points; + u32 soft_unlocks; + u32 soft_points; + }; + static AchievementManager* GetInstance(); void Init(); ResponseType Login(const std::string& password); @@ -84,6 +94,8 @@ private: void HandleLeaderboardCanceledEvent(const rc_runtime_event_t* runtime_event); void HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event); + PointSpread TallyScore() const; + template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, const std::function& init_request, @@ -107,7 +119,8 @@ private: SOFTCORE, HARDCORE } remote_unlock_status = UnlockType::LOCKED; - int session_unlock_count = 0; + u32 session_unlock_count = 0; + u32 points = 0; }; std::unordered_map m_unlock_map; From 184108905425b3fc453dfc210ae966a69668bac3 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 23 May 2023 08:51:54 -0400 Subject: [PATCH 7/8] Added Notification Popups for Game Start Added an OnScreenDisplay message to LoadGameByFilenameAsync to display a message when a player starts a game with achievements, notifying them of their current score. The score displayed is challenge points if the player is in challenge mode or challenge + casual if the player is in casual mode. A second message tells the player which mode they are in. To match RetroAchievements' website interface, the messages are blue for casual and gold for challenge. --- Source/Core/Core/AchievementManager.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index b6a1879642..10caf88387 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -156,6 +156,23 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, std::lock_guard lg{m_lock}; LoadUnlockData([](ResponseType r_type) {}); ActivateDeactivateAchievements(); + PointSpread spread = TallyScore(); + if (hardcore_mode_enabled) + { + OSD::AddMessage(fmt::format("You have {}/{} achievements worth {}/{} points", + spread.hard_unlocks, spread.total_count, spread.hard_points, + spread.total_points), + OSD::Duration::VERY_LONG, OSD::Color::YELLOW); + OSD::AddMessage("Hardcore mode is ON", OSD::Duration::VERY_LONG, OSD::Color::YELLOW); + } + else + { + OSD::AddMessage(fmt::format("You have {}/{} achievements worth {}/{} points", + spread.hard_unlocks + spread.soft_unlocks, spread.total_count, + spread.hard_points + spread.soft_points, spread.total_points), + OSD::Duration::VERY_LONG, OSD::Color::CYAN); + OSD::AddMessage("Hardcore mode is OFF", OSD::Duration::VERY_LONG, OSD::Color::CYAN); + } } ActivateDeactivateLeaderboards(); ActivateDeactivateRichPresence(); @@ -591,7 +608,7 @@ AchievementManager::PointSpread AchievementManager::TallyScore() const spread.hard_points += points; } else if (entry.second.remote_unlock_status == UnlockStatus::UnlockType::SOFTCORE || - entry.second.session_unlock_count > 0) + entry.second.session_unlock_count > 0) { spread.soft_unlocks++; spread.soft_points += points; From 57290a45e864dde336810089aec538adbb24f97f Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 23 May 2023 09:04:14 -0400 Subject: [PATCH 8/8] Added Notification Popups for Game Mastery Added OnScreenDisplay messages to HandleAchievementTriggeredEvent to display an extra congratulations message if the player has completed (unlocked all achievements in casual) or mastered (unlocked all achievements in challenge) the game. This also uses the display name retrieved when verifying credentials, which has now been added as a member field on AchievementManager. --- Source/Core/Core/AchievementManager.cpp | 14 ++++++++++++++ Source/Core/Core/AchievementManager.h | 1 + 2 files changed, 15 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 10caf88387..81c3493dca 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -340,6 +340,7 @@ AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std .username = username.c_str(), .api_token = api_token.c_str(), .password = password.c_str()}; ResponseType r_type = Request( login_request, &login_data, rc_api_init_login_request, rc_api_process_login_response); + m_display_name = login_data.display_name; if (r_type == ResponseType::SUCCESS) Config::SetBaseOrCurrent(Config::RA_API_TOKEN, login_data.api_token); rc_api_destroy_login_response(&login_data); @@ -546,6 +547,19 @@ void AchievementManager::HandleAchievementTriggeredEvent(const rc_runtime_event_ m_game_data.achievements[game_data_index].points), OSD::Duration::VERY_LONG, (hardcore_mode_enabled) ? OSD::Color::YELLOW : OSD::Color::CYAN); + PointSpread spread = TallyScore(); + if (spread.hard_points == spread.total_points) + { + OSD::AddMessage( + fmt::format("Congratulations! {} has mastered {}", m_display_name, m_game_data.title), + OSD::Duration::VERY_LONG, OSD::Color::YELLOW); + } + else if (spread.hard_points + spread.soft_points == spread.total_points) + { + OSD::AddMessage( + fmt::format("Congratulations! {} has completed {}", m_display_name, m_game_data.title), + OSD::Duration::VERY_LONG, OSD::Color::CYAN); + } ActivateDeactivateAchievement(runtime_event->id, Config::Get(Config::RA_ACHIEVEMENTS_ENABLED), Config::Get(Config::RA_UNOFFICIAL_ENABLED), Config::Get(Config::RA_ENCORE_ENABLED)); diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index da11f930ce..8e01b9220e 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -104,6 +104,7 @@ private: rc_runtime_t m_runtime{}; Core::System* m_system{}; bool m_is_runtime_initialized = false; + std::string m_display_name; std::array m_game_hash{}; u32 m_game_id = 0; rc_api_fetch_game_data_response_t m_game_data{};