From 05136ee099a3384e1a0783cf7a7fd588a22f1e32 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 14 Mar 2024 16:42:33 -0400 Subject: [PATCH 01/31] Add rc_client RequestV2 to AchievementManager RequestV2 is to be passed into rc_client upon construction so rc_client can handle server calls internally. --- Source/Core/Core/AchievementManager.cpp | 45 ++++++++++++++++++++++++- Source/Core/Core/AchievementManager.h | 4 +++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 9bb3bcddc2..c0f45d3c21 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -12,7 +12,6 @@ #include #include -#include "Common/HttpRequest.h" #include "Common/Image.h" #include "Common/Logging/Log.h" #include "Common/WorkQueueThread.h" @@ -1706,4 +1705,48 @@ static std::unique_ptr DecodeBadgeToOSDIcon(const AchievementManager: return icon; } +void AchievementManager::RequestV2(const rc_api_request_t* request, + rc_client_server_callback_t callback, void* callback_data, + rc_client_t* client) +{ + std::string url = request->url; + std::string post_data = request->post_data; + AchievementManager::GetInstance().m_queue.EmplaceItem([url = std::move(url), + post_data = std::move(post_data), + callback = std::move(callback), + callback_data = std::move(callback_data)] { + const Common::HttpRequest::Headers USER_AGENT_HEADER = {{"User-Agent", "Dolphin/Placeholder"}}; + + Common::HttpRequest http_request; + Common::HttpRequest::Response http_response; + if (!post_data.empty()) + { + http_response = http_request.Post(url, post_data, USER_AGENT_HEADER, + Common::HttpRequest::AllowedReturnCodes::All); + } + else + { + http_response = + http_request.Get(url, USER_AGENT_HEADER, Common::HttpRequest::AllowedReturnCodes::All); + } + + rc_api_server_response_t server_response; + if (http_response.has_value() && http_response->size() > 0) + { + server_response.body = reinterpret_cast(http_response->data()); + server_response.body_length = http_response->size(); + server_response.http_status_code = http_request.GetLastResponseCode(); + } + else + { + constexpr char error_message[] = "Failed HTTP request."; + server_response.body = error_message; + server_response.body_length = sizeof(error_message); + server_response.http_status_code = RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR; + } + + callback(&server_response, callback_data); + }); +} + #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 770e64c18c..81ffd44be6 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -15,9 +15,11 @@ #include #include +#include #include #include "Common/Event.h" +#include "Common/HttpRequest.h" #include "Common/WorkQueueThread.h" #include "DiscIO/Volume.h" @@ -202,6 +204,8 @@ private: const std::function& init_request, const std::function& process_response); ResponseType RequestImage(rc_api_fetch_image_request_t rc_request, Badge* rc_response); + static void RequestV2(const rc_api_request_t* request, rc_client_server_callback_t callback, + void* callback_data, rc_client_t* client); rc_runtime_t m_runtime{}; Core::System* m_system{}; From 4ec662bcdfd9bcdeb1481f6e2634f5ddbbc6192b Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 14 Mar 2024 19:08:01 -0400 Subject: [PATCH 02/31] Add rc_client MemoryPeekerV2 to AchievementManager Has a more traditional read-x-bytes structure; for passing into rc_client in the constructor. --- Source/Core/Core/AchievementManager.cpp | 17 +++++++++++++++++ Source/Core/Core/AchievementManager.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index c0f45d3c21..86f65a716a 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -1749,4 +1749,21 @@ void AchievementManager::RequestV2(const rc_api_request_t* request, }); } +u32 AchievementManager::MemoryPeekerV2(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client) +{ + if (buffer == nullptr) + return 0u; + auto& system = Core::System::GetInstance(); + Core::CPUThreadGuard threadguard(system); + for (u32 num_read = 0; num_read < num_bytes; num_read++) + { + auto value = system.GetMMU().HostTryReadU8(threadguard, address + num_read, + PowerPC::RequestedAddressSpace::Physical); + if (!value.has_value()) + return num_read; + buffer[num_read] = value.value().value; + } + return num_bytes; +} + #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 81ffd44be6..4429b07bff 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -204,8 +204,10 @@ private: const std::function& init_request, const std::function& process_response); ResponseType RequestImage(rc_api_fetch_image_request_t rc_request, Badge* rc_response); + static void RequestV2(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); + static u32 MemoryPeekerV2(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); rc_runtime_t m_runtime{}; Core::System* m_system{}; From ba519e4670b4e85df1ae03ba9474dd4f52f39343 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sun, 17 Mar 2024 08:26:49 -0400 Subject: [PATCH 03/31] Add rc_client to AchievementManager Also includes init and shutdown V2s. --- Source/Core/Core/AchievementManager.cpp | 30 ++++++++++++++++--------- Source/Core/Core/AchievementManager.h | 1 + 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 86f65a716a..fcf4deb237 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -33,13 +33,18 @@ AchievementManager& AchievementManager::GetInstance() void AchievementManager::Init() { - if (!m_is_runtime_initialized && Config::Get(Config::RA_ENABLED)) + if (!m_client && Config::Get(Config::RA_ENABLED)) { + m_client = rc_client_create(MemoryPeekerV2, RequestV2); std::string host_url = Config::Get(Config::RA_HOST_URL); if (!host_url.empty()) - rc_api_set_host(host_url.c_str()); - rc_runtime_init(&m_runtime); - m_is_runtime_initialized = true; + rc_client_set_host(m_client, host_url.c_str()); + rc_client_enable_logging(m_client, RC_CLIENT_LOG_LEVEL_VERBOSE, + [](const char* message, const rc_client_t* client) { + INFO_LOG_FMT(ACHIEVEMENTS, "{}", message); + }); + rc_client_set_hardcore_enabled(m_client, 0); + rc_client_set_unofficial_enabled(m_client, 1); m_queue.Reset("AchievementManagerQueue", [](const std::function& func) { func(); }); m_image_queue.Reset("AchievementManagerImageQueue", [](const std::function& func) { func(); }); @@ -942,13 +947,16 @@ void AchievementManager::Logout() void AchievementManager::Shutdown() { - CloseGame(); - SetDisabled(false); - m_is_runtime_initialized = false; - m_queue.Shutdown(); - // DON'T log out - keep those credentials for next run. - rc_runtime_destroy(&m_runtime); - INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager shut down."); + if (m_client) + { + CloseGame(); + SetDisabled(false); + m_queue.Shutdown(); + // DON'T log out - keep those credentials for next run. + rc_client_destroy(m_client); + m_client = nullptr; + INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager shut down."); + } } void* AchievementManager::FilereaderOpenByFilepath(const char* path_utf8) diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 4429b07bff..8676544c22 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -210,6 +210,7 @@ private: static u32 MemoryPeekerV2(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); rc_runtime_t m_runtime{}; + rc_client_t* m_client{}; Core::System* m_system{}; bool m_is_runtime_initialized = false; UpdateCallback m_update_callback = [] {}; From 7b3fac18cd8073db97df5673c41ee72288b2e493 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Mon, 18 Mar 2024 16:33:37 -0400 Subject: [PATCH 04/31] Remove synchronous achievement login Deletes AchievementManager::Login, renames LoginAsync to Login, and replaces the one synchronous call in the AchievementSettingsWidget with the async call. There is a minor usability regression in that the UI currently does not notify the user when a login has failed; this will be addressed in a later change (possibly in a different PR). --- Source/Core/Core/AchievementManager.cpp | 20 ++----------------- Source/Core/Core/AchievementManager.h | 3 +-- .../AchievementSettingsWidget.cpp | 4 ++-- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index fcf4deb237..097fdb9b5d 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -49,7 +49,7 @@ void AchievementManager::Init() m_image_queue.Reset("AchievementManagerImageQueue", [](const std::function& func) { func(); }); if (IsLoggedIn()) - LoginAsync("", [](ResponseType r_type) {}); + Login("", [](ResponseType r_type) {}); INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager Initialized"); } } @@ -64,23 +64,7 @@ void AchievementManager::SetUpdateCallback(UpdateCallback callback) m_update_callback(); } -AchievementManager::ResponseType AchievementManager::Login(const std::string& password) -{ - if (!m_is_runtime_initialized) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Attempted login (sync) to RetroAchievements server without " - "Achievement Manager initialized."); - return ResponseType::MANAGER_NOT_INITIALIZED; - } - - const ResponseType r_type = VerifyCredentials(password); - FetchBadges(); - - m_update_callback(); - return r_type; -} - -void AchievementManager::LoginAsync(const std::string& password, const ResponseCallback& callback) +void AchievementManager::Login(const std::string& password, const ResponseCallback& callback) { if (!m_is_runtime_initialized) { diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 8676544c22..f819610729 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -117,8 +117,7 @@ public: static AchievementManager& GetInstance(); void Init(); void SetUpdateCallback(UpdateCallback callback); - ResponseType Login(const std::string& password); - void LoginAsync(const std::string& password, const ResponseCallback& callback); + void Login(const std::string& password, const ResponseCallback& callback); bool IsLoggedIn() const; void HashGame(const std::string& file_path, const ResponseCallback& callback); void HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback); diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index 07ec6fb78f..ec8fde6293 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -254,9 +254,9 @@ void AchievementSettingsWidget::ToggleRAIntegration() void AchievementSettingsWidget::Login() { Config::SetBaseOrCurrent(Config::RA_USERNAME, m_common_username_input->text().toStdString()); - AchievementManager::GetInstance().Login(m_common_password_input->text().toStdString()); + AchievementManager::GetInstance().Login(m_common_password_input->text().toStdString(), + [](AchievementManager::ResponseType r_type) {}); m_common_password_input->setText(QString()); - m_common_login_failed->setVisible(Config::Get(Config::RA_API_TOKEN).empty()); SaveSettings(); } From 3bf8b5fb9075fa8e339c85b3aa9125de9eaea137 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 19 Mar 2024 12:41:18 -0400 Subject: [PATCH 05/31] Refactored Achievement Manager Login to use rc_client LoginCallback was created to handle the results of the asynchronous client login calls; VerifyCredentials was deleted as no longer necessary. --- Source/Core/Core/AchievementManager.cpp | 116 ++++++++++-------- Source/Core/Core/AchievementManager.h | 7 +- .../Achievements/AchievementHeaderWidget.cpp | 2 +- .../AchievementSettingsWidget.cpp | 3 +- .../Achievements/AchievementsWindow.cpp | 2 +- 5 files changed, 73 insertions(+), 57 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 097fdb9b5d..2eeec7bd5a 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -5,6 +5,7 @@ #include "Core/AchievementManager.h" +#include #include #include @@ -48,8 +49,8 @@ void AchievementManager::Init() m_queue.Reset("AchievementManagerQueue", [](const std::function& func) { func(); }); m_image_queue.Reset("AchievementManagerImageQueue", [](const std::function& func) { func(); }); - if (IsLoggedIn()) - Login("", [](ResponseType r_type) {}); + if (HasAPIToken()) + Login(""); INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager Initialized"); } } @@ -64,30 +65,36 @@ void AchievementManager::SetUpdateCallback(UpdateCallback callback) m_update_callback(); } -void AchievementManager::Login(const std::string& password, const ResponseCallback& callback) +void AchievementManager::Login(const std::string& password) { - if (!m_is_runtime_initialized) + if (!m_client) { - ERROR_LOG_FMT(ACHIEVEMENTS, "Attempted login (async) to RetroAchievements server without " - "Achievement Manager initialized."); - callback(ResponseType::MANAGER_NOT_INITIALIZED); + ERROR_LOG_FMT( + ACHIEVEMENTS, + "Attempted login to RetroAchievements server without achievement client initialized."); return; } - m_queue.EmplaceItem([this, password, callback] { - callback(VerifyCredentials(password)); - FetchBadges(); - m_update_callback(); - }); + if (password.empty()) + { + rc_client_begin_login_with_token(m_client, Config::Get(Config::RA_USERNAME).c_str(), + Config::Get(Config::RA_API_TOKEN).c_str(), LoginCallback, + nullptr); + } + else + { + rc_client_begin_login_with_password(m_client, Config::Get(Config::RA_USERNAME).c_str(), + password.c_str(), LoginCallback, nullptr); + } } -bool AchievementManager::IsLoggedIn() const +bool AchievementManager::HasAPIToken() const { return !Config::Get(Config::RA_API_TOKEN).empty(); } void AchievementManager::HashGame(const std::string& file_path, const ResponseCallback& callback) { - if (!Config::Get(Config::RA_ENABLED) || !IsLoggedIn()) + if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) { callback(ResponseType::NOT_ENABLED); return; @@ -142,7 +149,7 @@ void AchievementManager::HashGame(const std::string& file_path, const ResponseCa void AchievementManager::HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback) { - if (!Config::Get(Config::RA_ENABLED) || !IsLoggedIn()) + if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) { callback(ResponseType::NOT_ENABLED); return; @@ -220,7 +227,7 @@ void AchievementManager::HashGame(const DiscIO::Volume* volume, const ResponseCa void AchievementManager::LoadGameSync(const ResponseCallback& callback) { - if (!Config::Get(Config::RA_ENABLED) || !IsLoggedIn()) + if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) { callback(ResponseType::NOT_ENABLED); return; @@ -316,7 +323,7 @@ bool AchievementManager::IsGameLoaded() const void AchievementManager::LoadUnlockData(const ResponseCallback& callback) { - if (!Config::Get(Config::RA_ENABLED) || !IsLoggedIn()) + if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) { callback(ResponseType::NOT_ENABLED); return; @@ -339,7 +346,7 @@ void AchievementManager::LoadUnlockData(const ResponseCallback& callback) void AchievementManager::ActivateDeactivateAchievements() { std::lock_guard lg{m_lock}; - if (!Config::Get(Config::RA_ENABLED) || !IsLoggedIn()) + if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) return; bool enabled = Config::Get(Config::RA_ACHIEVEMENTS_ENABLED); bool unofficial = Config::Get(Config::RA_UNOFFICIAL_ENABLED); @@ -359,7 +366,7 @@ void AchievementManager::ActivateDeactivateAchievements() void AchievementManager::ActivateDeactivateLeaderboards() { std::lock_guard lg{m_lock}; - if (!Config::Get(Config::RA_ENABLED) || !IsLoggedIn()) + if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) return; bool leaderboards_enabled = Config::Get(Config::RA_LEADERBOARDS_ENABLED) && Config::Get(Config::RA_HARDCORE_ENABLED); @@ -386,7 +393,7 @@ void AchievementManager::ActivateDeactivateLeaderboards() void AchievementManager::ActivateDeactivateRichPresence() { std::lock_guard lg{m_lock}; - if (!Config::Get(Config::RA_ENABLED) || !IsLoggedIn()) + if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) return; rc_runtime_activate_richpresence( &m_runtime, @@ -399,7 +406,7 @@ void AchievementManager::ActivateDeactivateRichPresence() void AchievementManager::FetchBadges() { - if (!m_is_runtime_initialized || !IsLoggedIn() || !Config::Get(Config::RA_BADGES_ENABLED)) + if (!m_is_runtime_initialized || !HasAPIToken() || !Config::Get(Config::RA_BADGES_ENABLED)) { m_update_callback(); return; @@ -761,12 +768,12 @@ bool AchievementManager::IsHardcoreModeActive() const std::string AchievementManager::GetPlayerDisplayName() const { - return IsLoggedIn() ? m_display_name : ""; + return HasAPIToken() ? m_display_name : ""; } u32 AchievementManager::GetPlayerScore() const { - return IsLoggedIn() ? m_player_score : 0; + return HasAPIToken() ? m_player_score : 0; } const AchievementManager::BadgeStatus& AchievementManager::GetPlayerBadge() const @@ -1007,40 +1014,49 @@ void AchievementManager::FilereaderClose(void* file_handle) delete static_cast(file_handle); } -AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std::string& password) +void AchievementManager::LoginCallback(int result, const char* error_message, rc_client_t* client, + void* userdata) { - rc_api_login_response_t login_data{}; - std::string username, api_token; + if (result != RC_OK) { - std::lock_guard lg{m_lock}; - username = Config::Get(Config::RA_USERNAME); - api_token = Config::Get(Config::RA_API_TOKEN); + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to login {} to RetroAchievements server.", + Config::Get(Config::RA_USERNAME)); + return; } - rc_api_login_request_t login_request = { - .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); - if (r_type == ResponseType::SUCCESS) + + const rc_client_user_t* user; { - INFO_LOG_FMT(ACHIEVEMENTS, "Successfully logged in {} to RetroAchievements server.", username); - std::lock_guard lg{m_lock}; - if (username != Config::Get(Config::RA_USERNAME)) + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; + user = rc_client_get_user_info(client); + } + if (!user) + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to retrieve user information from client."); + return; + } + + std::string config_username = Config::Get(Config::RA_USERNAME); + if (config_username != user->username) + { + if (Common::CaseInsensitiveEquals(config_username, user->username)) { - INFO_LOG_FMT(ACHIEVEMENTS, "Attempted to login prior user {}; current user is {}.", username, - Config::Get(Config::RA_USERNAME)); - Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); - return ResponseType::EXPIRED_CONTEXT; + INFO_LOG_FMT(ACHIEVEMENTS, + "Case mismatch between site {} and local {}; updating local config.", + user->username, Config::Get(Config::RA_USERNAME)); + Config::SetBaseOrCurrent(Config::RA_USERNAME, user->username); + } + else + { + INFO_LOG_FMT(ACHIEVEMENTS, "Attempted to login prior user {}; current user is {}.", + user->username, Config::Get(Config::RA_USERNAME)); + rc_client_logout(client); + return; } - Config::SetBaseOrCurrent(Config::RA_API_TOKEN, login_data.api_token); - m_display_name = login_data.display_name; - m_player_score = login_data.score; } - else - { - WARN_LOG_FMT(ACHIEVEMENTS, "Failed to login {} to RetroAchievements server.", username); - } - rc_api_destroy_login_response(&login_data); - return r_type; + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully logged in {} to RetroAchievements server.", + user->username); + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; + Config::SetBaseOrCurrent(Config::RA_API_TOKEN, user->token); } AchievementManager::ResponseType AchievementManager::ResolveHash(const Hash& game_hash, diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index f819610729..3f4c3d88cf 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -117,8 +117,8 @@ public: static AchievementManager& GetInstance(); void Init(); void SetUpdateCallback(UpdateCallback callback); - void Login(const std::string& password, const ResponseCallback& callback); - bool IsLoggedIn() const; + void Login(const std::string& password); + bool HasAPIToken() const; void HashGame(const std::string& file_path, const ResponseCallback& callback); void HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback); bool IsGameLoaded() const; @@ -171,7 +171,8 @@ private: static size_t FilereaderRead(void* file_handle, void* buffer, size_t requested_bytes); static void FilereaderClose(void* file_handle); - ResponseType VerifyCredentials(const std::string& password); + static void LoginCallback(int result, const char* error_message, rc_client_t* client, + void* userdata); ResponseType ResolveHash(const Hash& game_hash, u32* game_id); void LoadGameSync(const ResponseCallback& callback); ResponseType StartRASession(); diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index e844d22d39..0aea31d5bf 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -69,7 +69,7 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare void AchievementHeaderWidget::UpdateData() { auto& instance = AchievementManager::GetInstance(); - if (!instance.IsLoggedIn()) + if (!instance.HasAPIToken()) { m_header_box->setVisible(false); return; diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index ec8fde6293..01e1470175 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -254,8 +254,7 @@ void AchievementSettingsWidget::ToggleRAIntegration() void AchievementSettingsWidget::Login() { Config::SetBaseOrCurrent(Config::RA_USERNAME, m_common_username_input->text().toStdString()); - AchievementManager::GetInstance().Login(m_common_password_input->text().toStdString(), - [](AchievementManager::ResponseType r_type) {}); + AchievementManager::GetInstance().Login(m_common_password_input->text().toStdString()); m_common_password_input->setText(QString()); SaveSettings(); } diff --git a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp index 3704884a2b..f8dcda0a25 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp @@ -79,7 +79,7 @@ void AchievementsWindow::UpdateData() const bool is_game_loaded = instance.IsGameLoaded(); m_header_widget->UpdateData(); - m_header_widget->setVisible(instance.IsLoggedIn()); + m_header_widget->setVisible(instance.HasAPIToken()); m_settings_widget->UpdateData(); m_progress_widget->UpdateData(); m_tab_widget->setTabVisible(1, is_game_loaded); From 355b89262101c50195908fa42323cc8af5d84cf1 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 19 Mar 2024 13:33:51 -0400 Subject: [PATCH 06/31] Get display name and score from client Delete m_display_name and m_player_score fields and retrieve those values directly from the client's user information. --- Source/Core/Core/AchievementManager.cpp | 43 ++++++++++++------- Source/Core/Core/AchievementManager.h | 4 +- .../Achievements/AchievementHeaderWidget.cpp | 3 +- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 2eeec7bd5a..231cbd5502 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -413,15 +413,16 @@ void AchievementManager::FetchBadges() } m_image_queue.Cancel(); - if (m_player_badge.name != m_display_name) + auto* user = rc_client_get_user_info(m_client); + if (m_player_badge.name.compare(user->display_name) != 0) { - m_image_queue.EmplaceItem([this] { + m_image_queue.EmplaceItem([this, user] { std::string name_to_fetch; { std::lock_guard lg{m_lock}; - if (m_display_name == m_player_badge.name) + if (m_player_badge.name.compare(user->display_name) == 0) return; - name_to_fetch = m_display_name; + name_to_fetch.assign(user->display_name); } rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), .image_type = RC_IMAGE_TYPE_USER}; @@ -430,10 +431,10 @@ void AchievementManager::FetchBadges() { 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) + if (name_to_fetch.compare(user->display_name) != 0) { INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {} for player id {}.", - name_to_fetch, m_display_name); + name_to_fetch, user->display_name); return; } m_player_badge.badge = std::move(fetched_badge); @@ -766,14 +767,24 @@ bool AchievementManager::IsHardcoreModeActive() const return (m_runtime.trigger_count + m_runtime.lboard_count > 0); } -std::string AchievementManager::GetPlayerDisplayName() const +std::string_view AchievementManager::GetPlayerDisplayName() const { - return HasAPIToken() ? m_display_name : ""; + if (!HasAPIToken()) + return ""; + auto* user = rc_client_get_user_info(m_client); + if (!user) + return ""; + return std::string_view(user->display_name); } u32 AchievementManager::GetPlayerScore() const { - return HasAPIToken() ? m_player_score : 0; + if (!HasAPIToken()) + return 0; + auto* user = rc_client_get_user_info(m_client); + if (!user) + return 0; + return user->score; } const AchievementManager::BadgeStatus& AchievementManager::GetPlayerBadge() const @@ -1477,13 +1488,14 @@ void AchievementManager::HandleAchievementTriggeredEvent(const rc_runtime_event_ nullptr); if (m_game_data.achievements[game_data_index].category == RC_ACHIEVEMENT_CATEGORY_CORE) { + auto* user = rc_client_get_user_info(m_client); m_queue.EmplaceItem([this, event_id] { AwardAchievement(event_id); }); PointSpread spread = TallyScore(); if (spread.hard_points == spread.total_points && it->second.remote_unlock_status != UnlockStatus::UnlockType::HARDCORE) { OSD::AddMessage( - fmt::format("Congratulations! {} has mastered {}", m_display_name, m_game_data.title), + fmt::format("Congratulations! {} has mastered {}", user->display_name, m_game_data.title), OSD::Duration::VERY_LONG, OSD::Color::YELLOW, (Config::Get(Config::RA_BADGES_ENABLED)) ? DecodeBadgeToOSDIcon(m_game_badge.badge) : nullptr); @@ -1491,11 +1503,12 @@ void AchievementManager::HandleAchievementTriggeredEvent(const rc_runtime_event_ else if (spread.hard_points + spread.soft_points == spread.total_points && it->second.remote_unlock_status == UnlockStatus::UnlockType::LOCKED) { - OSD::AddMessage( - fmt::format("Congratulations! {} has completed {}", m_display_name, m_game_data.title), - OSD::Duration::VERY_LONG, OSD::Color::CYAN, - (Config::Get(Config::RA_BADGES_ENABLED)) ? DecodeBadgeToOSDIcon(m_game_badge.badge) : - nullptr); + OSD::AddMessage(fmt::format("Congratulations! {} has completed {}", user->display_name, + m_game_data.title), + OSD::Duration::VERY_LONG, OSD::Color::CYAN, + (Config::Get(Config::RA_BADGES_ENABLED)) ? + DecodeBadgeToOSDIcon(m_game_badge.badge) : + nullptr); } } ActivateDeactivateAchievement(event_id, Config::Get(Config::RA_ACHIEVEMENTS_ENABLED), diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 3f4c3d88cf..b385e9ed30 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -135,7 +135,7 @@ public: std::recursive_mutex& GetLock(); bool IsHardcoreModeActive() const; - std::string GetPlayerDisplayName() const; + std::string_view GetPlayerDisplayName() const; u32 GetPlayerScore() const; const BadgeStatus& GetPlayerBadge() const; std::string GetGameDisplayName() const; @@ -216,8 +216,6 @@ private: UpdateCallback m_update_callback = [] {}; std::unique_ptr m_loading_volume; bool m_disabled = false; - std::string m_display_name; - u32 m_player_score = 0; BadgeStatus m_player_badge; Hash m_game_hash{}; u32 m_game_id = 0; diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index 0aea31d5bf..4d79dec531 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -15,6 +15,7 @@ #include "Core/Config/AchievementSettings.h" #include "Core/Core.h" +#include "DolphinQt/QtUtils/FromStdString.h" #include "DolphinQt/Settings.h" AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(parent) @@ -76,7 +77,7 @@ void AchievementHeaderWidget::UpdateData() } AchievementManager::PointSpread point_spread = instance.TallyScore(); - QString user_name = QString::fromStdString(instance.GetPlayerDisplayName()); + QString user_name = QtUtils::FromStdString(instance.GetPlayerDisplayName()); QString game_name = QString::fromStdString(instance.GetGameDisplayName()); AchievementManager::BadgeStatus player_badge = instance.GetPlayerBadge(); AchievementManager::BadgeStatus game_badge = instance.GetGameBadge(); From 486a9d2318cf379ebf484ef4020ac0df8f47f7d1 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 19 Mar 2024 22:34:11 -0400 Subject: [PATCH 07/31] Refactored Achievement Mananger to load games through rc_client HashGame has become LoadGame, similar structure with the file loaders but using the client instead. LoadGameCallback has been created to handle the results. The old LoadGameSync has been deleted as have several hash and load methods that it called. --- Source/Core/Core/AchievementManager.cpp | 486 ++---------------- Source/Core/Core/AchievementManager.h | 15 +- Source/Core/Core/Boot/Boot.cpp | 3 +- Source/Core/Core/HW/DVD/DVDInterface.cpp | 3 +- .../AchievementSettingsWidget.cpp | 5 - 5 files changed, 52 insertions(+), 460 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 231cbd5502..fc5008c1fc 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -92,18 +92,21 @@ bool AchievementManager::HasAPIToken() const return !Config::Get(Config::RA_API_TOKEN).empty(); } -void AchievementManager::HashGame(const std::string& file_path, const ResponseCallback& callback) +void AchievementManager::LoadGame(const std::string& file_path, const DiscIO::Volume* volume) { if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) { - callback(ResponseType::NOT_ENABLED); return; } - if (!m_is_runtime_initialized) + if (file_path.empty() && volume == nullptr) + { + WARN_LOG_FMT(ACHIEVEMENTS, "Called Load Game without a game."); + return; + } + if (!m_client) { ERROR_LOG_FMT(ACHIEVEMENTS, - "Attempted to load game achievements without Achievement Manager initialized."); - callback(ResponseType::MANAGER_NOT_INITIALIZED); + "Attempted to load game achievements without achievement client initialized."); return; } if (m_disabled) @@ -113,295 +116,32 @@ void AchievementManager::HashGame(const std::string& file_path, const ResponseCa OSD::Duration::VERY_LONG, OSD::Color::RED); return; } - m_system = &Core::System::GetInstance(); - m_queue.EmplaceItem([this, callback, file_path] { - Hash new_hash; - { - std::lock_guard lg{m_filereader_lock}; - rc_hash_filereader volume_reader{ - .open = &AchievementManager::FilereaderOpenByFilepath, - .seek = &AchievementManager::FilereaderSeek, - .tell = &AchievementManager::FilereaderTell, - .read = &AchievementManager::FilereaderRead, - .close = &AchievementManager::FilereaderClose, - }; - rc_hash_init_custom_filereader(&volume_reader); - if (!rc_hash_generate_from_file(new_hash.data(), RC_CONSOLE_GAMECUBE, file_path.c_str())) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to generate achievement hash from game file {}.", - file_path); - callback(ResponseType::MALFORMED_OBJECT); - } - } - { - std::lock_guard lg{m_lock}; - if (m_disabled) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Achievements disabled while hash was resolving."); - callback(ResponseType::EXPIRED_CONTEXT); - return; - } - m_game_hash = std::move(new_hash); - } - LoadGameSync(callback); - }); -} - -void AchievementManager::HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback) -{ - if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) - { - callback(ResponseType::NOT_ENABLED); - return; - } - if (!m_is_runtime_initialized) - { - ERROR_LOG_FMT(ACHIEVEMENTS, - "Attempted to load game achievements without Achievement Manager initialized."); - callback(ResponseType::MANAGER_NOT_INITIALIZED); - return; - } - if (volume == nullptr) - { - INFO_LOG_FMT(ACHIEVEMENTS, "New volume is empty."); - return; - } - if (m_disabled) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager is disabled until core is rebooted."); - OSD::AddMessage("Achievements are disabled until core is rebooted.", OSD::Duration::VERY_LONG, - OSD::Color::RED); - return; - } - // Need to SetDisabled outside a lock because it uses m_lock internally. - bool disable = true; + if (volume) { std::lock_guard lg{m_lock}; if (!m_loading_volume) { m_loading_volume = DiscIO::CreateVolume(volume->GetBlobReader().CopyReader()); - disable = false; } } - if (disable) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Disabling Achievement Manager due to hash spam."); - SetDisabled(true); - callback(ResponseType::EXPIRED_CONTEXT); - return; - } - m_system = &Core::System::GetInstance(); - m_queue.EmplaceItem([this, callback] { - Hash new_hash; - { - std::lock_guard lg{m_filereader_lock}; - rc_hash_filereader volume_reader{ - .open = &AchievementManager::FilereaderOpenByVolume, - .seek = &AchievementManager::FilereaderSeek, - .tell = &AchievementManager::FilereaderTell, - .read = &AchievementManager::FilereaderRead, - .close = &AchievementManager::FilereaderClose, - }; - rc_hash_init_custom_filereader(&volume_reader); - if (!rc_hash_generate_from_file(new_hash.data(), RC_CONSOLE_GAMECUBE, "")) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to generate achievement hash from volume."); - callback(ResponseType::MALFORMED_OBJECT); - return; - } - } - { - std::lock_guard lg{m_lock}; - if (m_disabled) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Achievements disabled while hash was resolving."); - callback(ResponseType::EXPIRED_CONTEXT); - return; - } - m_game_hash = std::move(new_hash); - m_loading_volume.reset(); - } - LoadGameSync(callback); - }); -} - -void AchievementManager::LoadGameSync(const ResponseCallback& callback) -{ - if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) - { - callback(ResponseType::NOT_ENABLED); - return; - } - u32 new_game_id = 0; - Hash current_hash; - { - std::lock_guard lg{m_lock}; - current_hash = m_game_hash; - } - const auto resolve_hash_response = ResolveHash(current_hash, &new_game_id); - if (resolve_hash_response != ResponseType::SUCCESS || new_game_id == 0) - { - INFO_LOG_FMT(ACHIEVEMENTS, "No RetroAchievements data found for this game."); - OSD::AddMessage("No RetroAchievements data found for this game.", OSD::Duration::VERY_LONG, - OSD::Color::RED); - SetDisabled(true); - callback(resolve_hash_response); - return; - } - u32 old_game_id; - { - std::lock_guard lg{m_lock}; - old_game_id = m_game_id; - } - if (new_game_id == old_game_id) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Alternate hash resolved for current game {}.", old_game_id); - callback(ResponseType::SUCCESS); - return; - } - else if (old_game_id != 0) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Swapping game {} for game {}; achievements disabled.", old_game_id, - new_game_id); - OSD::AddMessage("Achievements are now disabled. Please close emulation to re-enable.", - OSD::Duration::VERY_LONG, OSD::Color::RED); - SetDisabled(true); - callback(ResponseType::EXPIRED_CONTEXT); - return; - } - { - std::lock_guard lg{m_lock}; - m_game_id = new_game_id; - } - - const auto start_session_response = StartRASession(); - if (start_session_response != ResponseType::SUCCESS) - { - WARN_LOG_FMT(ACHIEVEMENTS, "Failed to connect to RetroAchievements server."); - OSD::AddMessage("Failed to connect to RetroAchievements server.", OSD::Duration::VERY_LONG, - OSD::Color::RED); - callback(start_session_response); - return; - } - - const auto fetch_game_data_response = FetchGameData(); - if (fetch_game_data_response != ResponseType::SUCCESS) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to retrieve data from RetroAchievements server."); - OSD::AddMessage("Unable to retrieve data from RetroAchievements server.", - OSD::Duration::VERY_LONG, OSD::Color::RED); - return; - } - INFO_LOG_FMT(ACHIEVEMENTS, "Loading achievements for {}.", m_game_data.title); - - // 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 - // unlock map but then forces them to wait until it's initialized before making modifications to - // it. - { - std::lock_guard lg{m_lock}; - m_is_game_loaded = true; - m_framecount = 0; - LoadUnlockData([](ResponseType r_type) {}); - ActivateDeactivateAchievements(); - 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); - - m_update_callback(); - callback(fetch_game_data_response); + std::lock_guard lg{m_filereader_lock}; + rc_hash_filereader volume_reader{ + .open = (volume) ? &AchievementManager::FilereaderOpenByVolume : + &AchievementManager::FilereaderOpenByFilepath, + .seek = &AchievementManager::FilereaderSeek, + .tell = &AchievementManager::FilereaderTell, + .read = &AchievementManager::FilereaderRead, + .close = &AchievementManager::FilereaderClose, + }; + rc_hash_init_custom_filereader(&volume_reader); + rc_client_begin_identify_and_load_game(m_client, RC_CONSOLE_GAMECUBE, file_path.c_str(), NULL, 0, + LoadGameCallback, NULL); } bool AchievementManager::IsGameLoaded() const { - return m_is_game_loaded; -} - -void AchievementManager::LoadUnlockData(const ResponseCallback& callback) -{ - if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) - { - callback(ResponseType::NOT_ENABLED); - return; - } - m_queue.EmplaceItem([this, callback] { - const auto hardcore_unlock_response = FetchUnlockData(true); - if (hardcore_unlock_response != ResponseType::SUCCESS) - { - ERROR_LOG_FMT(ACHIEVEMENTS, - "Failed to fetch hardcore unlock data; skipping softcore unlock."); - callback(hardcore_unlock_response); - return; - } - - callback(FetchUnlockData(false)); - m_update_callback(); - }); -} - -void AchievementManager::ActivateDeactivateAchievements() -{ - std::lock_guard lg{m_lock}; - if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) - return; - bool enabled = Config::Get(Config::RA_ACHIEVEMENTS_ENABLED); - bool unofficial = Config::Get(Config::RA_UNOFFICIAL_ENABLED); - 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, - .points = m_game_data.achievements[ix].points, - .category = m_game_data.achievements[ix].category}}); - ActivateDeactivateAchievement(iter.first->first, enabled, unofficial, encore); - } - INFO_LOG_FMT(ACHIEVEMENTS, "Achievements (de)activated."); -} - -void AchievementManager::ActivateDeactivateLeaderboards() -{ - std::lock_guard lg{m_lock}; - if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) - return; - bool leaderboards_enabled = - Config::Get(Config::RA_LEADERBOARDS_ENABLED) && Config::Get(Config::RA_HARDCORE_ENABLED); - 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) - { - rc_runtime_activate_lboard(&m_runtime, leaderboard_id, leaderboard.definition, nullptr, 0); - m_queue.EmplaceItem([this, leaderboard_id] { - FetchBoardInfo(leaderboard_id); - m_update_callback(); - }); - } - else - { - rc_runtime_deactivate_lboard(&m_runtime, m_game_data.leaderboards[ix].id); - } - } - INFO_LOG_FMT(ACHIEVEMENTS, "Leaderboards (de)activated."); -} - -void AchievementManager::ActivateDeactivateRichPresence() -{ - std::lock_guard lg{m_lock}; - if (!Config::Get(Config::RA_ENABLED) || !HasAPIToken()) - return; - rc_runtime_activate_richpresence( - &m_runtime, - (m_is_game_loaded && Config::Get(Config::RA_RICH_PRESENCE_ENABLED)) ? - m_game_data.rich_presence_script : - "", - nullptr, 0); - INFO_LOG_FMT(ACHIEVEMENTS, "Rich presence (de)activated."); + auto* game_info = rc_client_get_game_info(m_client); + return game_info && game_info->id != 0; } void AchievementManager::FetchBadges() @@ -910,14 +650,9 @@ void AchievementManager::CloseGame() { { std::lock_guard lg{m_lock}; - if (m_is_game_loaded) + if (rc_client_get_game_info(m_client)) { - m_is_game_loaded = false; m_active_challenges.clear(); - ActivateDeactivateAchievements(); - ActivateDeactivateLeaderboards(); - ActivateDeactivateRichPresence(); - m_game_id = 0; m_game_badge.name.clear(); m_unlock_map.clear(); m_leaderboard_map.clear(); @@ -925,6 +660,7 @@ void AchievementManager::CloseGame() m_game_data = {}; m_queue.Cancel(); m_image_queue.Cancel(); + rc_client_unload_game(m_client); m_system = nullptr; } } @@ -1070,157 +806,6 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc Config::SetBaseOrCurrent(Config::RA_API_TOKEN, user->token); } -AchievementManager::ResponseType AchievementManager::ResolveHash(const Hash& game_hash, - u32* game_id) -{ - rc_api_resolve_hash_response_t hash_data{}; - std::string username, api_token; - { - std::lock_guard lg{m_lock}; - username = Config::Get(Config::RA_USERNAME); - api_token = Config::Get(Config::RA_API_TOKEN); - } - rc_api_resolve_hash_request_t resolve_hash_request = { - .username = username.c_str(), .api_token = api_token.c_str(), .game_hash = game_hash.data()}; - ResponseType r_type = Request( - resolve_hash_request, &hash_data, rc_api_init_resolve_hash_request, - rc_api_process_resolve_hash_response); - if (r_type == ResponseType::SUCCESS) - { - *game_id = hash_data.game_id; - INFO_LOG_FMT(ACHIEVEMENTS, "Hashed game ID {} for RetroAchievements.", *game_id); - } - else - { - INFO_LOG_FMT(ACHIEVEMENTS, "Hash {} not recognized by RetroAchievements.", game_hash.data()); - } - rc_api_destroy_resolve_hash_response(&hash_data); - return r_type; -} - -AchievementManager::ResponseType AchievementManager::StartRASession() -{ - rc_api_start_session_request_t start_session_request; - rc_api_start_session_response_t session_data{}; - std::string username, api_token; - { - std::lock_guard lg{m_lock}; - username = Config::Get(Config::RA_USERNAME); - api_token = Config::Get(Config::RA_API_TOKEN); - start_session_request = { - .username = username.c_str(), .api_token = api_token.c_str(), .game_id = m_game_id}; - } - ResponseType r_type = Request( - start_session_request, &session_data, rc_api_init_start_session_request, - rc_api_process_start_session_response); - rc_api_destroy_start_session_response(&session_data); - return r_type; -} - -AchievementManager::ResponseType AchievementManager::FetchGameData() -{ - rc_api_fetch_game_data_request_t fetch_data_request; - rc_api_request_t api_request; - Common::HttpRequest http_request; - std::string username, api_token; - u32 game_id; - { - std::lock_guard lg{m_lock}; - username = Config::Get(Config::RA_USERNAME); - api_token = Config::Get(Config::RA_API_TOKEN); - game_id = m_game_id; - } - fetch_data_request = { - .username = username.c_str(), .api_token = api_token.c_str(), .game_id = game_id}; - if (rc_api_init_fetch_game_data_request(&api_request, &fetch_data_request) != RC_OK || - !api_request.post_data) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid API request for game data."); - return ResponseType::INVALID_REQUEST; - } - auto http_response = http_request.Post(api_request.url, api_request.post_data); - rc_api_destroy_request(&api_request); - if (!http_response.has_value() || http_response->size() == 0) - { - WARN_LOG_FMT(ACHIEVEMENTS, - "RetroAchievements connection failed while fetching game data for ID {}. \nURL: " - "{} \npost_data: {}", - game_id, api_request.url, - api_request.post_data == nullptr ? "NULL" : api_request.post_data); - return ResponseType::CONNECTION_FAILED; - } - std::lock_guard lg{m_lock}; - const std::string response_str(http_response->begin(), http_response->end()); - if (rc_api_process_fetch_game_data_response(&m_game_data, response_str.c_str()) != RC_OK) - { - ERROR_LOG_FMT(ACHIEVEMENTS, - "Failed to process HTTP response fetching game data for ID {}. \nURL: {} " - "\npost_data: {} \nresponse: {}", - game_id, api_request.url, - api_request.post_data == nullptr ? "NULL" : api_request.post_data, response_str); - rc_api_destroy_fetch_game_data_response(&m_game_data); - m_game_data = {}; - return ResponseType::MALFORMED_OBJECT; - } - if (!m_game_data.response.succeeded) - { - WARN_LOG_FMT( - ACHIEVEMENTS, - "Invalid RetroAchievements credentials fetching game data for ID {}; logging out user {}", - game_id, username); - // Logout technically does this via a CloseGame call, but doing this now prevents the activate - // methods from thinking they have something to do. - rc_api_destroy_fetch_game_data_response(&m_game_data); - m_game_data = {}; - Logout(); - return ResponseType::INVALID_CREDENTIALS; - } - if (game_id != m_game_id) - { - INFO_LOG_FMT(ACHIEVEMENTS, - "Attempted to retrieve game data for ID {}; running game is now ID {}", game_id, - m_game_id); - rc_api_destroy_fetch_game_data_response(&m_game_data); - m_game_data = {}; - return ResponseType::EXPIRED_CONTEXT; - } - INFO_LOG_FMT(ACHIEVEMENTS, "Retrieved game data for ID {}.", game_id); - return ResponseType::SUCCESS; -} - -AchievementManager::ResponseType AchievementManager::FetchUnlockData(bool hardcore) -{ - rc_api_fetch_user_unlocks_response_t unlock_data{}; - std::string username = Config::Get(Config::RA_USERNAME); - std::string api_token = Config::Get(Config::RA_API_TOKEN); - rc_api_fetch_user_unlocks_request_t fetch_unlocks_request = {.username = username.c_str(), - .api_token = api_token.c_str(), - .game_id = m_game_id, - .hardcore = hardcore}; - ResponseType r_type = - Request( - fetch_unlocks_request, &unlock_data, rc_api_init_fetch_user_unlocks_request, - rc_api_process_fetch_user_unlocks_response); - if (r_type == ResponseType::SUCCESS) - { - std::lock_guard lg{m_lock}; - bool enabled = Config::Get(Config::RA_ACHIEVEMENTS_ENABLED); - bool unofficial = Config::Get(Config::RA_UNOFFICIAL_ENABLED); - bool encore = Config::Get(Config::RA_ENCORE_ENABLED); - for (AchievementId ix = 0; ix < unlock_data.num_achievement_ids; ix++) - { - auto it = m_unlock_map.find(unlock_data.achievement_ids[ix]); - if (it == m_unlock_map.end()) - continue; - it->second.remote_unlock_status = - hardcore ? UnlockStatus::UnlockType::HARDCORE : UnlockStatus::UnlockType::SOFTCORE; - ActivateDeactivateAchievement(unlock_data.achievement_ids[ix], enabled, unofficial, encore); - } - } - rc_api_destroy_fetch_user_unlocks_response(&unlock_data); - return r_type; -} - AchievementManager::ResponseType AchievementManager::FetchBoardInfo(AchievementId leaderboard_id) { std::string username = Config::Get(Config::RA_USERNAME); @@ -1440,6 +1025,27 @@ AchievementManager::PingRichPresence(const RichPresence& rich_presence) return r_type; } +void AchievementManager::LoadGameCallback(int result, const char* error_message, + rc_client_t* client, void* userdata) +{ + if (result != RC_OK) + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to load data for current game."); + return; + } + + auto* game = rc_client_get_game_info(client); + if (!game) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to retrieve game information from client."); + return; + } + INFO_LOG_FMT(ACHIEVEMENTS, "Loaded data for game ID {}.", game->id); + + AchievementManager::GetInstance().FetchBadges(); + AchievementManager::GetInstance().m_system = &Core::System::GetInstance(); +} + void AchievementManager::DisplayWelcomeMessage() { std::lock_guard lg{m_lock}; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index b385e9ed30..7412913fa6 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -119,14 +119,9 @@ public: void SetUpdateCallback(UpdateCallback callback); void Login(const std::string& password); bool HasAPIToken() const; - void HashGame(const std::string& file_path, const ResponseCallback& callback); - void HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback); + void LoadGame(const std::string& file_path, const DiscIO::Volume* volume); bool IsGameLoaded() const; - void LoadUnlockData(const ResponseCallback& callback); - void ActivateDeactivateAchievements(); - void ActivateDeactivateLeaderboards(); - void ActivateDeactivateRichPresence(); void FetchBadges(); void DoFrame(); @@ -173,11 +168,7 @@ private: static void LoginCallback(int result, const char* error_message, rc_client_t* client, void* userdata); - ResponseType ResolveHash(const Hash& game_hash, u32* game_id); - void LoadGameSync(const ResponseCallback& callback); - ResponseType StartRASession(); - ResponseType FetchGameData(); - ResponseType FetchUnlockData(bool hardcore); + ResponseType FetchBoardInfo(AchievementId leaderboard_id); std::unique_ptr& GetLoadingVolume() { return m_loading_volume; }; @@ -189,6 +180,8 @@ private: ResponseType SubmitLeaderboard(AchievementId leaderboard_id, int value); ResponseType PingRichPresence(const RichPresence& rich_presence); + static void LoadGameCallback(int result, const char* error_message, rc_client_t* client, + void* userdata); void DisplayWelcomeMessage(); void HandleAchievementTriggeredEvent(const rc_runtime_event_t* runtime_event); diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 538308ef4f..d57f4e74bb 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -576,8 +576,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard, } #ifdef USE_RETRO_ACHIEVEMENTS - AchievementManager::GetInstance().HashGame(executable.path, - [](AchievementManager::ResponseType r_type) {}); + AchievementManager::GetInstance().LoadGame(executable.path, nullptr); #endif // USE_RETRO_ACHIEVEMENTS if (!executable.reader->LoadIntoMemory(system)) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index adf16777ab..fefa5f2d2d 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -399,8 +399,7 @@ void DVDInterface::SetDisc(std::unique_ptr disc, } #ifdef USE_RETRO_ACHIEVEMENTS - AchievementManager::GetInstance().HashGame(disc.get(), - [](AchievementManager::ResponseType r_type) {}); + AchievementManager::GetInstance().LoadGame("", disc.get()); #endif // USE_RETRO_ACHIEVEMENTS // Assume that inserting a disc requires having an empty disc before diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index 01e1470175..040b26ca31 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -268,19 +268,16 @@ void AchievementSettingsWidget::Logout() void AchievementSettingsWidget::ToggleAchievements() { SaveSettings(); - AchievementManager::GetInstance().ActivateDeactivateAchievements(); } void AchievementSettingsWidget::ToggleLeaderboards() { SaveSettings(); - AchievementManager::GetInstance().ActivateDeactivateLeaderboards(); } void AchievementSettingsWidget::ToggleRichPresence() { SaveSettings(); - AchievementManager::GetInstance().ActivateDeactivateRichPresence(); } void AchievementSettingsWidget::ToggleHardcore() @@ -311,13 +308,11 @@ void AchievementSettingsWidget::ToggleBadges() void AchievementSettingsWidget::ToggleUnofficial() { SaveSettings(); - AchievementManager::GetInstance().ActivateDeactivateAchievements(); } void AchievementSettingsWidget::ToggleEncore() { SaveSettings(); - AchievementManager::GetInstance().ActivateDeactivateAchievements(); } #endif // USE_RETRO_ACHIEVEMENTS From 7b835a20ca988515770d4952a431ae4ef9d96cd1 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Wed, 27 Mar 2024 23:22:10 -0400 Subject: [PATCH 08/31] Updated GetGameDisplayName to use rc_client --- Source/Core/Core/AchievementManager.cpp | 6 +++--- Source/Core/Core/AchievementManager.h | 2 +- .../Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index fc5008c1fc..f2728d5745 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -398,7 +398,7 @@ void AchievementManager::FetchBadges() void AchievementManager::DoFrame() { - if (!m_is_game_loaded || !Core::IsCPUThread()) + if (!IsGameLoaded() || !Core::IsCPUThread()) return; if (m_framecount == 0x200) { @@ -532,9 +532,9 @@ const AchievementManager::BadgeStatus& AchievementManager::GetPlayerBadge() cons return m_player_badge; } -std::string AchievementManager::GetGameDisplayName() const +std::string_view AchievementManager::GetGameDisplayName() const { - return IsGameLoaded() ? m_game_data.title : ""; + return IsGameLoaded() ? std::string_view(rc_client_get_game_info(m_client)->title) : ""; } AchievementManager::PointSpread AchievementManager::TallyScore() const diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 7412913fa6..3139272ee6 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -133,7 +133,7 @@ public: std::string_view GetPlayerDisplayName() const; u32 GetPlayerScore() const; const BadgeStatus& GetPlayerBadge() const; - std::string GetGameDisplayName() const; + std::string_view GetGameDisplayName() const; PointSpread TallyScore() const; rc_api_fetch_game_data_response_t* GetGameData(); const BadgeStatus& GetGameBadge() const; diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index 4d79dec531..bf151a77a1 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -78,7 +78,7 @@ void AchievementHeaderWidget::UpdateData() AchievementManager::PointSpread point_spread = instance.TallyScore(); QString user_name = QtUtils::FromStdString(instance.GetPlayerDisplayName()); - QString game_name = QString::fromStdString(instance.GetGameDisplayName()); + QString game_name = QtUtils::FromStdString(instance.GetGameDisplayName()); AchievementManager::BadgeStatus player_badge = instance.GetPlayerBadge(); AchievementManager::BadgeStatus game_badge = instance.GetGameBadge(); From c5bb1c4e688a9196f779465af27d5e2831dcafcb Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 30 Mar 2024 10:51:18 -0400 Subject: [PATCH 09/31] Retooled achievement badge fetch process This change was primarily made to refactor the badge fetching to use the client instead of the runtime, but in the process I also refactored the code to cut down on complexity and duplication. Now the FetchBadge method is passed a function that generates the badge name; this is used to ensure that once the badge is loaded that it is still the desired badge to avoid race conditions. --- Source/Core/Core/AchievementManager.cpp | 368 +++++------------- Source/Core/Core/AchievementManager.h | 8 +- .../AchievementSettingsWidget.cpp | 3 +- 3 files changed, 110 insertions(+), 269 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index f2728d5745..4734ca474e 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -144,256 +144,61 @@ bool AchievementManager::IsGameLoaded() const return game_info && game_info->id != 0; } -void AchievementManager::FetchBadges() +void AchievementManager::FetchPlayerBadge() { - if (!m_is_runtime_initialized || !HasAPIToken() || !Config::Get(Config::RA_BADGES_ENABLED)) - { - m_update_callback(); + FetchBadge(&m_player_badge, RC_IMAGE_TYPE_USER, [](const AchievementManager& manager) { + auto* user_info = rc_client_get_user_info(manager.m_client); + if (!user_info) + return std::string(""); + return std::string(user_info->display_name); + }); +} + +void AchievementManager::FetchGameBadges() +{ + FetchBadge(&m_game_badge, RC_IMAGE_TYPE_GAME, [](const AchievementManager& manager) { + auto* game_info = rc_client_get_game_info(manager.m_client); + if (!game_info) + return std::string(""); + return std::string(game_info->badge_name); + }); + + if (!rc_client_has_achievements(m_client)) return; - } - m_image_queue.Cancel(); - auto* user = rc_client_get_user_info(m_client); - if (m_player_badge.name.compare(user->display_name) != 0) - { - m_image_queue.EmplaceItem([this, user] { - std::string name_to_fetch; - { - std::lock_guard lg{m_lock}; - if (m_player_badge.name.compare(user->display_name) == 0) - return; - name_to_fetch.assign(user->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.compare(user->display_name) != 0) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {} for player id {}.", - name_to_fetch, user->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); - } - - m_update_callback(); - }); - } - - if (!IsGameLoaded()) - { - m_update_callback(); - return; - } - - bool badgematch = false; + rc_client_achievement_list_t* achievement_list; { std::lock_guard lg{m_lock}; - badgematch = m_game_badge.name == m_game_data.image_name; + achievement_list = rc_client_create_achievement_list( + m_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS); } - if (!badgematch) + for (u32 bx = 0; bx < achievement_list->num_buckets; bx++) { - m_image_queue.EmplaceItem([this] { - std::string name_to_fetch; - { - std::lock_guard lg{m_lock}; - if (m_game_badge.name == m_game_data.image_name) - return; - name_to_fetch = 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 != m_game_data.image_name) - { - 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); - } - - 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; - - const auto& initial_achievement = m_game_data.achievements[index]; - const std::string badge_name_to_fetch(initial_achievement.badge_name); - const UnlockStatus& unlock_status = m_unlock_map[initial_achievement.id]; - - if (unlock_status.unlocked_badge.name != badge_name_to_fetch) + auto& bucket = achievement_list->buckets[bx]; + for (u32 achievement = 0; achievement < bucket.num_achievements; achievement++) { - m_image_queue.EmplaceItem([this, index] { - std::string current_name, name_to_fetch; - { - std::lock_guard lock{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; - } - const auto& achievement = m_game_data.achievements[index]; - const 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 = 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 lock{m_lock}; - if (m_game_data.num_achievements <= index) - { - INFO_LOG_FMT(ACHIEVEMENTS, - "Fetched unlocked badge for index {} after achievement list cleared.", - index); - return; - } - const auto& achievement = m_game_data.achievements[index]; - const 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 != achievement.badge_name) - { - 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); - } + u32 achievement_id = bucket.achievements[achievement]->id; - m_update_callback(); - }); - } - if (unlock_status.locked_badge.name != badge_name_to_fetch) - { - m_image_queue.EmplaceItem([this, index] { - std::string current_name, name_to_fetch; - { - std::lock_guard lock{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; - } - const auto& achievement = m_game_data.achievements[index]; - const 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 = 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 lock{m_lock}; - if (m_game_data.num_achievements <= index) - { - INFO_LOG_FMT(ACHIEVEMENTS, - "Fetched locked badge for index {} after achievement list cleared.", - index); - return; - } - const auto& achievement = m_game_data.achievements[index]; - const 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 != achievement.badge_name) - { - 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); - } - - m_update_callback(); - }); + FetchBadge( + &m_unlocked_badges[achievement_id], RC_IMAGE_TYPE_ACHIEVEMENT, + [achievement_id](const AchievementManager& manager) { + if (!rc_client_get_achievement_info(manager.m_client, achievement_id)) + return std::string(""); + return std::string( + rc_client_get_achievement_info(manager.m_client, achievement_id)->badge_name); + }); + FetchBadge( + &m_locked_badges[achievement_id], RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, + [achievement_id](const AchievementManager& manager) { + if (!rc_client_get_achievement_info(manager.m_client, achievement_id)) + return std::string(""); + return std::string( + rc_client_get_achievement_info(manager.m_client, achievement_id)->badge_name); + }); } } - - m_update_callback(); + rc_client_destroy_achievement_list(achievement_list); } void AchievementManager::DoFrame() @@ -654,6 +459,8 @@ void AchievementManager::CloseGame() { m_active_challenges.clear(); m_game_badge.name.clear(); + m_unlocked_badges.clear(); + m_locked_badges.clear(); m_unlock_map.clear(); m_leaderboard_map.clear(); rc_api_destroy_fetch_game_data_response(&m_game_data); @@ -804,6 +611,7 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc user->username); std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; Config::SetBaseOrCurrent(Config::RA_API_TOKEN, user->token); + AchievementManager::GetInstance().FetchPlayerBadge(); } AchievementManager::ResponseType AchievementManager::FetchBoardInfo(AchievementId leaderboard_id) @@ -1042,7 +850,7 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message, } INFO_LOG_FMT(ACHIEVEMENTS, "Loaded data for game ID {}.", game->id); - AchievementManager::GetInstance().FetchBadges(); + AchievementManager::GetInstance().FetchGameBadges(); AchievementManager::GetInstance().m_system = &Core::System::GetInstance(); } @@ -1292,32 +1100,6 @@ AchievementManager::ResponseType AchievementManager::Request( } } -AchievementManager::ResponseType -AchievementManager::RequestImage(rc_api_fetch_image_request_t rc_request, Badge* rc_response) -{ - rc_api_request_t api_request; - Common::HttpRequest http_request; - if (rc_api_init_fetch_image_request(&api_request, &rc_request) != RC_OK) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid request for image."); - return ResponseType::INVALID_REQUEST; - } - auto http_response = http_request.Get(api_request.url); - if (http_response.has_value() && http_response->size() > 0) - { - rc_api_destroy_request(&api_request); - *rc_response = std::move(*http_response); - return ResponseType::SUCCESS; - } - else - { - WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed on image request.\n URL: {}", - api_request.url); - rc_api_destroy_request(&api_request); - return ResponseType::CONNECTION_FAILED; - } -} - static std::unique_ptr DecodeBadgeToOSDIcon(const AchievementManager::Badge& badge) { if (badge.empty()) @@ -1393,4 +1175,58 @@ u32 AchievementManager::MemoryPeekerV2(u32 address, u8* buffer, u32 num_bytes, r return num_bytes; } +void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32 badge_type, + const AchievementManager::BadgeNameFunction function) +{ + if (!m_client || !HasAPIToken() || !Config::Get(Config::RA_BADGES_ENABLED)) + { + m_update_callback(); + return; + } + + m_image_queue.EmplaceItem([this, badge, badge_type, function = std::move(function)] { + std::string name_to_fetch; + { + std::lock_guard lg{m_lock}; + name_to_fetch = function(*this); + if (name_to_fetch.empty()) + return; + } + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = badge_type}; + Badge fetched_badge; + rc_api_request_t api_request; + Common::HttpRequest http_request; + if (rc_api_init_fetch_image_request(&api_request, &icon_request) != RC_OK) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid request for image {}.", name_to_fetch); + return; + } + auto http_response = http_request.Get(api_request.url); + if (http_response.has_value() && http_response->size() <= 0) + { + WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed on image request.\n URL: {}", + api_request.url); + rc_api_destroy_request(&api_request); + m_update_callback(); + return; + } + + rc_api_destroy_request(&api_request); + fetched_badge = std::move(*http_response); + + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded badge id {}.", name_to_fetch); + std::lock_guard lg{m_lock}; + if (function(*this).empty() || name_to_fetch != function(*this)) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {}.", name_to_fetch); + return; + } + badge->badge = std::move(fetched_badge); + badge->name = std::move(name_to_fetch); + + m_update_callback(); + }); +} + #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 3139272ee6..3febabf606 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -51,6 +51,7 @@ public: }; using ResponseCallback = std::function; using UpdateCallback = std::function; + using BadgeNameFunction = std::function; struct PointSpread { @@ -122,7 +123,8 @@ public: void LoadGame(const std::string& file_path, const DiscIO::Volume* volume); bool IsGameLoaded() const; - void FetchBadges(); + void FetchPlayerBadge(); + void FetchGameBadges(); void DoFrame(); u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud); @@ -196,11 +198,11 @@ private: ResponseType Request(RcRequest rc_request, RcResponse* rc_response, const std::function& init_request, const std::function& process_response); - ResponseType RequestImage(rc_api_fetch_image_request_t rc_request, Badge* rc_response); static void RequestV2(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); static u32 MemoryPeekerV2(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); + void FetchBadge(BadgeStatus* badge, u32 badge_type, const BadgeNameFunction function); rc_runtime_t m_runtime{}; rc_client_t* m_client{}; @@ -216,6 +218,8 @@ private: bool m_is_game_loaded = false; u32 m_framecount = 0; BadgeStatus m_game_badge; + std::unordered_map m_unlocked_badges; + std::unordered_map m_locked_badges; RichPresence m_rich_presence; time_t m_last_ping_time = 0; diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index 040b26ca31..ecaee3ff96 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -302,7 +302,8 @@ void AchievementSettingsWidget::ToggleProgress() void AchievementSettingsWidget::ToggleBadges() { SaveSettings(); - AchievementManager::GetInstance().FetchBadges(); + AchievementManager::GetInstance().FetchPlayerBadge(); + AchievementManager::GetInstance().FetchGameBadges(); } void AchievementSettingsWidget::ToggleUnofficial() From 7497df99dfd3c66c05708236f349a961ac055cae Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 28 Mar 2024 12:56:01 -0400 Subject: [PATCH 10/31] Refactored Welcome Message to use rc_client Restructured the welcome message to use the information in rc_client, particularly the summary. Redesigned the message in the process to look cleaner. --- Source/Core/Core/AchievementManager.cpp | 48 +++++++++++++++---------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 4734ca474e..a3c3543685 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -857,28 +857,38 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message, void AchievementManager::DisplayWelcomeMessage() { std::lock_guard lg{m_lock}; - PointSpread spread = TallyScore(); - if (Config::Get(Config::RA_HARDCORE_ENABLED)) + const u32 color = + rc_client_get_hardcore_enabled(m_client) ? OSD::Color::YELLOW : OSD::Color::CYAN; + if (Config::Get(Config::RA_BADGES_ENABLED)) + { + OSD::AddMessage("", OSD::Duration::VERY_LONG, OSD::Color::GREEN, + DecodeBadgeToOSDIcon(m_game_badge.badge)); + } + auto info = rc_client_get_game_info(m_client); + if (!info) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Attempting to welcome player to game not running."); + return; + } + OSD::AddMessage(info->title, OSD::Duration::VERY_LONG, OSD::Color::GREEN); + rc_client_user_game_summary_t summary; + rc_client_get_user_game_summary(m_client, &summary); + OSD::AddMessage(fmt::format("You have {}/{} achievements worth {}/{} points", + summary.num_unlocked_achievements, summary.num_core_achievements, + summary.points_unlocked, summary.points_core), + OSD::Duration::VERY_LONG, color); + if (summary.num_unsupported_achievements > 0) { 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, - (Config::Get(Config::RA_BADGES_ENABLED)) ? DecodeBadgeToOSDIcon(m_game_badge.badge) : - nullptr); - 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, - (Config::Get(Config::RA_BADGES_ENABLED)) ? - DecodeBadgeToOSDIcon(m_game_badge.badge) : - nullptr); - OSD::AddMessage("Hardcore mode is OFF", OSD::Duration::VERY_LONG, OSD::Color::CYAN); + fmt::format("{} achievements unsupported", summary.num_unsupported_achievements), + OSD::Duration::VERY_LONG, OSD::Color::RED); } + OSD::AddMessage( + fmt::format("Hardcore mode is {}", rc_client_get_hardcore_enabled(m_client) ? "ON" : "OFF"), + OSD::Duration::VERY_LONG, color); + OSD::AddMessage(fmt::format("Leaderboard submissions are {}", + Config::Get(Config::RA_LEADERBOARDS_ENABLED) ? "ON" : "OFF"), + OSD::Duration::VERY_LONG, color); } void AchievementManager::HandleAchievementTriggeredEvent(const rc_runtime_event_t* runtime_event) From c88d4cf0403e36caf17008ea3c1e11b7d8d6732a Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 30 Mar 2024 17:30:58 -0400 Subject: [PATCH 11/31] Created EventHandlerV2 and added client to DoFrame As MemoryPeeker V1 is no longer in use, it is deleted and MemoryPeekerV2 is renamed in its place. --- Source/Core/Core/AchievementManager.cpp | 54 +++++-------------------- Source/Core/Core/AchievementManager.h | 4 +- 2 files changed, 11 insertions(+), 47 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index a3c3543685..6a34350cb2 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -36,10 +36,11 @@ void AchievementManager::Init() { if (!m_client && Config::Get(Config::RA_ENABLED)) { - m_client = rc_client_create(MemoryPeekerV2, RequestV2); + m_client = rc_client_create(MemoryPeeker, RequestV2); std::string host_url = Config::Get(Config::RA_HOST_URL); if (!host_url.empty()) rc_client_set_host(m_client, host_url.c_str()); + rc_client_set_event_handler(m_client, EventHandlerV2); rc_client_enable_logging(m_client, RC_CLIENT_LOG_LEVEL_VERBOSE, [](const char* message, const rc_client_t* client) { INFO_LOG_FMT(ACHIEVEMENTS, "{}", message); @@ -215,15 +216,7 @@ void AchievementManager::DoFrame() } { std::lock_guard lg{m_lock}; - rc_runtime_do_frame( - &m_runtime, - [](const rc_runtime_event_t* runtime_event) { - GetInstance().AchievementEventHandler(runtime_event); - }, - [](unsigned address, unsigned num_bytes, void* ud) { - return static_cast(ud)->MemoryPeeker(address, num_bytes, ud); - }, - this, nullptr); + rc_client_do_frame(m_client); } if (!m_system) return; @@ -237,36 +230,6 @@ void AchievementManager::DoFrame() } } -u32 AchievementManager::MemoryPeeker(u32 address, u32 num_bytes, void* ud) -{ - if (!m_system) - return 0u; - Core::CPUThreadGuard threadguard(*m_system); - switch (num_bytes) - { - case 1: - return m_system->GetMMU() - .HostTryReadU8(threadguard, address, PowerPC::RequestedAddressSpace::Physical) - .value_or(PowerPC::ReadResult(false, 0u)) - .value; - case 2: - return Common::swap16( - m_system->GetMMU() - .HostTryReadU16(threadguard, address, PowerPC::RequestedAddressSpace::Physical) - .value_or(PowerPC::ReadResult(false, 0u)) - .value); - case 4: - return Common::swap32( - m_system->GetMMU() - .HostTryReadU32(threadguard, address, PowerPC::RequestedAddressSpace::Physical) - .value_or(PowerPC::ReadResult(false, 0u)) - .value); - default: - ASSERT(false); - return 0u; - } -} - void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runtime_event) { switch (runtime_event->type) @@ -757,10 +720,7 @@ void AchievementManager::GenerateRichPresence(const Core::CPUThreadGuard& guard) std::lock_guard lg{m_lock}; rc_runtime_get_richpresence( &m_runtime, m_rich_presence.data(), RP_SIZE, - [](unsigned address, unsigned num_bytes, void* ud) { - return static_cast(ud)->MemoryPeeker(address, num_bytes, ud); - }, - this, nullptr); + [](unsigned address, unsigned num_bytes, void* ud) { return 0u; }, this, nullptr); } AchievementManager::ResponseType AchievementManager::AwardAchievement(AchievementId achievement_id) @@ -1168,7 +1128,7 @@ void AchievementManager::RequestV2(const rc_api_request_t* request, }); } -u32 AchievementManager::MemoryPeekerV2(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client) +u32 AchievementManager::MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client) { if (buffer == nullptr) return 0u; @@ -1239,4 +1199,8 @@ void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32 }); } +void AchievementManager::EventHandlerV2(const rc_client_event_t* event, rc_client_t* client) +{ +} + #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 3febabf606..89ebb2bc1b 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -127,7 +127,6 @@ public: void FetchGameBadges(); void DoFrame(); - u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud); void AchievementEventHandler(const rc_runtime_event_t* runtime_event); std::recursive_mutex& GetLock(); @@ -201,8 +200,9 @@ private: static void RequestV2(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); - static u32 MemoryPeekerV2(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); + static u32 MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); void FetchBadge(BadgeStatus* badge, u32 badge_type, const BadgeNameFunction function); + static void EventHandlerV2(const rc_client_event_t* event, rc_client_t* client); rc_runtime_t m_runtime{}; rc_client_t* m_client{}; From bc3e429dd92b130cd1476b8fce4a04cd1a08bb26 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 30 Mar 2024 18:04:00 -0400 Subject: [PATCH 12/31] Handle Achievement Triggered Client Event Also deletes the old runtime-based Achievement Triggered event from the old handler, and the methods used by it to publish to the server and reactivate/deactivate achievements in the runtime. --- Source/Core/Core/AchievementManager.cpp | 153 ++++-------------------- Source/Core/Core/AchievementManager.h | 5 +- 2 files changed, 26 insertions(+), 132 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 6a34350cb2..1a8a7479d5 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -234,9 +234,6 @@ void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runti { switch (runtime_event->type) { - case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED: - HandleAchievementTriggeredEvent(runtime_event); - break; case RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED: HandleAchievementProgressUpdatedEvent(runtime_event); break; @@ -665,56 +662,6 @@ AchievementManager::ResponseType AchievementManager::FetchBoardInfo(AchievementI return ResponseType::SUCCESS; } -void AchievementManager::ActivateDeactivateAchievement(AchievementId id, bool enabled, - bool unofficial, bool encore) -{ - auto it = m_unlock_map.find(id); - if (it == m_unlock_map.end()) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Attempted to unlock unknown achievement id {}.", id); - return; - } - const UnlockStatus& status = it->second; - u32 index = status.game_data_index; - bool active = (rc_runtime_get_achievement(&m_runtime, id) != nullptr); - bool hardcore_mode_enabled = Config::Get(Config::RA_HARDCORE_ENABLED); - - // Deactivate achievements if game is not loaded - bool activate = m_is_game_loaded; - // Activate achievements only if achievements are enabled - if (activate && !enabled) - activate = false; - // Deactivate if achievement is unofficial, unless unofficial achievements are enabled - if (activate && !unofficial && - m_game_data.achievements[index].category == RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL) - { - activate = false; - } - // If encore mode is on, activate/deactivate regardless of current unlock status - if (activate && !encore) - { - // Encore is off, achievement has been unlocked in this session, deactivate - activate = (status.session_unlock_count == 0); - // Encore is off, achievement has been hardcore unlocked on site, deactivate - if (activate && status.remote_unlock_status == UnlockStatus::UnlockType::HARDCORE) - activate = false; - // Encore is off, hardcore is off, achievement has been softcore unlocked on site, deactivate - if (activate && !hardcore_mode_enabled && - status.remote_unlock_status == UnlockStatus::UnlockType::SOFTCORE) - { - activate = false; - } - } - - if (!active && activate) - { - rc_runtime_activate_achievement(&m_runtime, id, m_game_data.achievements[index].definition, - nullptr, 0); - } - if (active && !activate) - rc_runtime_deactivate_achievement(&m_runtime, id); -} - void AchievementManager::GenerateRichPresence(const Core::CPUThreadGuard& guard) { std::lock_guard lg{m_lock}; @@ -723,33 +670,6 @@ void AchievementManager::GenerateRichPresence(const Core::CPUThreadGuard& guard) [](unsigned address, unsigned num_bytes, void* ud) { return 0u; }, this, nullptr); } -AchievementManager::ResponseType AchievementManager::AwardAchievement(AchievementId achievement_id) -{ - std::string username = Config::Get(Config::RA_USERNAME); - std::string api_token = Config::Get(Config::RA_API_TOKEN); - bool hardcore_mode_enabled = Config::Get(Config::RA_HARDCORE_ENABLED); - rc_api_award_achievement_request_t award_request = {.username = username.c_str(), - .api_token = api_token.c_str(), - .achievement_id = achievement_id, - .hardcore = hardcore_mode_enabled, - .game_hash = m_game_hash.data()}; - rc_api_award_achievement_response_t award_response = {}; - ResponseType r_type = - Request( - award_request, &award_response, rc_api_init_award_achievement_request, - rc_api_process_award_achievement_response); - rc_api_destroy_award_achievement_response(&award_response); - if (r_type == ResponseType::SUCCESS) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Awarded achievement ID {}.", achievement_id); - } - else - { - WARN_LOG_FMT(ACHIEVEMENTS, "Failed to award achievement ID {}.", achievement_id); - } - return r_type; -} - AchievementManager::ResponseType AchievementManager::SubmitLeaderboard(AchievementId leaderboard_id, int value) { @@ -851,55 +771,6 @@ void AchievementManager::DisplayWelcomeMessage() OSD::Duration::VERY_LONG, color); } -void AchievementManager::HandleAchievementTriggeredEvent(const rc_runtime_event_t* runtime_event) -{ - bool hardcore_mode_enabled = Config::Get(Config::RA_HARDCORE_ENABLED); - const auto event_id = runtime_event->id; - auto it = m_unlock_map.find(event_id); - if (it == m_unlock_map.end()) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid achievement triggered event with id {}.", event_id); - return; - } - it->second.session_unlock_count++; - 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, - (Config::Get(Config::RA_BADGES_ENABLED)) ? - DecodeBadgeToOSDIcon(it->second.unlocked_badge.badge) : - nullptr); - if (m_game_data.achievements[game_data_index].category == RC_ACHIEVEMENT_CATEGORY_CORE) - { - auto* user = rc_client_get_user_info(m_client); - m_queue.EmplaceItem([this, event_id] { AwardAchievement(event_id); }); - PointSpread spread = TallyScore(); - if (spread.hard_points == spread.total_points && - it->second.remote_unlock_status != UnlockStatus::UnlockType::HARDCORE) - { - OSD::AddMessage( - fmt::format("Congratulations! {} has mastered {}", user->display_name, m_game_data.title), - OSD::Duration::VERY_LONG, OSD::Color::YELLOW, - (Config::Get(Config::RA_BADGES_ENABLED)) ? DecodeBadgeToOSDIcon(m_game_badge.badge) : - nullptr); - } - else if (spread.hard_points + spread.soft_points == spread.total_points && - it->second.remote_unlock_status == UnlockStatus::UnlockType::LOCKED) - { - OSD::AddMessage(fmt::format("Congratulations! {} has completed {}", user->display_name, - m_game_data.title), - OSD::Duration::VERY_LONG, OSD::Color::CYAN, - (Config::Get(Config::RA_BADGES_ENABLED)) ? - DecodeBadgeToOSDIcon(m_game_badge.badge) : - nullptr); - } - } - ActivateDeactivateAchievement(event_id, Config::Get(Config::RA_ACHIEVEMENTS_ENABLED), - Config::Get(Config::RA_UNOFFICIAL_ENABLED), - Config::Get(Config::RA_ENCORE_ENABLED)); -} - void AchievementManager::HandleAchievementProgressUpdatedEvent( const rc_runtime_event_t* runtime_event) { @@ -1019,6 +890,21 @@ void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_ ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid leaderboard triggered event with id {}.", event_id); } +void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t* client_event) +{ + OSD::AddMessage(fmt::format("Unlocked: {} ({})", client_event->achievement->title, + client_event->achievement->points), + OSD::Duration::VERY_LONG, + (rc_client_get_hardcore_enabled(AchievementManager::GetInstance().m_client)) ? + OSD::Color::YELLOW : + OSD::Color::CYAN, + (Config::Get(Config::RA_BADGES_ENABLED)) ? + DecodeBadgeToOSDIcon(AchievementManager::GetInstance() + .m_unlocked_badges[client_event->achievement->id] + .badge) : + nullptr); +} + // 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 @@ -1201,6 +1087,15 @@ void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32 void AchievementManager::EventHandlerV2(const rc_client_event_t* event, rc_client_t* client) { + switch (event->type) + { + case RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED: + HandleAchievementTriggeredEvent(event); + break; + default: + INFO_LOG_FMT(ACHIEVEMENTS, "Event triggered of unhandled type {}", event->type); + break; + } } #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 89ebb2bc1b..94f40eb173 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -174,10 +174,8 @@ private: std::unique_ptr& GetLoadingVolume() { return m_loading_volume; }; - void ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore); void GenerateRichPresence(const Core::CPUThreadGuard& guard); - ResponseType AwardAchievement(AchievementId achievement_id); ResponseType SubmitLeaderboard(AchievementId leaderboard_id, int value); ResponseType PingRichPresence(const RichPresence& rich_presence); @@ -185,7 +183,6 @@ private: void* userdata); void DisplayWelcomeMessage(); - void HandleAchievementTriggeredEvent(const rc_runtime_event_t* runtime_event); void HandleAchievementProgressUpdatedEvent(const rc_runtime_event_t* runtime_event); void HandleAchievementPrimedEvent(const rc_runtime_event_t* runtime_event); void HandleAchievementUnprimedEvent(const rc_runtime_event_t* runtime_event); @@ -193,6 +190,8 @@ private: void HandleLeaderboardCanceledEvent(const rc_runtime_event_t* runtime_event); void HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event); + static void HandleAchievementTriggeredEvent(const rc_client_event_t* client_event); + template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, const std::function& init_request, From eee77ec6b753f7c007e9b39112a7c9f82f0a15e3 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sun, 31 Mar 2024 08:45:16 -0400 Subject: [PATCH 13/31] Handle Leaderboard Submitted/Started/Failed Client Events Also deletes the corresponding runtime-based events from the old event handler. --- Source/Core/Core/AchievementManager.cpp | 129 ++++++------------------ Source/Core/Core/AchievementManager.h | 7 +- 2 files changed, 33 insertions(+), 103 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 1a8a7479d5..d9987015e1 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -243,15 +243,6 @@ void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runti case RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED: HandleAchievementUnprimedEvent(runtime_event); break; - case RC_RUNTIME_EVENT_LBOARD_STARTED: - HandleLeaderboardStartedEvent(runtime_event); - break; - case RC_RUNTIME_EVENT_LBOARD_CANCELED: - HandleLeaderboardCanceledEvent(runtime_event); - break; - case RC_RUNTIME_EVENT_LBOARD_TRIGGERED: - HandleLeaderboardTriggeredEvent(runtime_event); - break; } } @@ -670,33 +661,6 @@ void AchievementManager::GenerateRichPresence(const Core::CPUThreadGuard& guard) [](unsigned address, unsigned num_bytes, void* ud) { return 0u; }, this, nullptr); } -AchievementManager::ResponseType AchievementManager::SubmitLeaderboard(AchievementId leaderboard_id, - int value) -{ - std::string username = Config::Get(Config::RA_USERNAME); - std::string api_token = Config::Get(Config::RA_API_TOKEN); - rc_api_submit_lboard_entry_request_t submit_request = {.username = username.c_str(), - .api_token = api_token.c_str(), - .leaderboard_id = leaderboard_id, - .score = value, - .game_hash = m_game_hash.data()}; - rc_api_submit_lboard_entry_response_t submit_response = {}; - ResponseType r_type = - Request( - submit_request, &submit_response, rc_api_init_submit_lboard_entry_request, - rc_api_process_submit_lboard_entry_response); - rc_api_destroy_submit_lboard_entry_response(&submit_response); - if (r_type == ResponseType::SUCCESS) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Submitted leaderboard ID {}.", leaderboard_id); - } - else - { - WARN_LOG_FMT(ACHIEVEMENTS, "Failed to submit leaderboard ID {}.", leaderboard_id); - } - return r_type; -} - AchievementManager::ResponseType AchievementManager::PingRichPresence(const RichPresence& rich_presence) { @@ -827,69 +791,6 @@ void AchievementManager::HandleAchievementUnprimedEvent(const rc_runtime_event_t m_active_challenges.erase(it->second.unlocked_badge.name); } -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); - return; - } - } - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid leaderboard started event with id {}.", runtime_event->id); -} - -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); - return; - } - } - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid leaderboard canceled event with id {}.", runtime_event->id); -} - -void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event) -{ - const auto event_id = runtime_event->id; - const auto event_value = runtime_event->value; - m_queue.EmplaceItem([this, event_id, event_value] { SubmitLeaderboard(event_id, event_value); }); - for (u32 ix = 0; ix < m_game_data.num_leaderboards; ix++) - { - if (m_game_data.leaderboards[ix].id == event_id) - { - FormattedValue value{}; - rc_runtime_format_lboard_value(value.data(), static_cast(value.size()), event_value, - m_game_data.leaderboards[ix].format); - if (std::find(value.begin(), value.end(), '\0') == value.end()) - { - OSD::AddMessage(fmt::format("Scored {} on leaderboard: {}", - std::string_view{value.data(), value.size()}, - m_game_data.leaderboards[ix].title), - OSD::Duration::VERY_LONG, OSD::Color::YELLOW); - } - else - { - OSD::AddMessage(fmt::format("Scored {} on leaderboard: {}", value.data(), - m_game_data.leaderboards[ix].title), - OSD::Duration::VERY_LONG, OSD::Color::YELLOW); - } - m_queue.EmplaceItem([this, event_id] { - FetchBoardInfo(event_id); - m_update_callback(); - }); - break; - } - } - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid leaderboard triggered event with id {}.", event_id); -} - void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t* client_event) { OSD::AddMessage(fmt::format("Unlocked: {} ({})", client_event->achievement->title, @@ -905,6 +806,27 @@ void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t nullptr); } +void AchievementManager::HandleLeaderboardStartedEvent(const rc_client_event_t* client_event) +{ + OSD::AddMessage(fmt::format("Attempting leaderboard: {} - {}", client_event->leaderboard->title, + client_event->leaderboard->description), + OSD::Duration::VERY_LONG, OSD::Color::GREEN); +} + +void AchievementManager::HandleLeaderboardFailedEvent(const rc_client_event_t* client_event) +{ + OSD::AddMessage(fmt::format("Failed leaderboard: {}", client_event->leaderboard->title), + OSD::Duration::VERY_LONG, OSD::Color::RED); +} + +void AchievementManager::HandleLeaderboardSubmittedEvent(const rc_client_event_t* client_event) +{ + OSD::AddMessage(fmt::format("Scored {} on leaderboard: {}", + client_event->leaderboard->tracker_value, + client_event->leaderboard->title), + OSD::Duration::VERY_LONG, OSD::Color::YELLOW); +} + // 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 @@ -1092,6 +1014,15 @@ void AchievementManager::EventHandlerV2(const rc_client_event_t* event, rc_clien case RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED: HandleAchievementTriggeredEvent(event); break; + case RC_CLIENT_EVENT_LEADERBOARD_STARTED: + HandleLeaderboardStartedEvent(event); + break; + case RC_CLIENT_EVENT_LEADERBOARD_FAILED: + HandleLeaderboardFailedEvent(event); + break; + case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED: + HandleLeaderboardSubmittedEvent(event); + break; default: INFO_LOG_FMT(ACHIEVEMENTS, "Event triggered of unhandled type {}", event->type); break; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 94f40eb173..acc87fcdb2 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -176,7 +176,6 @@ private: void GenerateRichPresence(const Core::CPUThreadGuard& guard); - ResponseType SubmitLeaderboard(AchievementId leaderboard_id, int value); ResponseType PingRichPresence(const RichPresence& rich_presence); static void LoadGameCallback(int result, const char* error_message, rc_client_t* client, @@ -186,11 +185,11 @@ private: void HandleAchievementProgressUpdatedEvent(const rc_runtime_event_t* runtime_event); void HandleAchievementPrimedEvent(const rc_runtime_event_t* runtime_event); void HandleAchievementUnprimedEvent(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); static void HandleAchievementTriggeredEvent(const rc_client_event_t* client_event); + static void HandleLeaderboardStartedEvent(const rc_client_event_t* client_event); + static void HandleLeaderboardFailedEvent(const rc_client_event_t* client_event); + static void HandleLeaderboardSubmittedEvent(const rc_client_event_t* client_event); template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, From 6f3a608e929d2973a5766197d4a5aae1c8f4fc06 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 24 Feb 2024 12:00:09 -0500 Subject: [PATCH 14/31] Add active leaderboard tracker to achievement manager The active leaderboard data (leaderboards currently being attempted, which get displayed on screen) is now tracked. When a leaderboard is started its value is added to a vector (sorted by start frame). There are a separate set of client events specifically to handle leaderboard trackers, that are used to populate and manage this vector. The top portion of this vector (by RetroAchievement standards, the first four items) is exposed to be displayed on screen. --- Source/Core/Core/AchievementManager.cpp | 47 +++++++++++++++++++++++++ Source/Core/Core/AchievementManager.h | 6 ++++ 2 files changed, 53 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index d9987015e1..f3e4b41ba6 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -402,6 +402,16 @@ const AchievementManager::NamedIconMap& AchievementManager::GetChallengeIcons() return m_active_challenges; } +std::vector AchievementManager::GetActiveLeaderboards() const +{ + std::vector display_values; + for (u32 ix = 0; ix < MAX_DISPLAYED_LBOARDS && ix < m_active_leaderboards.size(); ix++) + { + display_values.push_back(std::string(m_active_leaderboards[ix].display)); + } + return display_values; +} + void AchievementManager::CloseGame() { { @@ -409,6 +419,7 @@ void AchievementManager::CloseGame() if (rc_client_get_game_info(m_client)) { m_active_challenges.clear(); + m_active_leaderboards.clear(); m_game_badge.name.clear(); m_unlocked_badges.clear(); m_locked_badges.clear(); @@ -827,6 +838,33 @@ void AchievementManager::HandleLeaderboardSubmittedEvent(const rc_client_event_t OSD::Duration::VERY_LONG, OSD::Color::YELLOW); } +void AchievementManager::HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* client_event) +{ + auto& active_leaderboards = AchievementManager::GetInstance().m_active_leaderboards; + for (auto& leaderboard : active_leaderboards) + { + if (leaderboard.id == client_event->leaderboard_tracker->id) + { + strncpy(leaderboard.display, client_event->leaderboard_tracker->display, + RC_CLIENT_LEADERBOARD_DISPLAY_SIZE); + } + } +} + +void AchievementManager::HandleLeaderboardTrackerShowEvent(const rc_client_event_t* client_event) +{ + AchievementManager::GetInstance().m_active_leaderboards.push_back( + *client_event->leaderboard_tracker); +} + +void AchievementManager::HandleLeaderboardTrackerHideEvent(const rc_client_event_t* client_event) +{ + auto& active_leaderboards = AchievementManager::GetInstance().m_active_leaderboards; + std::erase_if(active_leaderboards, [client_event](const auto& leaderboard) { + return leaderboard.id == client_event->leaderboard_tracker->id; + }); +} + // 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 @@ -1023,6 +1061,15 @@ void AchievementManager::EventHandlerV2(const rc_client_event_t* event, rc_clien case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED: HandleLeaderboardSubmittedEvent(event); break; + case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE: + HandleLeaderboardTrackerUpdateEvent(event); + break; + case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW: + HandleLeaderboardTrackerShowEvent(event); + break; + case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE: + HandleLeaderboardTrackerHideEvent(event); + break; default: INFO_LOG_FMT(ACHIEVEMENTS, "Event triggered of unhandled type {}", event->type); break; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index acc87fcdb2..a833de0be9 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -73,6 +73,7 @@ public: using RichPresence = std::array; using Badge = std::vector; using NamedIconMap = std::map, std::less<>>; + static constexpr size_t MAX_DISPLAYED_LBOARDS = 4; struct BadgeStatus { @@ -146,6 +147,7 @@ public: bool IsDisabled() const { return m_disabled; }; void SetDisabled(bool disabled); const NamedIconMap& GetChallengeIcons() const; + std::vector GetActiveLeaderboards() const; void CloseGame(); void Logout(); @@ -190,6 +192,9 @@ private: static void HandleLeaderboardStartedEvent(const rc_client_event_t* client_event); static void HandleLeaderboardFailedEvent(const rc_client_event_t* client_event); static void HandleLeaderboardSubmittedEvent(const rc_client_event_t* client_event); + static void HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* client_event); + static void HandleLeaderboardTrackerShowEvent(const rc_client_event_t* client_event); + static void HandleLeaderboardTrackerHideEvent(const rc_client_event_t* client_event); template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, @@ -224,6 +229,7 @@ private: std::unordered_map m_unlock_map; std::unordered_map m_leaderboard_map; NamedIconMap m_active_challenges; + std::vector m_active_leaderboards; Common::WorkQueueThread> m_queue; Common::WorkQueueThread> m_image_queue; From 7895b739ee2be1f429d4660e27e69f3805921d25 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 24 Feb 2024 12:06:59 -0500 Subject: [PATCH 15/31] Display active leaderboard data on screen Up to four leaderboards are displayed in a window in the bottom right of the screen (vertically above challenge icons, if there are any). As per RetroAchievements standards, the markers only display the current leaderboard values with no further context necessary. --- Source/Core/VideoCommon/OnScreenUI.cpp | 99 ++++++++++++++------------ Source/Core/VideoCommon/OnScreenUI.h | 2 +- 2 files changed, 54 insertions(+), 47 deletions(-) diff --git a/Source/Core/VideoCommon/OnScreenUI.cpp b/Source/Core/VideoCommon/OnScreenUI.cpp index 4d0213a1a4..715f218b05 100644 --- a/Source/Core/VideoCommon/OnScreenUI.cpp +++ b/Source/Core/VideoCommon/OnScreenUI.cpp @@ -332,63 +332,70 @@ void OnScreenUI::DrawDebugText() } #ifdef USE_RETRO_ACHIEVEMENTS -void OnScreenUI::DrawChallenges() +void OnScreenUI::DrawChallengesAndLeaderboards() { std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; const auto& challenge_icons = AchievementManager::GetInstance().GetChallengeIcons(); - if (challenge_icons.empty()) - return; - - const std::string window_name = "Challenges"; - - u32 sum_of_icon_heights = 0; - u32 max_icon_width = 0; - for (const auto& [name, icon] : challenge_icons) + const auto& leaderboard_progress = AchievementManager::GetInstance().GetActiveLeaderboards(); + float leaderboard_y = ImGui::GetIO().DisplaySize.y; + if (!challenge_icons.empty()) { - // These *should* all be the same square size but you never know. - if (icon->width > max_icon_width) - max_icon_width = icon->width; - sum_of_icon_heights += icon->height; - } - ImGui::SetNextWindowPos( - ImVec2(ImGui::GetIO().DisplaySize.x - 20.f * m_backbuffer_scale - max_icon_width, - ImGui::GetIO().DisplaySize.y - 20.f * m_backbuffer_scale - sum_of_icon_heights)); - ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f)); - if (ImGui::Begin(window_name.c_str(), nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing)) - { - for (const auto& [name, icon] : challenge_icons) + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y), 0, + ImVec2(1.0, 1.0)); + ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f)); + if (ImGui::Begin("Challenges", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing)) { - if (m_challenge_texture_map.find(name) != m_challenge_texture_map.end()) - continue; - const u32 width = icon->width; - const u32 height = icon->height; - TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0, - AbstractTextureType::Texture_2DArray); - auto res = m_challenge_texture_map.insert_or_assign(name, g_gfx->CreateTexture(tex_config)); - res.first->second->Load(0, width, height, width, icon->rgba_data.data(), - sizeof(u32) * width * height); - } - for (auto& [name, texture] : m_challenge_texture_map) - { - auto icon_itr = challenge_icons.find(name); - if (icon_itr == challenge_icons.end()) + for (const auto& [name, icon] : challenge_icons) { - m_challenge_texture_map.erase(name); - continue; + if (m_challenge_texture_map.find(name) != m_challenge_texture_map.end()) + continue; + const u32 width = icon->width; + const u32 height = icon->height; + TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0, + AbstractTextureType::Texture_2DArray); + auto res = m_challenge_texture_map.insert_or_assign(name, g_gfx->CreateTexture(tex_config)); + res.first->second->Load(0, width, height, width, icon->rgba_data.data(), + sizeof(u32) * width * height); } - if (texture) + for (auto& [name, texture] : m_challenge_texture_map) { - ImGui::Image(texture.get(), ImVec2(static_cast(icon_itr->second->width), - static_cast(icon_itr->second->height))); + auto icon_itr = challenge_icons.find(name); + if (icon_itr == challenge_icons.end()) + { + m_challenge_texture_map.erase(name); + continue; + } + if (texture) + { + ImGui::Image(texture.get(), ImVec2(static_cast(icon_itr->second->width), + static_cast(icon_itr->second->height))); + } } + leaderboard_y -= ImGui::GetWindowHeight(); } + ImGui::End(); } - ImGui::End(); + if (!leaderboard_progress.empty()) + { + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x, leaderboard_y), 0, + ImVec2(1.0, 1.0)); + ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f)); + if (ImGui::Begin("Leaderboards", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing)) + { + for (const auto& value : leaderboard_progress) + ImGui::Text(value.data()); + } + ImGui::End(); + } } #endif // USE_RETRO_ACHIEVEMENTS @@ -400,7 +407,7 @@ void OnScreenUI::Finalize() DrawDebugText(); OSD::DrawMessages(); #ifdef USE_RETRO_ACHIEVEMENTS - DrawChallenges(); + DrawChallengesAndLeaderboards(); #endif // USE_RETRO_ACHIEVEMENTS ImGui::Render(); } diff --git a/Source/Core/VideoCommon/OnScreenUI.h b/Source/Core/VideoCommon/OnScreenUI.h index 9b7aa0d3f8..1acef96901 100644 --- a/Source/Core/VideoCommon/OnScreenUI.h +++ b/Source/Core/VideoCommon/OnScreenUI.h @@ -62,7 +62,7 @@ public: private: void DrawDebugText(); #ifdef USE_RETRO_ACHIEVEMENTS - void DrawChallenges(); + void DrawChallengesAndLeaderboards(); #endif // USE_RETRO_ACHIEVEMENTS // ImGui resources. From a70733f74f5b337f5da4bc9888cfa3b79d465ce7 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Mon, 1 Apr 2024 14:30:39 -0400 Subject: [PATCH 16/31] Handle Achievement Challenge Indicator Show/Hide Client Events Also deletes the corresponding runtime-based events from the old event handler. --- Source/Core/Core/AchievementManager.cpp | 62 +++++++++++-------------- Source/Core/Core/AchievementManager.h | 4 +- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index f3e4b41ba6..61f002209a 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -237,12 +237,6 @@ void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runti case RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED: HandleAchievementProgressUpdatedEvent(runtime_event); break; - case RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED: - HandleAchievementPrimedEvent(runtime_event); - break; - case RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED: - HandleAchievementUnprimedEvent(runtime_event); - break; } } @@ -774,34 +768,6 @@ void AchievementManager::HandleAchievementProgressUpdatedEvent( nullptr); } -void AchievementManager::HandleAchievementPrimedEvent(const rc_runtime_event_t* runtime_event) -{ - if (!Config::Get(Config::RA_BADGES_ENABLED)) - return; - auto it = m_unlock_map.find(runtime_event->id); - if (it == m_unlock_map.end()) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid achievement primed event with id {}.", runtime_event->id); - return; - } - m_active_challenges[it->second.unlocked_badge.name] = - DecodeBadgeToOSDIcon(it->second.unlocked_badge.badge); -} - -void AchievementManager::HandleAchievementUnprimedEvent(const rc_runtime_event_t* runtime_event) -{ - if (!Config::Get(Config::RA_BADGES_ENABLED)) - return; - auto it = m_unlock_map.find(runtime_event->id); - if (it == m_unlock_map.end()) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid achievement unprimed event with id {}.", - runtime_event->id); - return; - } - m_active_challenges.erase(it->second.unlocked_badge.name); -} - void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t* client_event) { OSD::AddMessage(fmt::format("Unlocked: {} ({})", client_event->achievement->title, @@ -865,6 +831,28 @@ void AchievementManager::HandleLeaderboardTrackerHideEvent(const rc_client_event }); } +void AchievementManager::HandleAchievementChallengeIndicatorShowEvent( + const rc_client_event_t* client_event) +{ + if (Config::Get(Config::RA_BADGES_ENABLED)) + { + auto& unlocked_badges = AchievementManager::GetInstance().m_unlocked_badges; + if (const auto unlocked_iter = unlocked_badges.find(client_event->achievement->id); + unlocked_iter != unlocked_badges.end()) + { + AchievementManager::GetInstance().m_active_challenges[client_event->achievement->badge_name] = + DecodeBadgeToOSDIcon(unlocked_iter->second.badge); + } + } +} + +void AchievementManager::HandleAchievementChallengeIndicatorHideEvent( + const rc_client_event_t* client_event) +{ + AchievementManager::GetInstance().m_active_challenges.erase( + client_event->achievement->badge_name); +} + // 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 @@ -1070,6 +1058,12 @@ void AchievementManager::EventHandlerV2(const rc_client_event_t* event, rc_clien case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE: HandleLeaderboardTrackerHideEvent(event); break; + case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW: + HandleAchievementChallengeIndicatorShowEvent(event); + break; + case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE: + HandleAchievementChallengeIndicatorHideEvent(event); + break; default: INFO_LOG_FMT(ACHIEVEMENTS, "Event triggered of unhandled type {}", event->type); break; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index a833de0be9..5292cc182e 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -185,8 +185,6 @@ private: void DisplayWelcomeMessage(); void HandleAchievementProgressUpdatedEvent(const rc_runtime_event_t* runtime_event); - void HandleAchievementPrimedEvent(const rc_runtime_event_t* runtime_event); - void HandleAchievementUnprimedEvent(const rc_runtime_event_t* runtime_event); static void HandleAchievementTriggeredEvent(const rc_client_event_t* client_event); static void HandleLeaderboardStartedEvent(const rc_client_event_t* client_event); @@ -195,6 +193,8 @@ private: static void HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* client_event); static void HandleLeaderboardTrackerShowEvent(const rc_client_event_t* client_event); static void HandleLeaderboardTrackerHideEvent(const rc_client_event_t* client_event); + static void HandleAchievementChallengeIndicatorShowEvent(const rc_client_event_t* client_event); + static void HandleAchievementChallengeIndicatorHideEvent(const rc_client_event_t* client_event); template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, From 6b5b7cbd7cc066bbdbc99dfe0b025f804d51ecb5 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Mon, 1 Apr 2024 14:45:17 -0400 Subject: [PATCH 17/31] Handle Achievement Progress Client Events This is not a 1 to 1 relationship with how the events look primarily because currently achievement progress messages are in OnScreenDisplay, which currently vanishes messages automatically. As this covers the last remaining runtime-based event from the old event handler, that handler has been deleted and the new event handler has been renamed to take its place. --- Source/Core/Core/AchievementManager.cpp | 63 +++++++++---------------- Source/Core/Core/AchievementManager.h | 6 +-- 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 61f002209a..a0715f3ea5 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -40,7 +40,7 @@ void AchievementManager::Init() std::string host_url = Config::Get(Config::RA_HOST_URL); if (!host_url.empty()) rc_client_set_host(m_client, host_url.c_str()); - rc_client_set_event_handler(m_client, EventHandlerV2); + rc_client_set_event_handler(m_client, EventHandler); rc_client_enable_logging(m_client, RC_CLIENT_LOG_LEVEL_VERBOSE, [](const char* message, const rc_client_t* client) { INFO_LOG_FMT(ACHIEVEMENTS, "{}", message); @@ -230,16 +230,6 @@ void AchievementManager::DoFrame() } } -void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runtime_event) -{ - switch (runtime_event->type) - { - case RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED: - HandleAchievementProgressUpdatedEvent(runtime_event); - break; - } -} - std::recursive_mutex& AchievementManager::GetLock() { return m_lock; @@ -740,34 +730,6 @@ void AchievementManager::DisplayWelcomeMessage() OSD::Duration::VERY_LONG, color); } -void AchievementManager::HandleAchievementProgressUpdatedEvent( - const rc_runtime_event_t* runtime_event) -{ - if (!Config::Get(Config::RA_PROGRESS_ENABLED)) - return; - auto it = m_unlock_map.find(runtime_event->id); - if (it == m_unlock_map.end()) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid achievement progress updated event with id {}.", - runtime_event->id); - return; - } - AchievementId game_data_index = it->second.game_data_index; - FormattedValue value{}; - if (rc_runtime_format_achievement_measured(&m_runtime, runtime_event->id, value.data(), - FORMAT_SIZE) == 0) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to format measured data {}.", value.data()); - return; - } - OSD::AddMessage( - fmt::format("{} {}", m_game_data.achievements[game_data_index].title, value.data()), - OSD::Duration::SHORT, OSD::Color::GREEN, - (Config::Get(Config::RA_BADGES_ENABLED)) ? - DecodeBadgeToOSDIcon(it->second.unlocked_badge.badge) : - nullptr); -} - void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t* client_event) { OSD::AddMessage(fmt::format("Unlocked: {} ({})", client_event->achievement->title, @@ -853,6 +815,19 @@ void AchievementManager::HandleAchievementChallengeIndicatorHideEvent( client_event->achievement->badge_name); } +void AchievementManager::HandleAchievementProgressIndicatorShowEvent( + const rc_client_event_t* client_event) +{ + OSD::AddMessage(fmt::format("{} {}", client_event->achievement->title, + client_event->achievement->measured_progress), + OSD::Duration::SHORT, OSD::Color::GREEN, + (Config::Get(Config::RA_BADGES_ENABLED)) ? + DecodeBadgeToOSDIcon(AchievementManager::GetInstance() + .m_unlocked_badges[client_event->achievement->id] + .badge) : + nullptr); +} + // 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 @@ -1033,7 +1008,7 @@ void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32 }); } -void AchievementManager::EventHandlerV2(const rc_client_event_t* event, rc_client_t* client) +void AchievementManager::EventHandler(const rc_client_event_t* event, rc_client_t* client) { switch (event->type) { @@ -1064,6 +1039,14 @@ void AchievementManager::EventHandlerV2(const rc_client_event_t* event, rc_clien case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE: HandleAchievementChallengeIndicatorHideEvent(event); break; + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW: + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE: + HandleAchievementProgressIndicatorShowEvent(event); + break; + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE: + // OnScreenDisplay messages disappear over time, so this is unnecessary + // unless the display algorithm changes in the future. + break; default: INFO_LOG_FMT(ACHIEVEMENTS, "Event triggered of unhandled type {}", event->type); break; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 5292cc182e..addc3aff49 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -128,7 +128,6 @@ public: void FetchGameBadges(); void DoFrame(); - void AchievementEventHandler(const rc_runtime_event_t* runtime_event); std::recursive_mutex& GetLock(); bool IsHardcoreModeActive() const; @@ -184,8 +183,6 @@ private: void* userdata); void DisplayWelcomeMessage(); - void HandleAchievementProgressUpdatedEvent(const rc_runtime_event_t* runtime_event); - static void HandleAchievementTriggeredEvent(const rc_client_event_t* client_event); static void HandleLeaderboardStartedEvent(const rc_client_event_t* client_event); static void HandleLeaderboardFailedEvent(const rc_client_event_t* client_event); @@ -195,6 +192,7 @@ private: static void HandleLeaderboardTrackerHideEvent(const rc_client_event_t* client_event); static void HandleAchievementChallengeIndicatorShowEvent(const rc_client_event_t* client_event); static void HandleAchievementChallengeIndicatorHideEvent(const rc_client_event_t* client_event); + static void HandleAchievementProgressIndicatorShowEvent(const rc_client_event_t* client_event); template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, @@ -205,7 +203,7 @@ private: void* callback_data, rc_client_t* client); static u32 MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); void FetchBadge(BadgeStatus* badge, u32 badge_type, const BadgeNameFunction function); - static void EventHandlerV2(const rc_client_event_t* event, rc_client_t* client); + static void EventHandler(const rc_client_event_t* event, rc_client_t* client); rc_runtime_t m_runtime{}; rc_client_t* m_client{}; From 70116b222d5063e309b06e6f568cc9660ad3e276 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Mon, 1 Apr 2024 15:20:35 -0400 Subject: [PATCH 18/31] Handle Game Completed Client Event --- Source/Core/Core/AchievementManager.cpp | 22 ++++++++++++++++++++++ Source/Core/Core/AchievementManager.h | 1 + 2 files changed, 23 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index a0715f3ea5..451aa4d49a 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -828,6 +828,25 @@ void AchievementManager::HandleAchievementProgressIndicatorShowEvent( nullptr); } +void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* client_event, + rc_client_t* client) +{ + auto* user_info = rc_client_get_user_info(client); + auto* game_info = rc_client_get_game_info(client); + if (!user_info || !game_info) + { + WARN_LOG_FMT(ACHIEVEMENTS, "Received Game Completed event when game not running."); + return; + } + bool hardcore = rc_client_get_hardcore_enabled(client); + OSD::AddMessage(fmt::format("Congratulations! {} has {} {}", user_info->display_name, + hardcore ? "mastered" : "completed", game_info->title), + OSD::Duration::VERY_LONG, hardcore ? OSD::Color::YELLOW : OSD::Color::CYAN, + (Config::Get(Config::RA_BADGES_ENABLED)) ? + DecodeBadgeToOSDIcon(AchievementManager::GetInstance().m_game_badge.badge) : + nullptr); +} + // 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 @@ -1047,6 +1066,9 @@ void AchievementManager::EventHandler(const rc_client_event_t* event, rc_client_ // OnScreenDisplay messages disappear over time, so this is unnecessary // unless the display algorithm changes in the future. break; + case RC_CLIENT_EVENT_GAME_COMPLETED: + HandleGameCompletedEvent(event, client); + break; default: INFO_LOG_FMT(ACHIEVEMENTS, "Event triggered of unhandled type {}", event->type); break; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index addc3aff49..cc2dcda34e 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -193,6 +193,7 @@ private: static void HandleAchievementChallengeIndicatorShowEvent(const rc_client_event_t* client_event); static void HandleAchievementChallengeIndicatorHideEvent(const rc_client_event_t* client_event); static void HandleAchievementProgressIndicatorShowEvent(const rc_client_event_t* client_event); + static void HandleGameCompletedEvent(const rc_client_event_t* client_event, rc_client_t* client); template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, From 062720913136ddf25a61d9239a809115dbbc9050 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Fri, 16 Feb 2024 14:25:04 -0500 Subject: [PATCH 19/31] Add DoState to AchievementManager While state loading is not allowed in the hardcore mode that most players will use, it is allowed in softcore mode; more importantly, if something fails to unlock or unlocks when it shouldn't in either mode the player can create a save that retains the current achievement state. --- Source/Core/Core/AchievementManager.cpp | 40 +++++++++++++++++++++++++ Source/Core/Core/AchievementManager.h | 2 ++ Source/Core/Core/State.cpp | 6 +++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 451aa4d49a..4d4700e857 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -396,6 +396,46 @@ std::vector AchievementManager::GetActiveLeaderboards() const return display_values; } +void AchievementManager::DoState(PointerWrap& p) +{ + if (!m_client || !Config::Get(Config::RA_ENABLED)) + return; + size_t size = 0; + if (!p.IsReadMode()) + size = rc_client_progress_size(m_client); + p.Do(size); + auto buffer = std::make_unique(size); + if (!p.IsReadMode()) + { + int result = rc_client_serialize_progress_sized(m_client, buffer.get(), size); + if (result != RC_OK) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Failed serializing achievement client with error code {}", + result); + return; + } + } + p.DoArray(buffer.get(), (u32)size); + if (p.IsReadMode()) + { + int result = rc_client_deserialize_progress_sized(m_client, buffer.get(), size); + if (result != RC_OK) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Failed deserializing achievement client with error code {}", + result); + return; + } + size_t new_size = rc_client_progress_size(m_client); + if (size != new_size) + { + ERROR_LOG_FMT(ACHIEVEMENTS, "Loaded client size {} does not match size in state {}", new_size, + size); + return; + } + } + p.DoMarker("AchievementManager"); +} + void AchievementManager::CloseGame() { { diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index cc2dcda34e..eb1cb3d740 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -148,6 +148,8 @@ public: const NamedIconMap& GetChallengeIcons() const; std::vector GetActiveLeaderboards() const; + void DoState(PointerWrap& p); + void CloseGame(); void Logout(); void Shutdown(); diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 90f07d2756..4b3d0edfd0 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -98,7 +98,7 @@ static size_t s_state_writes_in_queue; static std::condition_variable s_state_write_queue_is_empty; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 167; // Last changed in PR 12494 +constexpr u32 STATE_VERSION = 168; // Last changed in PR 12639 // Increase this if the StateExtendedHeader definition changes constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217 @@ -198,6 +198,10 @@ static void DoState(Core::System& system, PointerWrap& p) p.DoMarker("Wiimote"); Gecko::DoState(p); p.DoMarker("Gecko"); + +#ifdef USE_RETRO_ACHIEVEMENTS + AchievementManager::GetInstance().DoState(p); +#endif // USE_RETRO_ACHIEVEMENTS } void LoadFromBuffer(Core::System& system, std::vector& buffer) From 3793d723b970be7ae8f40457dd64fbae1cfdb9e9 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 9 Mar 2024 14:14:08 -0500 Subject: [PATCH 20/31] Created AchievementBox Qt object AchievementBox is an extension of QGroupBox that contains the data for a single achievement, initialized with the achievement data and able to reference AchievementManager to update itself. --- Source/Core/Core/AchievementManager.cpp | 19 ++- Source/Core/Core/AchievementManager.h | 6 +- .../DolphinQt/Achievements/AchievementBox.cpp | 104 ++++++++++++++++ .../DolphinQt/Achievements/AchievementBox.h | 32 +++++ .../AchievementProgressWidget.cpp | 117 ++---------------- .../Achievements/AchievementProgressWidget.h | 3 - Source/Core/DolphinQt/CMakeLists.txt | 2 + Source/Core/DolphinQt/DolphinQt.vcxproj | 2 + 8 files changed, 171 insertions(+), 114 deletions(-) create mode 100644 Source/Core/DolphinQt/Achievements/AchievementBox.cpp create mode 100644 Source/Core/DolphinQt/Achievements/AchievementBox.h diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 4d4700e857..a40df12104 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -306,6 +306,11 @@ AchievementManager::PointSpread AchievementManager::TallyScore() const return spread; } +rc_client_t* AchievementManager::GetClient() +{ + return m_client; +} + rc_api_fetch_game_data_response_t* AchievementManager::GetGameData() { return &m_game_data; @@ -316,10 +321,20 @@ const AchievementManager::BadgeStatus& AchievementManager::GetGameBadge() const return m_game_badge; } -const AchievementManager::UnlockStatus& +const AchievementManager::BadgeStatus& AchievementManager::GetAchievementBadge(AchievementId id, + bool locked) const +{ + auto& badge_list = locked ? m_locked_badges : m_locked_badges; + auto itr = badge_list.find(id); + return (itr == badge_list.end()) ? m_default_badge : itr->second; +} + +const AchievementManager::UnlockStatus* AchievementManager::GetUnlockStatus(AchievementId achievement_id) const { - return m_unlock_map.at(achievement_id); + if (m_unlock_map.count(achievement_id) < 1) + return nullptr; + return &m_unlock_map.at(achievement_id); } AchievementManager::ResponseType diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index eb1cb3d740..6cd7d39a90 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -136,9 +136,11 @@ public: const BadgeStatus& GetPlayerBadge() const; std::string_view GetGameDisplayName() const; PointSpread TallyScore() const; + rc_client_t* GetClient(); rc_api_fetch_game_data_response_t* GetGameData(); const BadgeStatus& GetGameBadge() const; - const UnlockStatus& GetUnlockStatus(AchievementId achievement_id) const; + const BadgeStatus& GetAchievementBadge(AchievementId id, bool locked) const; + const UnlockStatus* GetUnlockStatus(AchievementId achievement_id) const; AchievementManager::ResponseType GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target); const std::unordered_map& GetLeaderboardsInfo() const; @@ -163,6 +165,8 @@ private: std::unique_ptr volume; }; + const BadgeStatus m_default_badge; + static void* FilereaderOpenByFilepath(const char* path_utf8); static void* FilereaderOpenByVolume(const char* path_utf8); static void FilereaderSeek(void* file_handle, int64_t offset, int origin); diff --git a/Source/Core/DolphinQt/Achievements/AchievementBox.cpp b/Source/Core/DolphinQt/Achievements/AchievementBox.cpp new file mode 100644 index 0000000000..534b965d78 --- /dev/null +++ b/Source/Core/DolphinQt/Achievements/AchievementBox.cpp @@ -0,0 +1,104 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef USE_RETRO_ACHIEVEMENTS +#include "DolphinQt/Achievements/AchievementBox.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "Core/AchievementManager.h" +#include "Core/Config/AchievementSettings.h" + +#include "DolphinQt/QtUtils/FromStdString.h" + +AchievementBox::AchievementBox(QWidget* parent, rc_client_achievement_t* achievement) + : QGroupBox(parent), m_achievement(achievement) +{ + const auto& instance = AchievementManager::GetInstance(); + if (!instance.IsGameLoaded()) + return; + + m_badge = new QLabel(); + QLabel* title = new QLabel(QString::fromUtf8(achievement->title, strlen(achievement->title))); + QLabel* description = + new QLabel(QString::fromUtf8(achievement->description, strlen(achievement->description))); + QLabel* points = new QLabel(tr("%1 points").arg(achievement->points)); + m_status = new QLabel(); + m_progress_bar = new QProgressBar(); + QSizePolicy sp_retain = m_progress_bar->sizePolicy(); + sp_retain.setRetainSizeWhenHidden(true); + m_progress_bar->setSizePolicy(sp_retain); + + QVBoxLayout* a_col_right = new QVBoxLayout(); + a_col_right->addWidget(title); + a_col_right->addWidget(description); + a_col_right->addWidget(points); + a_col_right->addWidget(m_status); + a_col_right->addWidget(m_progress_bar); + QHBoxLayout* a_total = new QHBoxLayout(); + a_total->addWidget(m_badge); + a_total->addLayout(a_col_right); + setLayout(a_total); + + UpdateData(); +} + +void AchievementBox::UpdateData() +{ + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; + + const auto& badge = AchievementManager::GetInstance().GetAchievementBadge( + m_achievement->id, m_achievement->state != RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED); + std::string_view color = AchievementManager::GRAY; + if (m_achievement->unlocked & RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) + color = AchievementManager::GOLD; + else if (m_achievement->unlocked & RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE) + color = AchievementManager::BLUE; + if (Config::Get(Config::RA_BADGES_ENABLED) && badge.name != "") + { + QImage i_badge{}; + if (i_badge.loadFromData(&badge.badge.front(), static_cast(badge.badge.size()))) + { + m_badge->setPixmap(QPixmap::fromImage(i_badge).scaled(64, 64, Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + m_badge->adjustSize(); + m_badge->setStyleSheet( + QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color))); + } + } + else + { + m_badge->setText({}); + } + + if (m_achievement->state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) + { + m_status->setText( + tr("Unlocked at %1") + .arg(QDateTime::fromSecsSinceEpoch(m_achievement->unlock_time).toString())); + } + else + { + m_status->setText(tr("Locked")); + } + + if (m_achievement->measured_percent > 0.000) + { + m_progress_bar->setRange(0, 100); + m_progress_bar->setValue(m_achievement->measured_percent); + m_progress_bar->setVisible(true); + } + else + { + m_progress_bar->setVisible(false); + } +} + +#endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementBox.h b/Source/Core/DolphinQt/Achievements/AchievementBox.h new file mode 100644 index 0000000000..1bb7ce56ad --- /dev/null +++ b/Source/Core/DolphinQt/Achievements/AchievementBox.h @@ -0,0 +1,32 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef USE_RETRO_ACHIEVEMENTS +#include + +#include "Core/AchievementManager.h" + +class QLabel; +class QProgressBar; +class QWidget; + +struct rc_api_achievement_definition_t; + +class AchievementBox final : public QGroupBox +{ + Q_OBJECT +public: + explicit AchievementBox(QWidget* parent, rc_client_achievement_t* achievement); + void UpdateData(); + +private: + QLabel* m_badge; + QLabel* m_status; + QProgressBar* m_progress_bar; + + rc_client_achievement_t* m_achievement; +}; + +#endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp index 39cc36948c..bfa008db5a 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp @@ -18,11 +18,10 @@ #include "Core/Config/MainSettings.h" #include "Core/Core.h" +#include "DolphinQt/Achievements/AchievementBox.h" #include "DolphinQt/QtUtils/ClearLayoutRecursively.h" #include "DolphinQt/Settings.h" -static constexpr bool hardcore_mode_enabled = false; - AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget(parent) { m_common_box = new QGroupBox(); @@ -42,88 +41,6 @@ AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget( setLayout(layout); } -QGroupBox* -AchievementProgressWidget::CreateAchievementBox(const rc_api_achievement_definition_t* achievement) -{ - const auto& instance = AchievementManager::GetInstance(); - if (!instance.IsGameLoaded()) - return new QGroupBox(); - - QLabel* a_badge = new QLabel(); - const auto unlock_status = instance.GetUnlockStatus(achievement->id); - const AchievementManager::BadgeStatus* badge = &unlock_status.locked_badge; - std::string_view color = AchievementManager::GRAY; - if (unlock_status.remote_unlock_status == AchievementManager::UnlockStatus::UnlockType::HARDCORE) - { - badge = &unlock_status.unlocked_badge; - color = AchievementManager::GOLD; - } - else if (hardcore_mode_enabled && unlock_status.session_unlock_count > 1) - { - badge = &unlock_status.unlocked_badge; - color = AchievementManager::GOLD; - } - else if (unlock_status.remote_unlock_status == - AchievementManager::UnlockStatus::UnlockType::SOFTCORE) - { - badge = &unlock_status.unlocked_badge; - color = AchievementManager::BLUE; - } - else if (unlock_status.session_unlock_count > 1) - { - badge = &unlock_status.unlocked_badge; - color = AchievementManager::BLUE; - } - if (Config::Get(Config::RA_BADGES_ENABLED) && badge->name != "") - { - QImage i_badge{}; - if (i_badge.loadFromData(&badge->badge.front(), (int)badge->badge.size())) - { - a_badge->setPixmap(QPixmap::fromImage(i_badge).scaled(64, 64, Qt::KeepAspectRatio, - Qt::SmoothTransformation)); - a_badge->adjustSize(); - a_badge->setStyleSheet( - QStringLiteral("border: 4px solid %1").arg(QString::fromStdString(std::string(color)))); - } - } - - QLabel* a_title = new QLabel(QString::fromUtf8(achievement->title, strlen(achievement->title))); - QLabel* a_description = - new QLabel(QString::fromUtf8(achievement->description, strlen(achievement->description))); - QLabel* a_points = new QLabel(tr("%1 points").arg(achievement->points)); - QLabel* a_status = new QLabel(GetStatusString(achievement->id)); - QProgressBar* a_progress_bar = new QProgressBar(); - QSizePolicy sp_retain = a_progress_bar->sizePolicy(); - sp_retain.setRetainSizeWhenHidden(true); - a_progress_bar->setSizePolicy(sp_retain); - unsigned int value = 0; - unsigned int target = 0; - if (AchievementManager::GetInstance().GetAchievementProgress(achievement->id, &value, &target) == - AchievementManager::ResponseType::SUCCESS && - target > 0) - { - a_progress_bar->setRange(0, target); - a_progress_bar->setValue(value); - } - else - { - a_progress_bar->setVisible(false); - } - - QVBoxLayout* a_col_right = new QVBoxLayout(); - a_col_right->addWidget(a_title); - a_col_right->addWidget(a_description); - a_col_right->addWidget(a_points); - a_col_right->addWidget(a_status); - a_col_right->addWidget(a_progress_bar); - QHBoxLayout* a_total = new QHBoxLayout(); - a_total->addWidget(a_badge); - a_total->addLayout(a_col_right); - QGroupBox* a_group_box = new QGroupBox(); - a_group_box->setLayout(a_total); - return a_group_box; -} - void AchievementProgressWidget::UpdateData() { ClearLayoutRecursively(m_common_layout); @@ -132,34 +49,18 @@ void AchievementProgressWidget::UpdateData() if (!instance.IsGameLoaded()) return; - const auto* game_data = instance.GetGameData(); - for (u32 ix = 0; ix < game_data->num_achievements; ix++) + auto* client = instance.GetClient(); + auto* achievement_list = + rc_client_create_achievement_list(client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE); + for (u32 ix = 0; ix < achievement_list->num_buckets; ix++) { - m_common_layout->addWidget(CreateAchievementBox(game_data->achievements + ix)); - } -} - -QString AchievementProgressWidget::GetStatusString(u32 achievement_id) const -{ - const auto unlock_status = AchievementManager::GetInstance().GetUnlockStatus(achievement_id); - if (unlock_status.session_unlock_count > 0) - { - if (Config::Get(Config::RA_ENCORE_ENABLED)) + for (u32 jx = 0; jx < achievement_list->buckets[ix].num_achievements; jx++) { - return tr("Unlocked %1 times this session").arg(unlock_status.session_unlock_count); + m_common_layout->addWidget( + new AchievementBox(this, achievement_list->buckets[ix].achievements[jx])); } - return tr("Unlocked this session"); } - switch (unlock_status.remote_unlock_status) - { - case AchievementManager::UnlockStatus::UnlockType::LOCKED: - return tr("Locked"); - case AchievementManager::UnlockStatus::UnlockType::SOFTCORE: - return tr("Unlocked (Casual)"); - case AchievementManager::UnlockStatus::UnlockType::HARDCORE: - return tr("Unlocked"); - } - return {}; } #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.h b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.h index b1e09e40d8..32d2754fd3 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.h @@ -24,9 +24,6 @@ public: void UpdateData(); private: - QGroupBox* CreateAchievementBox(const rc_api_achievement_definition_t* achievement); - QString GetStatusString(u32 achievement_id) const; - QGroupBox* m_common_box; QVBoxLayout* m_common_layout; }; diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 87bd9883d5..dc946b5b50 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -28,6 +28,8 @@ add_executable(dolphin-emu CheatSearchWidget.h CheatsManager.cpp CheatsManager.h + Achievements/AchievementBox.cpp + Achievements/AchievementBox.h Achievements/AchievementHeaderWidget.cpp Achievements/AchievementHeaderWidget.h Achievements/AchievementLeaderboardWidget.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index cbcd07bfad..9dd72e3622 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -48,6 +48,7 @@ + @@ -267,6 +268,7 @@ + From d2069e888d75abc51fa23e9dd5d43f405e48ce33 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 9 Mar 2024 21:34:20 -0500 Subject: [PATCH 21/31] Refactored AchievementProgressWidget to maintain AchievementBox list AchievementProgressWidget maintains in memory a map of AchievementBox pointers so that UpdateData can operate on them individually. UpdateData is overhauled for three options: UpdateData(true) will destroy the entire list and re-create it from scratch as before, to be used if the game or player changes/closes/logs out. UpdateData(false) will loop through the map and call UpdateData on every achievement box, to be used for certain settings changes such as enabling badges or disabling hardcore mode. UpdateData(set) will call UpdateData on only the IDs in the set, to be used when achievements are unlocked. --- .../AchievementProgressWidget.cpp | 59 +++++++++++++------ .../Achievements/AchievementProgressWidget.h | 6 +- .../Achievements/AchievementsWindow.cpp | 2 +- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp index bfa008db5a..548bd5b9c1 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp @@ -27,10 +27,7 @@ AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget( m_common_box = new QGroupBox(); m_common_layout = new QVBoxLayout(); - { - std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; - UpdateData(); - } + UpdateData(true); m_common_box->setLayout(m_common_layout); @@ -41,24 +38,48 @@ AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget( setLayout(layout); } -void AchievementProgressWidget::UpdateData() +void AchievementProgressWidget::UpdateData(bool clean_all) { - ClearLayoutRecursively(m_common_layout); - - auto& instance = AchievementManager::GetInstance(); - if (!instance.IsGameLoaded()) - return; - - auto* client = instance.GetClient(); - auto* achievement_list = - rc_client_create_achievement_list(client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, - RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE); - for (u32 ix = 0; ix < achievement_list->num_buckets; ix++) + if (clean_all) { - for (u32 jx = 0; jx < achievement_list->buckets[ix].num_achievements; jx++) + m_achievement_boxes.clear(); + ClearLayoutRecursively(m_common_layout); + + auto& instance = AchievementManager::GetInstance(); + if (!instance.IsGameLoaded()) + return; + auto* client = instance.GetClient(); + auto* achievement_list = rc_client_create_achievement_list( + client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE); + for (u32 ix = 0; ix < achievement_list->num_buckets; ix++) { - m_common_layout->addWidget( - new AchievementBox(this, achievement_list->buckets[ix].achievements[jx])); + for (u32 jx = 0; jx < achievement_list->buckets[ix].num_achievements; jx++) + { + auto* achievement = achievement_list->buckets[ix].achievements[jx]; + m_achievement_boxes[achievement->id] = std::make_shared(this, achievement); + m_common_layout->addWidget(m_achievement_boxes[achievement->id].get()); + } + } + rc_client_destroy_achievement_list(achievement_list); + } + else + { + for (auto box : m_achievement_boxes) + { + box.second->UpdateData(); + } + } +} + +void AchievementProgressWidget::UpdateData( + const std::set& update_ids) +{ + for (auto& [id, box] : m_achievement_boxes) + { + if (update_ids.contains(id)) + { + box->UpdateData(); } } } diff --git a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.h b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.h index 32d2754fd3..9aa2f8cfe7 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.h @@ -7,7 +7,9 @@ #include #include "Common/CommonTypes.h" +#include "Core/AchievementManager.h" +class AchievementBox; class QCheckBox; class QGroupBox; class QLineEdit; @@ -21,11 +23,13 @@ class AchievementProgressWidget final : public QWidget Q_OBJECT public: explicit AchievementProgressWidget(QWidget* parent); - void UpdateData(); + void UpdateData(bool clean_all); + void UpdateData(const std::set& update_ids); private: QGroupBox* m_common_box; QVBoxLayout* m_common_layout; + std::map> m_achievement_boxes; }; #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp index f8dcda0a25..a95e844e29 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp @@ -81,7 +81,7 @@ void AchievementsWindow::UpdateData() m_header_widget->UpdateData(); m_header_widget->setVisible(instance.HasAPIToken()); m_settings_widget->UpdateData(); - m_progress_widget->UpdateData(); + m_progress_widget->UpdateData(true); m_tab_widget->setTabVisible(1, is_game_loaded); m_leaderboard_widget->UpdateData(); m_tab_widget->setTabVisible(2, is_game_loaded); From c57be0efca6c3c5a8b0d9870b73fe4fa738d265b Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 9 Mar 2024 16:46:41 -0500 Subject: [PATCH 22/31] Refactor Leaderboard widget to allow partial updates Similarly to the Progress widget (though without the separate object for each box, because each Leaderboard unit is just three text fields stacked vertically), AchievementLeaderboardWidget.UpdateData will now accept three options: destroy all and rebuild, update all, or update a set of rows. As part of this, AchievementManager::GetLeaderboardsInfo has been refactored to GetLeaderboardInfo to return a single leaderboard for the ID passed in. --- Source/Core/Core/AchievementManager.cpp | 8 +- Source/Core/Core/AchievementManager.h | 2 +- .../AchievementLeaderboardWidget.cpp | 169 +++++++++++------- .../AchievementLeaderboardWidget.h | 7 +- .../Achievements/AchievementsWindow.cpp | 2 +- 5 files changed, 118 insertions(+), 70 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index a40df12104..f73072fa59 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -358,10 +358,12 @@ AchievementManager::GetAchievementProgress(AchievementId achievement_id, u32* va return ResponseType::SUCCESS; } -const std::unordered_map& -AchievementManager::GetLeaderboardsInfo() const +const AchievementManager::LeaderboardStatus* +AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderboard_id) const { - return m_leaderboard_map; + if (m_leaderboard_map.count(leaderboard_id) < 1) + return nullptr; + return &m_leaderboard_map.at(leaderboard_id); } AchievementManager::RichPresence AchievementManager::GetRichPresence() const diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 6cd7d39a90..785c463fbf 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -143,7 +143,7 @@ public: const UnlockStatus* GetUnlockStatus(AchievementId achievement_id) const; AchievementManager::ResponseType GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target); - const std::unordered_map& GetLeaderboardsInfo() const; + const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id) const; RichPresence GetRichPresence() const; bool IsDisabled() const { return m_disabled; }; void SetDisabled(bool disabled); diff --git a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp index 13ab205f9c..e9705caffa 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp @@ -24,10 +24,7 @@ AchievementLeaderboardWidget::AchievementLeaderboardWidget(QWidget* parent) : QW m_common_box = new QGroupBox(); m_common_layout = new QGridLayout(); - { - std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; - UpdateData(); - } + UpdateData(true); m_common_box->setLayout(m_common_layout); @@ -38,77 +35,121 @@ AchievementLeaderboardWidget::AchievementLeaderboardWidget(QWidget* parent) : QW setLayout(layout); } -void AchievementLeaderboardWidget::UpdateData() +void AchievementLeaderboardWidget::UpdateData(bool clean_all) { - ClearLayoutRecursively(m_common_layout); - - if (!AchievementManager::GetInstance().IsGameLoaded()) - return; - const auto& leaderboards = AchievementManager::GetInstance().GetLeaderboardsInfo(); - int row = 0; - for (const auto& board_row : leaderboards) + if (clean_all) { - const AchievementManager::LeaderboardStatus& board = board_row.second; - QLabel* a_title = new QLabel(QString::fromStdString(board.name)); - QLabel* a_description = new QLabel(QString::fromStdString(board.description)); - QVBoxLayout* a_col_left = new QVBoxLayout(); - a_col_left->addWidget(a_title); - a_col_left->addWidget(a_description); - if (row > 0) + ClearLayoutRecursively(m_common_layout); + + auto& instance = AchievementManager::GetInstance(); + if (!instance.IsGameLoaded()) + return; + + rc_api_fetch_game_data_response_t* game_data; { - QFrame* a_divider = new QFrame(); - a_divider->setFrameShape(QFrame::HLine); - m_common_layout->addWidget(a_divider, row - 1, 0); + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; + game_data = instance.GetGameData(); } - m_common_layout->addLayout(a_col_left, row, 0); - // Each leaderboard entry is displayed with four values. These are *generally* intended to be, - // in order, the first place entry, the entry one above the player, the player's entry, and - // the entry one below the player. - // Edge cases: - // * If there are fewer than four entries in the leaderboard, all entries will be shown in - // order and the remainder of the list will be padded with empty values. - // * If the player does not currently have a score in the leaderboard, or is in the top 3, - // the four slots will be the top four players in order. - // * If the player is last place, the player will be in the fourth slot, and the second and - // third slots will be the two players above them. The first slot will always be first place. - std::array to_display{1, 2, 3, 4}; - if (board.player_index > to_display.size() - 1) + for (u32 row = 0; row < game_data->num_leaderboards; row += 2) { - // If the rank one below than the player is found, offset = 1. - u32 offset = static_cast(board.entries.count(board.player_index + 1)); - // Example: player is 10th place but not last - // to_display = {1, 10-3+1+1, 10-3+1+2, 10-3+1+3} = {1, 9, 10, 11} - // Example: player is 15th place and is last - // to_display = {1, 15-3+0+1, 15-3+0+2, 15-3+0+3} = {1, 13, 14, 15} - for (size_t i = 1; i < to_display.size(); ++i) - to_display[i] = board.player_index - 3 + offset + static_cast(i); - } - for (size_t i = 0; i < to_display.size(); ++i) - { - u32 index = to_display[i]; - QLabel* a_rank = new QLabel(QStringLiteral("---")); - QLabel* a_username = new QLabel(QStringLiteral("---")); - QLabel* a_score = new QLabel(QStringLiteral("---")); - const auto it = board.entries.find(index); - if (it != board.entries.end()) - { - a_rank->setText(tr("Rank %1").arg(it->second.rank)); - a_username->setText(QString::fromStdString(it->second.username)); - a_score->setText(QString::fromUtf8(it->second.score.data())); - } - QVBoxLayout* a_col = new QVBoxLayout(); - a_col->addWidget(a_rank); - a_col->addWidget(a_username); - a_col->addWidget(a_score); + const auto* leaderboard = game_data->leaderboards + (row / 2); + m_leaderboard_order[leaderboard->id] = row; + QLabel* a_title = new QLabel(QString::fromUtf8(leaderboard->title)); + QLabel* a_description = new QLabel(QString::fromUtf8(leaderboard->description)); + QVBoxLayout* a_col_left = new QVBoxLayout(); + a_col_left->addWidget(a_title); + a_col_left->addWidget(a_description); if (row > 0) { QFrame* a_divider = new QFrame(); a_divider->setFrameShape(QFrame::HLine); - m_common_layout->addWidget(a_divider, row - 1, static_cast(i) + 1); + m_common_layout->addWidget(a_divider, row - 1, 0); + } + m_common_layout->addLayout(a_col_left, row, 0); + for (size_t ix = 0; ix < 4; ix++) + { + QVBoxLayout* a_col = new QVBoxLayout(); + for (size_t jx = 0; jx < 3; jx++) + a_col->addWidget(new QLabel(QStringLiteral("---"))); + if (row > 0) + { + QFrame* a_divider = new QFrame(); + a_divider->setFrameShape(QFrame::HLine); + m_common_layout->addWidget(a_divider, row - 1, static_cast(ix) + 1); + } + m_common_layout->addLayout(a_col, row, static_cast(ix) + 1); } - m_common_layout->addLayout(a_col, row, static_cast(i) + 1); } - row += 2; + } + for (auto row : m_leaderboard_order) + { + UpdateRow(row.second); + } +} + +void AchievementLeaderboardWidget::UpdateData( + const std::set& update_ids) +{ + for (auto row : m_leaderboard_order) + { + if (update_ids.contains(row.first)) + { + UpdateRow(row.second); + } + } +} + +void AchievementLeaderboardWidget::UpdateRow(AchievementManager::AchievementId leaderboard_id) +{ + const auto leaderboard_itr = m_leaderboard_order.find(leaderboard_id); + if (leaderboard_itr == m_leaderboard_order.end()) + return; + const int row = leaderboard_itr->second; + + const AchievementManager::LeaderboardStatus* board; + { + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; + board = AchievementManager::GetInstance().GetLeaderboardInfo(leaderboard_id); + } + if (!board) + return; + + // Each leaderboard entry is displayed with four values. These are *generally* intended to be, + // in order, the first place entry, the entry one above the player, the player's entry, and + // the entry one below the player. + // Edge cases: + // * If there are fewer than four entries in the leaderboard, all entries will be shown in + // order and the remainder of the list will be padded with empty values. + // * If the player does not currently have a score in the leaderboard, or is in the top 3, + // the four slots will be the top four players in order. + // * If the player is last place, the player will be in the fourth slot, and the second and + // third slots will be the two players above them. The first slot will always be first place. + std::array to_display{1, 2, 3, 4}; + if (board->player_index > to_display.size() - 1) + { + // If the rank one below than the player is found, offset = 1. + u32 offset = static_cast(board->entries.count(board->player_index + 1)); + // Example: player is 10th place but not last + // to_display = {1, 10-3+1+1, 10-3+1+2, 10-3+1+3} = {1, 9, 10, 11} + // Example: player is 15th place and is last + // to_display = {1, 15-3+0+1, 15-3+0+2, 15-3+0+3} = {1, 13, 14, 15} + for (size_t ix = 1; ix < to_display.size(); ++ix) + to_display[ix] = board->player_index - 3 + offset + static_cast(ix); + } + for (size_t ix = 0; ix < to_display.size(); ++ix) + { + const auto it = board->entries.find(to_display[ix]); + if (it != board->entries.end()) + { + QVBoxLayout* a_col = new QVBoxLayout(); + a_col->addWidget(new QLabel(tr("Rank %1").arg(it->second.rank))); + a_col->addWidget(new QLabel(QString::fromStdString(it->second.username))); + a_col->addWidget(new QLabel(QString::fromUtf8(it->second.score.data()))); + auto old_item = m_common_layout->itemAtPosition(row, static_cast(ix) + 1); + m_common_layout->removeItem(old_item); + ClearLayoutRecursively(static_cast(old_item)); + m_common_layout->addLayout(a_col, row, static_cast(ix) + 1); + } } } diff --git a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h index 055ea6ab3f..cafd2483bd 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.h @@ -6,6 +6,8 @@ #ifdef USE_RETRO_ACHIEVEMENTS #include +#include "Core/AchievementManager.h" + class QGroupBox; class QGridLayout; @@ -14,11 +16,14 @@ class AchievementLeaderboardWidget final : public QWidget Q_OBJECT public: explicit AchievementLeaderboardWidget(QWidget* parent); - void UpdateData(); + void UpdateData(bool clean_all); + void UpdateData(const std::set& update_ids); + void UpdateRow(AchievementManager::AchievementId leaderboard_id); private: QGroupBox* m_common_box; QGridLayout* m_common_layout; + std::map m_leaderboard_order; }; #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp index a95e844e29..6f26811a49 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp @@ -83,7 +83,7 @@ void AchievementsWindow::UpdateData() m_settings_widget->UpdateData(); m_progress_widget->UpdateData(true); m_tab_widget->setTabVisible(1, is_game_loaded); - m_leaderboard_widget->UpdateData(); + m_leaderboard_widget->UpdateData(true); m_tab_widget->setTabVisible(2, is_game_loaded); } update(); From fa2210f80df70e01d997a9dd9855c12b95dc61b9 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Wed, 3 Apr 2024 00:02:54 -0400 Subject: [PATCH 23/31] Refactor leaderboard entry calls to use rc_client Leaderboard entry calls are asyncronous and use a callback. Logic remains the same, but the callback populates the list that the UI grabs values from. --- Source/Core/Core/AchievementManager.cpp | 122 ++++++------------ Source/Core/Core/AchievementManager.h | 8 +- .../AchievementLeaderboardWidget.cpp | 57 ++++---- 3 files changed, 76 insertions(+), 111 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index f73072fa59..1c10b3b67e 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -359,11 +359,17 @@ AchievementManager::GetAchievementProgress(AchievementId achievement_id, u32* va } const AchievementManager::LeaderboardStatus* -AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderboard_id) const +AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderboard_id) { - if (m_leaderboard_map.count(leaderboard_id) < 1) - return nullptr; - return &m_leaderboard_map.at(leaderboard_id); + if (const auto leaderboard_iter = m_leaderboard_map.find(leaderboard_id); + leaderboard_iter != m_leaderboard_map.end()) + { + if (leaderboard_iter->second.entries.size() == 0) + FetchBoardInfo(leaderboard_id); + return &leaderboard_iter->second; + } + + return nullptr; } AchievementManager::RichPresence AchievementManager::GetRichPresence() const @@ -617,92 +623,39 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc AchievementManager::GetInstance().FetchPlayerBadge(); } -AchievementManager::ResponseType AchievementManager::FetchBoardInfo(AchievementId leaderboard_id) +void AchievementManager::FetchBoardInfo(AchievementId leaderboard_id) { - std::string username = Config::Get(Config::RA_USERNAME); - LeaderboardStatus lboard{}; + u32* callback_data_1 = new u32(leaderboard_id); + u32* callback_data_2 = new u32(leaderboard_id); + rc_client_begin_fetch_leaderboard_entries(m_client, leaderboard_id, 1, 4, + LeaderboardEntriesCallback, callback_data_1); + rc_client_begin_fetch_leaderboard_entries_around_user( + m_client, leaderboard_id, 4, LeaderboardEntriesCallback, callback_data_2); +} +void AchievementManager::LeaderboardEntriesCallback(int result, const char* error_message, + rc_client_leaderboard_entry_list_t* list, + rc_client_t* client, void* userdata) +{ + if (result != RC_OK) { - 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]; - auto 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.insert_or_assign(org_entry.index, std::move(dest_entry)); - } - rc_api_destroy_fetch_leaderboard_info_response(&board_info); + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to fetch leaderboard entries."); + return; } + u32 leaderboard_id = *reinterpret_cast(userdata); + delete userdata; + auto& leaderboard = AchievementManager::GetInstance().m_leaderboard_map[leaderboard_id]; + for (size_t ix = 0; ix < list->num_entries; ix++) { - // 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]; - auto 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.insert_or_assign(org_entry.index, std::move(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{AchievementManager::GetInstance().GetLock()}; + const auto& response_entry = list->entries[ix]; + auto& map_entry = leaderboard.entries[response_entry.index]; + map_entry.username.assign(response_entry.user); + memcpy(map_entry.score.data(), response_entry.display, FORMAT_SIZE); + map_entry.rank = response_entry.rank; } - - { - std::lock_guard lg{m_lock}; - m_leaderboard_map.insert_or_assign(leaderboard_id, std::move(lboard)); - } - - return ResponseType::SUCCESS; + AchievementManager::GetInstance().m_update_callback(); } void AchievementManager::GenerateRichPresence(const Core::CPUThreadGuard& guard) @@ -807,12 +760,14 @@ void AchievementManager::HandleLeaderboardStartedEvent(const rc_client_event_t* OSD::AddMessage(fmt::format("Attempting leaderboard: {} - {}", client_event->leaderboard->title, client_event->leaderboard->description), OSD::Duration::VERY_LONG, OSD::Color::GREEN); + AchievementManager::GetInstance().FetchBoardInfo(client_event->leaderboard->id); } void AchievementManager::HandleLeaderboardFailedEvent(const rc_client_event_t* client_event) { OSD::AddMessage(fmt::format("Failed leaderboard: {}", client_event->leaderboard->title), OSD::Duration::VERY_LONG, OSD::Color::RED); + AchievementManager::GetInstance().FetchBoardInfo(client_event->leaderboard->id); } void AchievementManager::HandleLeaderboardSubmittedEvent(const rc_client_event_t* client_event) @@ -821,6 +776,7 @@ void AchievementManager::HandleLeaderboardSubmittedEvent(const rc_client_event_t client_event->leaderboard->tracker_value, client_event->leaderboard->title), OSD::Duration::VERY_LONG, OSD::Color::YELLOW); + AchievementManager::GetInstance().FetchBoardInfo(client_event->leaderboard->id); } void AchievementManager::HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* client_event) diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 785c463fbf..9283401461 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -143,7 +143,7 @@ public: const UnlockStatus* GetUnlockStatus(AchievementId achievement_id) const; AchievementManager::ResponseType GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target); - const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id) const; + const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id); RichPresence GetRichPresence() const; bool IsDisabled() const { return m_disabled; }; void SetDisabled(bool disabled); @@ -177,7 +177,7 @@ private: static void LoginCallback(int result, const char* error_message, rc_client_t* client, void* userdata); - ResponseType FetchBoardInfo(AchievementId leaderboard_id); + void FetchBoardInfo(AchievementId leaderboard_id); std::unique_ptr& GetLoadingVolume() { return m_loading_volume; }; @@ -189,6 +189,10 @@ private: void* userdata); void DisplayWelcomeMessage(); + static void LeaderboardEntriesCallback(int result, const char* error_message, + rc_client_leaderboard_entry_list_t* list, + rc_client_t* client, void* userdata); + static void HandleAchievementTriggeredEvent(const rc_client_event_t* client_event); static void HandleLeaderboardStartedEvent(const rc_client_event_t* client_event); static void HandleLeaderboardFailedEvent(const rc_client_event_t* client_event); diff --git a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp index e9705caffa..6cb1892643 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp @@ -44,42 +44,47 @@ void AchievementLeaderboardWidget::UpdateData(bool clean_all) auto& instance = AchievementManager::GetInstance(); if (!instance.IsGameLoaded()) return; + auto* client = instance.GetClient(); + auto* leaderboard_list = + rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE); - rc_api_fetch_game_data_response_t* game_data; + u32 row = 0; + for (u32 bucket = 0; bucket < leaderboard_list->num_buckets; bucket++) { - std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; - game_data = instance.GetGameData(); - } - for (u32 row = 0; row < game_data->num_leaderboards; row += 2) - { - const auto* leaderboard = game_data->leaderboards + (row / 2); - m_leaderboard_order[leaderboard->id] = row; - QLabel* a_title = new QLabel(QString::fromUtf8(leaderboard->title)); - QLabel* a_description = new QLabel(QString::fromUtf8(leaderboard->description)); - QVBoxLayout* a_col_left = new QVBoxLayout(); - a_col_left->addWidget(a_title); - a_col_left->addWidget(a_description); - if (row > 0) + const auto& leaderboard_bucket = leaderboard_list->buckets[bucket]; + for (u32 board = 0; board < leaderboard_bucket.num_leaderboards; board++) { - QFrame* a_divider = new QFrame(); - a_divider->setFrameShape(QFrame::HLine); - m_common_layout->addWidget(a_divider, row - 1, 0); - } - m_common_layout->addLayout(a_col_left, row, 0); - for (size_t ix = 0; ix < 4; ix++) - { - QVBoxLayout* a_col = new QVBoxLayout(); - for (size_t jx = 0; jx < 3; jx++) - a_col->addWidget(new QLabel(QStringLiteral("---"))); + const auto* leaderboard = leaderboard_bucket.leaderboards[board]; + m_leaderboard_order[leaderboard->id] = row; + QLabel* a_title = new QLabel(QString::fromUtf8(leaderboard->title)); + QLabel* a_description = new QLabel(QString::fromUtf8(leaderboard->description)); + QVBoxLayout* a_col_left = new QVBoxLayout(); + a_col_left->addWidget(a_title); + a_col_left->addWidget(a_description); if (row > 0) { QFrame* a_divider = new QFrame(); a_divider->setFrameShape(QFrame::HLine); - m_common_layout->addWidget(a_divider, row - 1, static_cast(ix) + 1); + m_common_layout->addWidget(a_divider, row - 1, 0); } - m_common_layout->addLayout(a_col, row, static_cast(ix) + 1); + m_common_layout->addLayout(a_col_left, row, 0); + for (size_t ix = 0; ix < 4; ix++) + { + QVBoxLayout* a_col = new QVBoxLayout(); + for (size_t jx = 0; jx < 3; jx++) + a_col->addWidget(new QLabel(QStringLiteral("---"))); + if (row > 0) + { + QFrame* a_divider = new QFrame(); + a_divider->setFrameShape(QFrame::HLine); + m_common_layout->addWidget(a_divider, row - 1, static_cast(ix) + 1); + } + m_common_layout->addLayout(a_col, row, static_cast(ix) + 1); + } + row += 2; } } + rc_client_destroy_leaderboard_list(leaderboard_list); } for (auto row : m_leaderboard_order) { From 4214c301ef32d3e2fbff1a0747fa657a002522eb Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 9 Mar 2024 20:18:40 -0500 Subject: [PATCH 24/31] Refactor AchievementsWindow::UpdateData to take a partial update parameter UpdateData in AchievementsWindow now only updates the components being requested, massively improving the window's performance. The parameter is UpdatedItems in AchievementManager, which tracks which portions of the system have been updated for every update callback. --- Source/Core/Core/AchievementManager.cpp | 63 +++++++++++-------- Source/Core/Core/AchievementManager.h | 19 +++++- .../Achievements/AchievementHeaderWidget.cpp | 4 +- .../AchievementLeaderboardWidget.cpp | 2 - .../AchievementProgressWidget.cpp | 2 - .../Achievements/AchievementsWindow.cpp | 41 +++++++++--- .../Achievements/AchievementsWindow.h | 4 +- Source/Core/DolphinQt/MainWindow.cpp | 1 + 8 files changed, 88 insertions(+), 48 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 1c10b3b67e..2386cb25f8 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -61,9 +61,9 @@ void AchievementManager::SetUpdateCallback(UpdateCallback callback) m_update_callback = std::move(callback); if (!m_update_callback) - m_update_callback = [] {}; + m_update_callback = [](UpdatedItems) {}; - m_update_callback(); + m_update_callback(UpdatedItems{.all = true}); } void AchievementManager::Login(const std::string& password) @@ -147,22 +147,26 @@ bool AchievementManager::IsGameLoaded() const void AchievementManager::FetchPlayerBadge() { - FetchBadge(&m_player_badge, RC_IMAGE_TYPE_USER, [](const AchievementManager& manager) { - auto* user_info = rc_client_get_user_info(manager.m_client); - if (!user_info) - return std::string(""); - return std::string(user_info->display_name); - }); + FetchBadge(&m_player_badge, RC_IMAGE_TYPE_USER, + [](const AchievementManager& manager) { + auto* user_info = rc_client_get_user_info(manager.m_client); + if (!user_info) + return std::string(""); + return std::string(user_info->display_name); + }, + {.player_icon = true}); } void AchievementManager::FetchGameBadges() { - FetchBadge(&m_game_badge, RC_IMAGE_TYPE_GAME, [](const AchievementManager& manager) { - auto* game_info = rc_client_get_game_info(manager.m_client); - if (!game_info) - return std::string(""); - return std::string(game_info->badge_name); - }); + FetchBadge(&m_game_badge, RC_IMAGE_TYPE_GAME, + [](const AchievementManager& manager) { + auto* game_info = rc_client_get_game_info(manager.m_client); + if (!game_info) + return std::string(""); + return std::string(game_info->badge_name); + }, + {.game_icon = true}); if (!rc_client_has_achievements(m_client)) return; @@ -188,7 +192,8 @@ void AchievementManager::FetchGameBadges() return std::string(""); return std::string( rc_client_get_achievement_info(manager.m_client, achievement_id)->badge_name); - }); + }, + {.achievements = {achievement_id}}); FetchBadge( &m_locked_badges[achievement_id], RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, [achievement_id](const AchievementManager& manager) { @@ -196,7 +201,8 @@ void AchievementManager::FetchGameBadges() return std::string(""); return std::string( rc_client_get_achievement_info(manager.m_client, achievement_id)->badge_name); - }); + }, + {.achievements = {achievement_id}}); } } rc_client_destroy_achievement_list(achievement_list); @@ -226,7 +232,7 @@ void AchievementManager::DoFrame() GenerateRichPresence(Core::CPUThreadGuard{*m_system}); m_queue.EmplaceItem([this] { PingRichPresence(m_rich_presence); }); m_last_ping_time = current_time; - m_update_callback(); + m_update_callback(UpdatedItems{.rich_presence = true}); } } @@ -394,13 +400,13 @@ void AchievementManager::SetDisabled(bool disable) INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager has been disabled."); OSD::AddMessage("Please close all games to re-enable achievements.", OSD::Duration::VERY_LONG, OSD::Color::RED); - m_update_callback(); + m_update_callback(UpdatedItems{.all = true}); } if (previously_disabled && !disable) { INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager has been re-enabled."); - m_update_callback(); + m_update_callback(UpdatedItems{.all = true}); } }; @@ -481,7 +487,7 @@ void AchievementManager::CloseGame() } } - m_update_callback(); + m_update_callback(UpdatedItems{.all = true}); INFO_LOG_FMT(ACHIEVEMENTS, "Game closed."); } @@ -495,7 +501,7 @@ void AchievementManager::Logout() Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); } - m_update_callback(); + m_update_callback(UpdatedItems{.all = true}); INFO_LOG_FMT(ACHIEVEMENTS, "Logged out from server."); } @@ -655,7 +661,7 @@ void AchievementManager::LeaderboardEntriesCallback(int result, const char* erro memcpy(map_entry.score.data(), response_entry.display, FORMAT_SIZE); map_entry.rank = response_entry.rank; } - AchievementManager::GetInstance().m_update_callback(); + AchievementManager::GetInstance().m_update_callback({.leaderboards = {leaderboard_id}}); } void AchievementManager::GenerateRichPresence(const Core::CPUThreadGuard& guard) @@ -701,6 +707,7 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message, AchievementManager::GetInstance().FetchGameBadges(); AchievementManager::GetInstance().m_system = &Core::System::GetInstance(); + AchievementManager::GetInstance().m_update_callback({.all = true}); } void AchievementManager::DisplayWelcomeMessage() @@ -987,15 +994,17 @@ u32 AchievementManager::MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_ } void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32 badge_type, - const AchievementManager::BadgeNameFunction function) + const AchievementManager::BadgeNameFunction function, + const UpdatedItems callback_data) { if (!m_client || !HasAPIToken() || !Config::Get(Config::RA_BADGES_ENABLED)) { - m_update_callback(); + m_update_callback(callback_data); return; } - m_image_queue.EmplaceItem([this, badge, badge_type, function = std::move(function)] { + m_image_queue.EmplaceItem([this, badge, badge_type, function = std::move(function), + callback_data = std::move(callback_data)] { std::string name_to_fetch; { std::lock_guard lg{m_lock}; @@ -1019,7 +1028,7 @@ void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32 WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed on image request.\n URL: {}", api_request.url); rc_api_destroy_request(&api_request); - m_update_callback(); + m_update_callback(callback_data); return; } @@ -1036,7 +1045,7 @@ void AchievementManager::FetchBadge(AchievementManager::BadgeStatus* badge, u32 badge->badge = std::move(fetched_badge); badge->name = std::move(name_to_fetch); - m_update_callback(); + m_update_callback(callback_data); }); } diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 9283401461..da816df6a5 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -50,7 +50,6 @@ public: UNKNOWN_FAILURE }; using ResponseCallback = std::function; - using UpdateCallback = std::function; using BadgeNameFunction = std::function; struct PointSpread @@ -116,6 +115,19 @@ public: std::unordered_map entries; }; + struct UpdatedItems + { + bool all = false; + bool player_icon = false; + bool game_icon = false; + bool all_achievements = false; + std::set achievements{}; + bool all_leaderboards = false; + std::set leaderboards{}; + bool rich_presence = false; + }; + using UpdateCallback = std::function; + static AchievementManager& GetInstance(); void Init(); void SetUpdateCallback(UpdateCallback callback); @@ -213,14 +225,15 @@ private: static void RequestV2(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); static u32 MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); - void FetchBadge(BadgeStatus* badge, u32 badge_type, const BadgeNameFunction function); + void FetchBadge(BadgeStatus* badge, u32 badge_type, const BadgeNameFunction function, + const UpdatedItems callback_data); static void EventHandler(const rc_client_event_t* event, rc_client_t* client); rc_runtime_t m_runtime{}; rc_client_t* m_client{}; Core::System* m_system{}; bool m_is_runtime_initialized = false; - UpdateCallback m_update_callback = [] {}; + UpdateCallback m_update_callback = [](const UpdatedItems&) {}; std::unique_ptr m_loading_volume; bool m_disabled = false; BadgeStatus m_player_badge; diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index bf151a77a1..5ae4f72676 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -62,13 +62,11 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare m_total->setContentsMargins(0, 0, 0, 0); m_total->setAlignment(Qt::AlignTop); setLayout(m_total); - - std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; - UpdateData(); } void AchievementHeaderWidget::UpdateData() { + std::lock_guard lg{AchievementManager::GetInstance().GetLock()}; auto& instance = AchievementManager::GetInstance(); if (!instance.HasAPIToken()) { diff --git a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp index 6cb1892643..d2a52970f8 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementLeaderboardWidget.cpp @@ -24,8 +24,6 @@ AchievementLeaderboardWidget::AchievementLeaderboardWidget(QWidget* parent) : QW m_common_box = new QGroupBox(); m_common_layout = new QGridLayout(); - UpdateData(true); - m_common_box->setLayout(m_common_layout); auto* layout = new QVBoxLayout; diff --git a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp index 548bd5b9c1..0164ee6a78 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp @@ -27,8 +27,6 @@ AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget( m_common_box = new QGroupBox(); m_common_layout = new QVBoxLayout(); - UpdateData(true); - m_common_box->setLayout(m_common_layout); auto* layout = new QVBoxLayout; diff --git a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp index 6f26811a49..b796b9dc3e 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementsWindow.cpp @@ -28,11 +28,13 @@ AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent) CreateMainLayout(); ConnectWidgets(); AchievementManager::GetInstance().SetUpdateCallback( - [this] { QueueOnObject(this, &AchievementsWindow::UpdateData); }); + [this](AchievementManager::UpdatedItems updated_items) { + QueueOnObject(this, [this, updated_items = std::move(updated_items)] { + AchievementsWindow::UpdateData(std::move(updated_items)); + }); + }); connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, - &AchievementsWindow::UpdateData); - - UpdateData(); + [this] { AchievementsWindow::UpdateData({.all = true}); }); } void AchievementsWindow::showEvent(QShowEvent* event) @@ -71,19 +73,38 @@ void AchievementsWindow::ConnectWidgets() connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); } -void AchievementsWindow::UpdateData() +void AchievementsWindow::UpdateData(AchievementManager::UpdatedItems updated_items) { + m_settings_widget->UpdateData(); + if (updated_items.all) + { + m_header_widget->UpdateData(); + m_progress_widget->UpdateData(true); + m_leaderboard_widget->UpdateData(true); + } + else + { + if (updated_items.player_icon || updated_items.game_icon || updated_items.rich_presence || + updated_items.all_achievements || updated_items.achievements.size() > 0) + { + m_header_widget->UpdateData(); + } + if (updated_items.all_achievements) + m_progress_widget->UpdateData(false); + else if (updated_items.achievements.size() > 0) + m_progress_widget->UpdateData(updated_items.achievements); + if (updated_items.all_leaderboards) + m_leaderboard_widget->UpdateData(false); + else if (updated_items.leaderboards.size() > 0) + m_leaderboard_widget->UpdateData(updated_items.leaderboards); + } + { auto& instance = AchievementManager::GetInstance(); std::lock_guard lg{instance.GetLock()}; const bool is_game_loaded = instance.IsGameLoaded(); - - m_header_widget->UpdateData(); m_header_widget->setVisible(instance.HasAPIToken()); - m_settings_widget->UpdateData(); - m_progress_widget->UpdateData(true); m_tab_widget->setTabVisible(1, is_game_loaded); - m_leaderboard_widget->UpdateData(true); m_tab_widget->setTabVisible(2, is_game_loaded); } update(); diff --git a/Source/Core/DolphinQt/Achievements/AchievementsWindow.h b/Source/Core/DolphinQt/Achievements/AchievementsWindow.h index 751749fbc3..3012707b3d 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementsWindow.h +++ b/Source/Core/DolphinQt/Achievements/AchievementsWindow.h @@ -6,6 +6,8 @@ #ifdef USE_RETRO_ACHIEVEMENTS #include +#include "Core/AchievementManager.h" + class AchievementHeaderWidget; class AchievementLeaderboardWidget; class AchievementSettingsWidget; @@ -19,7 +21,7 @@ class AchievementsWindow : public QDialog Q_OBJECT public: explicit AchievementsWindow(QWidget* parent); - void UpdateData(); + void UpdateData(AchievementManager::UpdatedItems updated_items); void ForceSettingsTab(); private: diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index aacf4f933b..f1c6951595 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -2005,6 +2005,7 @@ void MainWindow::ShowAchievementsWindow() m_achievements_window->show(); m_achievements_window->raise(); m_achievements_window->activateWindow(); + m_achievements_window->UpdateData(AchievementManager::UpdatedItems{.all = true}); } void MainWindow::ShowAchievementSettings() From 878e6e847b09d373bb24220a7ffdbd3a00b1290e Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Fri, 5 Apr 2024 22:46:40 -0400 Subject: [PATCH 25/31] Update Achievement Dialog Header to use rc_client Two portions of this need updating. Anything related to points and unlock counts and scoring uses game_summary now instead of the TallyScore method. Unfortunately this comes with the drawback that I cannot easily at this time access the number of points/unlocks from the other hardcore mode, so things like the second progress bar have been deleted. Rich presence, which no longer needs to be stored, but can be calculated at request. As the AchievementHeader can now update just the Rich Presence, DoFrame can now simply call a header update with .rp=true and the current Rich Presence will be calculated immediately. As the two items above are the last remaining things to use a number of the components in AchievementManager, this also deletes: Request V1 (V2 is renamed accordingly), ResponseType, PointSpread, TallyScore, UnlockStatus, and the RP generation and ping methods. --- Source/Core/Core/AchievementManager.cpp | 155 ++---------------- Source/Core/Core/AchievementManager.h | 59 +------ .../Achievements/AchievementHeaderWidget.cpp | 105 +++++------- .../Achievements/AchievementHeaderWidget.h | 6 +- 4 files changed, 52 insertions(+), 273 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 2386cb25f8..ab6482f13c 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -36,7 +36,7 @@ void AchievementManager::Init() { if (!m_client && Config::Get(Config::RA_ENABLED)) { - m_client = rc_client_create(MemoryPeeker, RequestV2); + m_client = rc_client_create(MemoryPeeker, Request); std::string host_url = Config::Get(Config::RA_HOST_URL); if (!host_url.empty()) rc_client_set_host(m_client, host_url.c_str()); @@ -226,12 +226,11 @@ void AchievementManager::DoFrame() } if (!m_system) return; - time_t current_time = std::time(nullptr); - if (difftime(current_time, m_last_ping_time) > 120) + auto current_time = std::chrono::steady_clock::now(); + if (current_time - m_last_rp_time > std::chrono::seconds{10}) { - GenerateRichPresence(Core::CPUThreadGuard{*m_system}); - m_queue.EmplaceItem([this] { PingRichPresence(m_rich_presence); }); - m_last_ping_time = current_time; + m_last_rp_time = current_time; + rc_client_get_rich_presence_message(m_client, m_rich_presence.data(), RP_SIZE); m_update_callback(UpdatedItems{.rich_presence = true}); } } @@ -283,35 +282,6 @@ std::string_view AchievementManager::GetGameDisplayName() const return IsGameLoaded() ? std::string_view(rc_client_get_game_info(m_client)->title) : ""; } -AchievementManager::PointSpread AchievementManager::TallyScore() const -{ - PointSpread spread{}; - if (!IsGameLoaded()) - return spread; - bool hardcore_mode_enabled = Config::Get(Config::RA_HARDCORE_ENABLED); - for (const auto& entry : m_unlock_map) - { - if (entry.second.category != RC_ACHIEVEMENT_CATEGORY_CORE) - continue; - 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; -} - rc_client_t* AchievementManager::GetClient() { return m_client; @@ -335,35 +305,6 @@ const AchievementManager::BadgeStatus& AchievementManager::GetAchievementBadge(A return (itr == badge_list.end()) ? m_default_badge : itr->second; } -const AchievementManager::UnlockStatus* -AchievementManager::GetUnlockStatus(AchievementId achievement_id) const -{ - if (m_unlock_map.count(achievement_id) < 1) - return nullptr; - return &m_unlock_map.at(achievement_id); -} - -AchievementManager::ResponseType -AchievementManager::GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target) -{ - if (!IsGameLoaded()) - { - ERROR_LOG_FMT( - ACHIEVEMENTS, - "Attempted to request measured data for achievement ID {} when no game is running.", - achievement_id); - return ResponseType::INVALID_REQUEST; - } - int result = rc_runtime_get_achievement_measured(&m_runtime, achievement_id, value, target); - if (result == 0) - { - WARN_LOG_FMT(ACHIEVEMENTS, "Failed to get measured data for achievement ID {}.", - achievement_id); - return ResponseType::MALFORMED_OBJECT; - } - return ResponseType::SUCCESS; -} - const AchievementManager::LeaderboardStatus* AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderboard_id) { @@ -380,7 +321,6 @@ AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderb AchievementManager::RichPresence AchievementManager::GetRichPresence() const { - std::lock_guard lg{m_lock}; return m_rich_presence; } @@ -476,7 +416,6 @@ void AchievementManager::CloseGame() m_game_badge.name.clear(); m_unlocked_badges.clear(); m_locked_badges.clear(); - m_unlock_map.clear(); m_leaderboard_map.clear(); rc_api_destroy_fetch_game_data_response(&m_game_data); m_game_data = {}; @@ -664,30 +603,6 @@ void AchievementManager::LeaderboardEntriesCallback(int result, const char* erro AchievementManager::GetInstance().m_update_callback({.leaderboards = {leaderboard_id}}); } -void AchievementManager::GenerateRichPresence(const Core::CPUThreadGuard& guard) -{ - std::lock_guard lg{m_lock}; - rc_runtime_get_richpresence( - &m_runtime, m_rich_presence.data(), RP_SIZE, - [](unsigned address, unsigned num_bytes, void* ud) { return 0u; }, this, nullptr); -} - -AchievementManager::ResponseType -AchievementManager::PingRichPresence(const RichPresence& rich_presence) -{ - std::string username = Config::Get(Config::RA_USERNAME); - std::string api_token = Config::Get(Config::RA_API_TOKEN); - rc_api_ping_request_t ping_request = {.username = username.c_str(), - .api_token = api_token.c_str(), - .game_id = m_game_id, - .rich_presence = rich_presence.data()}; - rc_api_ping_response_t ping_response = {}; - ResponseType r_type = Request( - ping_request, &ping_response, rc_api_init_ping_request, rc_api_process_ping_response); - rc_api_destroy_ping_response(&ping_response); - return r_type; -} - void AchievementManager::LoadGameCallback(int result, const char* error_message, rc_client_t* client, void* userdata) { @@ -708,6 +623,9 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message, AchievementManager::GetInstance().FetchGameBadges(); AchievementManager::GetInstance().m_system = &Core::System::GetInstance(); AchievementManager::GetInstance().m_update_callback({.all = true}); + // Set this to a value that will immediately trigger RP + AchievementManager::GetInstance().m_last_rp_time = + std::chrono::steady_clock::now() - std::chrono::minutes{2}; } void AchievementManager::DisplayWelcomeMessage() @@ -867,57 +785,6 @@ void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* clien nullptr); } -// 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 -// Call rc_api_init_X_request to convert this into a generic rc_api_request_t struct -// Perform the HTTP request using the url and post_data in the rc_api_request_t struct -// Call rc_api_process_X_response to convert the raw string HTTP response into a -// rc_api_X_response_t struct -// Use the data in the rc_api_X_response_t struct as needed -// Call rc_api_destroy_X_response when finished with the response struct to free memory -template -AchievementManager::ResponseType AchievementManager::Request( - RcRequest rc_request, RcResponse* rc_response, - const std::function& init_request, - const std::function& process_response) -{ - rc_api_request_t api_request; - Common::HttpRequest http_request; - if (init_request(&api_request, &rc_request) != RC_OK || !api_request.post_data) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid API request."); - return ResponseType::INVALID_REQUEST; - } - auto http_response = http_request.Post(api_request.url, api_request.post_data); - rc_api_destroy_request(&api_request); - if (http_response.has_value() && http_response->size() > 0) - { - const std::string response_str(http_response->begin(), http_response->end()); - if (process_response(rc_response, response_str.c_str()) != RC_OK) - { - ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to process HTTP response. \nURL: {} \nresponse: {}", - api_request.url, response_str); - return ResponseType::MALFORMED_OBJECT; - } - if (rc_response->response.succeeded) - { - return ResponseType::SUCCESS; - } - else - { - Logout(); - WARN_LOG_FMT(ACHIEVEMENTS, "Invalid RetroAchievements credentials; failed login."); - return ResponseType::INVALID_CREDENTIALS; - } - } - else - { - WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed. \nURL: {}", api_request.url); - return ResponseType::CONNECTION_FAILED; - } -} - static std::unique_ptr DecodeBadgeToOSDIcon(const AchievementManager::Badge& badge) { if (badge.empty()) @@ -932,9 +799,9 @@ static std::unique_ptr DecodeBadgeToOSDIcon(const AchievementManager: return icon; } -void AchievementManager::RequestV2(const rc_api_request_t* request, - rc_client_server_callback_t callback, void* callback_data, - rc_client_t* client) +void AchievementManager::Request(const rc_api_request_t* request, + rc_client_server_callback_t callback, void* callback_data, + rc_client_t* client) { std::string url = request->url; std::string post_data = request->post_data; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index da816df6a5..e814f1a32b 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -37,31 +37,8 @@ struct Icon; class AchievementManager { public: - enum class ResponseType - { - SUCCESS, - NOT_ENABLED, - MANAGER_NOT_INITIALIZED, - INVALID_REQUEST, - INVALID_CREDENTIALS, - CONNECTION_FAILED, - MALFORMED_OBJECT, - EXPIRED_CONTEXT, - UNKNOWN_FAILURE - }; - using ResponseCallback = std::function; using BadgeNameFunction = std::function; - struct PointSpread - { - u32 total_count; - u32 total_points; - u32 hard_unlocks; - u32 hard_points; - u32 soft_unlocks; - u32 soft_points; - }; - static constexpr size_t HASH_SIZE = 33; using Hash = std::array; using AchievementId = u32; @@ -80,22 +57,6 @@ public: Badge badge{}; }; - struct UnlockStatus - { - AchievementId game_data_index = 0; - enum class UnlockType - { - LOCKED, - SOFTCORE, - HARDCORE - } remote_unlock_status = UnlockType::LOCKED; - u32 session_unlock_count = 0; - u32 points = 0; - BadgeStatus locked_badge; - BadgeStatus unlocked_badge; - u32 category = RC_ACHIEVEMENT_CATEGORY_CORE; - }; - static constexpr std::string_view GRAY = "transparent"; static constexpr std::string_view GOLD = "#FFD700"; static constexpr std::string_view BLUE = "#0B71C1"; @@ -147,14 +108,10 @@ public: u32 GetPlayerScore() const; const BadgeStatus& GetPlayerBadge() const; std::string_view GetGameDisplayName() const; - PointSpread TallyScore() const; rc_client_t* GetClient(); rc_api_fetch_game_data_response_t* GetGameData(); const BadgeStatus& GetGameBadge() const; const BadgeStatus& GetAchievementBadge(AchievementId id, bool locked) const; - const UnlockStatus* GetUnlockStatus(AchievementId achievement_id) const; - AchievementManager::ResponseType GetAchievementProgress(AchievementId achievement_id, u32* value, - u32* target); const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id); RichPresence GetRichPresence() const; bool IsDisabled() const { return m_disabled; }; @@ -193,10 +150,6 @@ private: std::unique_ptr& GetLoadingVolume() { return m_loading_volume; }; - void GenerateRichPresence(const Core::CPUThreadGuard& guard); - - ResponseType PingRichPresence(const RichPresence& rich_presence); - static void LoadGameCallback(int result, const char* error_message, rc_client_t* client, void* userdata); void DisplayWelcomeMessage(); @@ -217,13 +170,8 @@ private: static void HandleAchievementProgressIndicatorShowEvent(const rc_client_event_t* client_event); static void HandleGameCompletedEvent(const rc_client_event_t* client_event, rc_client_t* client); - template - ResponseType Request(RcRequest rc_request, RcResponse* rc_response, - const std::function& init_request, - const std::function& process_response); - - static void RequestV2(const rc_api_request_t* request, rc_client_server_callback_t callback, - void* callback_data, rc_client_t* client); + static void Request(const rc_api_request_t* request, rc_client_server_callback_t callback, + void* callback_data, rc_client_t* client); static u32 MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client); void FetchBadge(BadgeStatus* badge, u32 badge_type, const BadgeNameFunction function, const UpdatedItems callback_data); @@ -246,9 +194,8 @@ private: std::unordered_map m_unlocked_badges; std::unordered_map m_locked_badges; RichPresence m_rich_presence; - time_t m_last_ping_time = 0; + std::chrono::steady_clock::time_point m_last_rp_time = std::chrono::steady_clock::now(); - std::unordered_map m_unlock_map; std::unordered_map m_leaderboard_map; NamedIconMap m_active_challenges; std::vector m_active_leaderboards; diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index 5ae4f72676..a1a0a1d7c0 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include "Core/AchievementManager.h" #include "Core/Config/AchievementSettings.h" #include "Core/Core.h" @@ -24,8 +26,7 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare m_game_icon = new QLabel(); m_name = new QLabel(); m_points = new QLabel(); - m_game_progress_hard = new QProgressBar(); - m_game_progress_soft = new QProgressBar(); + m_game_progress = new QProgressBar(); m_rich_presence = new QLabel(); m_locked_warning = new QLabel(); @@ -33,12 +34,9 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare "games to re-enable achievements.")); m_locked_warning->setStyleSheet(QStringLiteral("QLabel { color : red; }")); - QSizePolicy sp_retain = m_game_progress_hard->sizePolicy(); + QSizePolicy sp_retain = m_game_progress->sizePolicy(); sp_retain.setRetainSizeWhenHidden(true); - m_game_progress_hard->setSizePolicy(sp_retain); - sp_retain = m_game_progress_soft->sizePolicy(); - sp_retain.setRetainSizeWhenHidden(true); - m_game_progress_soft->setSizePolicy(sp_retain); + m_game_progress->setSizePolicy(sp_retain); QVBoxLayout* icon_col = new QVBoxLayout(); icon_col->addWidget(m_user_icon); @@ -46,8 +44,7 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare QVBoxLayout* text_col = new QVBoxLayout(); text_col->addWidget(m_name); text_col->addWidget(m_points); - text_col->addWidget(m_game_progress_hard); - text_col->addWidget(m_game_progress_soft); + text_col->addWidget(m_game_progress); text_col->addWidget(m_rich_presence); text_col->addWidget(m_locked_warning); QHBoxLayout* header_layout = new QHBoxLayout(); @@ -74,7 +71,6 @@ void AchievementHeaderWidget::UpdateData() return; } - AchievementManager::PointSpread point_spread = instance.TallyScore(); QString user_name = QtUtils::FromStdString(instance.GetPlayerDisplayName()); QString game_name = QtUtils::FromStdString(instance.GetGameDisplayName()); AchievementManager::BadgeStatus player_badge = instance.GetPlayerBadge(); @@ -83,27 +79,28 @@ void AchievementHeaderWidget::UpdateData() m_user_icon->setVisible(false); m_user_icon->clear(); m_user_icon->setText({}); - if (Config::Get(Config::RA_BADGES_ENABLED)) + if (Config::Get(Config::RA_BADGES_ENABLED) && !player_badge.name.empty()) { - if (!player_badge.name.empty()) + QImage i_user_icon{}; + if (i_user_icon.loadFromData(&player_badge.badge.front(), (int)player_badge.badge.size())) { - QImage i_user_icon{}; - if (i_user_icon.loadFromData(&player_badge.badge.front(), (int)player_badge.badge.size())) - { - m_user_icon->setPixmap(QPixmap::fromImage(i_user_icon) - .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - m_user_icon->adjustSize(); - m_user_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent")); - m_user_icon->setVisible(true); - } + m_user_icon->setPixmap(QPixmap::fromImage(i_user_icon) + .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + m_user_icon->adjustSize(); + m_user_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent")); + m_user_icon->setVisible(true); } } m_game_icon->setVisible(false); m_game_icon->clear(); m_game_icon->setText({}); - if (Config::Get(Config::RA_BADGES_ENABLED)) + + if (instance.IsGameLoaded()) { - if (!game_badge.name.empty()) + rc_client_user_game_summary_t game_summary; + rc_client_get_user_game_summary(instance.GetClient(), &game_summary); + + if (Config::Get(Config::RA_BADGES_ENABLED) && !game_badge.name.empty()) { QImage i_game_icon{}; if (i_game_icon.loadFromData(&game_badge.badge.front(), (int)game_badge.badge.size())) @@ -112,30 +109,29 @@ void AchievementHeaderWidget::UpdateData() .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); m_game_icon->adjustSize(); std::string_view color = AchievementManager::GRAY; - if (point_spread.hard_unlocks == point_spread.total_count) - color = AchievementManager::GOLD; - else if (point_spread.hard_unlocks + point_spread.soft_unlocks == point_spread.total_count) - color = AchievementManager::BLUE; + if (game_summary.num_core_achievements == game_summary.num_unlocked_achievements) + { + color = + instance.IsHardcoreModeActive() ? AchievementManager::GOLD : AchievementManager::BLUE; + } m_game_icon->setStyleSheet( - QStringLiteral("border: 4px solid %1").arg(QString::fromStdString(std::string(color)))); + QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color))); m_game_icon->setVisible(true); } } - } - if (!game_name.isEmpty()) - { m_name->setText(tr("%1 is playing %2").arg(user_name).arg(game_name)); - m_points->setText(GetPointsString(user_name, point_spread)); + m_points->setText(tr("%1 has unlocked %2/%3 achievements worth %4/%5 points") + .arg(user_name) + .arg(game_summary.num_unlocked_achievements) + .arg(game_summary.num_core_achievements) + .arg(game_summary.points_unlocked) + .arg(game_summary.points_core)); - m_game_progress_hard->setRange(0, point_spread.total_count); - if (!m_game_progress_hard->isVisible()) - m_game_progress_hard->setVisible(true); - m_game_progress_hard->setValue(point_spread.hard_unlocks); - m_game_progress_soft->setRange(0, point_spread.total_count); - m_game_progress_soft->setValue(point_spread.hard_unlocks + point_spread.soft_unlocks); - if (!m_game_progress_soft->isVisible()) - m_game_progress_soft->setVisible(true); + m_game_progress->setRange(0, game_summary.num_core_achievements); + if (!m_game_progress->isVisible()) + m_game_progress->setVisible(true); + m_game_progress->setValue(game_summary.num_unlocked_achievements); m_rich_presence->setText(QString::fromUtf8(instance.GetRichPresence().data())); if (!m_rich_presence->isVisible()) m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); @@ -146,37 +142,10 @@ void AchievementHeaderWidget::UpdateData() m_name->setText(user_name); m_points->setText(tr("%1 points").arg(instance.GetPlayerScore())); - m_game_progress_hard->setVisible(false); - m_game_progress_soft->setVisible(false); + m_game_progress->setVisible(false); m_rich_presence->setVisible(false); m_locked_warning->setVisible(instance.IsDisabled()); } } -QString -AchievementHeaderWidget::GetPointsString(const QString& user_name, - const AchievementManager::PointSpread& point_spread) const -{ - if (point_spread.soft_points > 0) - { - return tr("%1 has unlocked %2/%3 achievements (%4 hardcore) worth %5/%6 points (%7 hardcore)") - .arg(user_name) - .arg(point_spread.hard_unlocks + point_spread.soft_unlocks) - .arg(point_spread.total_count) - .arg(point_spread.hard_unlocks) - .arg(point_spread.hard_points + point_spread.soft_points) - .arg(point_spread.total_points) - .arg(point_spread.hard_points); - } - else - { - return tr("%1 has unlocked %2/%3 achievements worth %4/%5 points") - .arg(user_name) - .arg(point_spread.hard_unlocks) - .arg(point_spread.total_count) - .arg(point_spread.hard_points) - .arg(point_spread.total_points); - } -} - #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h index 7a644bd763..b246e9dbad 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h @@ -20,15 +20,11 @@ public: void UpdateData(); private: - QString GetPointsString(const QString& user_name, - const AchievementManager::PointSpread& point_spread) const; - QLabel* m_user_icon; QLabel* m_game_icon; QLabel* m_name; QLabel* m_points; - QProgressBar* m_game_progress_hard; - QProgressBar* m_game_progress_soft; + QProgressBar* m_game_progress; QLabel* m_rich_presence; QLabel* m_locked_warning; QGroupBox* m_header_box; From b05028da1f97ed55a21ac1f87077d003fa568efb Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 2 Apr 2024 15:38:59 -0400 Subject: [PATCH 26/31] Added Change Media client functionality to AchievementMananger The client can handle media changes natively so disabling can take place internally. This code uses the same external calls to load data, but will call either BeginLoad or BeginChangeMedia based on whether any media is already loaded. Due to the client's handling of media changes (it simply disables hardcore if an unknown media is detected) the existing functionality for "disabling" the achievements is no longer necessary and can be deleted. --- Source/Core/Core/AchievementManager.cpp | 65 ++++++++----------- Source/Core/Core/AchievementManager.h | 5 +- Source/Core/Core/BootManager.cpp | 2 +- Source/Core/Core/ConfigManager.cpp | 2 +- Source/Core/Core/Core.cpp | 1 - Source/Core/Core/IOS/ES/ES.cpp | 2 +- .../Achievements/AchievementHeaderWidget.cpp | 8 --- .../Achievements/AchievementHeaderWidget.h | 1 - 8 files changed, 33 insertions(+), 53 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index ab6482f13c..350a591389 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -110,13 +110,6 @@ void AchievementManager::LoadGame(const std::string& file_path, const DiscIO::Vo "Attempted to load game achievements without achievement client initialized."); return; } - if (m_disabled) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager is disabled until core is rebooted."); - OSD::AddMessage("Achievements are disabled until you restart emulation.", - OSD::Duration::VERY_LONG, OSD::Color::RED); - return; - } if (volume) { std::lock_guard lg{m_lock}; @@ -135,8 +128,15 @@ void AchievementManager::LoadGame(const std::string& file_path, const DiscIO::Vo .close = &AchievementManager::FilereaderClose, }; rc_hash_init_custom_filereader(&volume_reader); - rc_client_begin_identify_and_load_game(m_client, RC_CONSOLE_GAMECUBE, file_path.c_str(), NULL, 0, - LoadGameCallback, NULL); + if (rc_client_get_game_info(m_client)) + { + rc_client_begin_change_media(m_client, file_path.c_str(), NULL, 0, ChangeMediaCallback, NULL); + } + else + { + rc_client_begin_identify_and_load_game(m_client, RC_CONSOLE_GAMECUBE, file_path.c_str(), NULL, + 0, LoadGameCallback, NULL); + } } bool AchievementManager::IsGameLoaded() const @@ -324,32 +324,6 @@ AchievementManager::RichPresence AchievementManager::GetRichPresence() const return m_rich_presence; } -void AchievementManager::SetDisabled(bool disable) -{ - bool previously_disabled; - { - std::lock_guard lg{m_lock}; - previously_disabled = m_disabled; - m_disabled = disable; - if (disable && m_is_game_loaded) - CloseGame(); - } - - if (!previously_disabled && disable && Config::Get(Config::RA_ENABLED)) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager has been disabled."); - OSD::AddMessage("Please close all games to re-enable achievements.", OSD::Duration::VERY_LONG, - OSD::Color::RED); - m_update_callback(UpdatedItems{.all = true}); - } - - if (previously_disabled && !disable) - { - INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager has been re-enabled."); - m_update_callback(UpdatedItems{.all = true}); - } -}; - const AchievementManager::NamedIconMap& AchievementManager::GetChallengeIcons() const { return m_active_challenges; @@ -435,7 +409,6 @@ void AchievementManager::Logout() { std::lock_guard lg{m_lock}; CloseGame(); - SetDisabled(false); m_player_badge.name.clear(); Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); } @@ -449,7 +422,6 @@ void AchievementManager::Shutdown() if (m_client) { CloseGame(); - SetDisabled(false); m_queue.Shutdown(); // DON'T log out - keep those credentials for next run. rc_client_destroy(m_client); @@ -628,6 +600,25 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message, std::chrono::steady_clock::now() - std::chrono::minutes{2}; } +void AchievementManager::ChangeMediaCallback(int result, const char* error_message, + rc_client_t* client, void* userdata) +{ + if (result == RC_OK) + return; + + if (result == RC_HARDCORE_DISABLED) + { + WARN_LOG_FMT(ACHIEVEMENTS, "Hardcore disabled. Unrecognized media inserted."); + } + else + { + if (!error_message) + error_message = rc_error_str(result); + + ERROR_LOG_FMT(ACHIEVEMENTS, "RetroAchievements media change failed: {}", error_message); + } +} + void AchievementManager::DisplayWelcomeMessage() { std::lock_guard lg{m_lock}; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index e814f1a32b..289cb4158f 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -114,8 +114,6 @@ public: const BadgeStatus& GetAchievementBadge(AchievementId id, bool locked) const; const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id); RichPresence GetRichPresence() const; - bool IsDisabled() const { return m_disabled; }; - void SetDisabled(bool disabled); const NamedIconMap& GetChallengeIcons() const; std::vector GetActiveLeaderboards() const; @@ -152,6 +150,8 @@ private: static void LoadGameCallback(int result, const char* error_message, rc_client_t* client, void* userdata); + static void ChangeMediaCallback(int result, const char* error_message, rc_client_t* client, + void* userdata); void DisplayWelcomeMessage(); static void LeaderboardEntriesCallback(int result, const char* error_message, @@ -183,7 +183,6 @@ private: bool m_is_runtime_initialized = false; UpdateCallback m_update_callback = [](const UpdatedItems&) {}; std::unique_ptr m_loading_volume; - bool m_disabled = false; BadgeStatus m_player_badge; Hash m_game_hash{}; u32 m_game_id = 0; diff --git a/Source/Core/Core/BootManager.cpp b/Source/Core/Core/BootManager.cpp index 9e9fb3b58c..6f7681fd30 100644 --- a/Source/Core/Core/BootManager.cpp +++ b/Source/Core/Core/BootManager.cpp @@ -167,7 +167,7 @@ bool BootCore(Core::System& system, std::unique_ptr boot, } #ifdef USE_RETRO_ACHIEVEMENTS - AchievementManager::GetInstance().SetDisabled(false); + AchievementManager::GetInstance().CloseGame(); #endif // USE_RETRO_ACHIEVEMENTS const bool load_ipl = !system.IsWii() && !Config::Get(Config::MAIN_SKIP_IPL) && diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 4adb0a240b..c8f86ef70c 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -171,7 +171,7 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri #ifdef USE_RETRO_ACHIEVEMENTS if (game_id != "00000000") - AchievementManager::GetInstance().SetDisabled(true); + AchievementManager::GetInstance().CloseGame(); #endif // USE_RETRO_ACHIEVEMENTS if (game_id == "00000000") diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 0dc3db14ce..0498d1ba36 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -290,7 +290,6 @@ void Stop(Core::System& system) // - Hammertime! #ifdef USE_RETRO_ACHIEVEMENTS AchievementManager::GetInstance().CloseGame(); - AchievementManager::GetInstance().SetDisabled(false); #endif // USE_RETRO_ACHIEVEMENTS s_is_stopping = true; diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 216821cfbd..8eb35f3db9 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -481,7 +481,7 @@ bool ESDevice::LaunchPPCTitle(u64 title_id) #ifdef USE_RETRO_ACHIEVEMENTS INFO_LOG_FMT(ACHIEVEMENTS, "WAD and NAND formats not currently supported by Achievement Manager."); - AchievementManager::GetInstance().SetDisabled(true); + AchievementManager::GetInstance().CloseGame(); #endif // USE_RETRO_ACHIEVEMENTS core_timing.RemoveEvent(s_bootstrap_ppc_for_launch_event); diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index a1a0a1d7c0..6da62b478b 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -28,11 +28,6 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare m_points = new QLabel(); m_game_progress = new QProgressBar(); m_rich_presence = new QLabel(); - m_locked_warning = new QLabel(); - - m_locked_warning->setText(tr("Achievements have been disabled.
Please close all running " - "games to re-enable achievements.")); - m_locked_warning->setStyleSheet(QStringLiteral("QLabel { color : red; }")); QSizePolicy sp_retain = m_game_progress->sizePolicy(); sp_retain.setRetainSizeWhenHidden(true); @@ -46,7 +41,6 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare text_col->addWidget(m_points); text_col->addWidget(m_game_progress); text_col->addWidget(m_rich_presence); - text_col->addWidget(m_locked_warning); QHBoxLayout* header_layout = new QHBoxLayout(); header_layout->addLayout(icon_col); header_layout->addLayout(text_col); @@ -135,7 +129,6 @@ void AchievementHeaderWidget::UpdateData() m_rich_presence->setText(QString::fromUtf8(instance.GetRichPresence().data())); if (!m_rich_presence->isVisible()) m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); - m_locked_warning->setVisible(false); } else { @@ -144,7 +137,6 @@ void AchievementHeaderWidget::UpdateData() m_game_progress->setVisible(false); m_rich_presence->setVisible(false); - m_locked_warning->setVisible(instance.IsDisabled()); } } diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h index b246e9dbad..0964ef488f 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h @@ -26,7 +26,6 @@ private: QLabel* m_points; QProgressBar* m_game_progress; QLabel* m_rich_presence; - QLabel* m_locked_warning; QGroupBox* m_header_box; }; From 3bf5b8e610102d2db8b07e6821f7efea6ea5fc0a Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 2 Apr 2024 19:33:52 -0400 Subject: [PATCH 27/31] Handle Reset and Server Error client events. --- Source/Core/Core/AchievementManager.cpp | 18 ++++++++++++++++++ Source/Core/Core/AchievementManager.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 350a591389..32524b7469 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -776,6 +776,18 @@ void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* clien nullptr); } +void AchievementManager::HandleResetEvent(const rc_client_event_t* client_event) +{ + INFO_LOG_FMT(ACHIEVEMENTS, "Reset requested by Achievement Mananger"); + Core::Stop(Core::System::GetInstance()); +} + +void AchievementManager::HandleServerErrorEvent(const rc_client_event_t* client_event) +{ + ERROR_LOG_FMT(ACHIEVEMENTS, "RetroAchievements server error: {} {}", + client_event->server_error->api, client_event->server_error->error_message); +} + static std::unique_ptr DecodeBadgeToOSDIcon(const AchievementManager::Badge& badge) { if (badge.empty()) @@ -949,6 +961,12 @@ void AchievementManager::EventHandler(const rc_client_event_t* event, rc_client_ case RC_CLIENT_EVENT_GAME_COMPLETED: HandleGameCompletedEvent(event, client); break; + case RC_CLIENT_EVENT_RESET: + HandleResetEvent(event); + break; + case RC_CLIENT_EVENT_SERVER_ERROR: + HandleServerErrorEvent(event); + break; default: INFO_LOG_FMT(ACHIEVEMENTS, "Event triggered of unhandled type {}", event->type); break; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 289cb4158f..41f09f94b7 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -169,6 +169,8 @@ private: static void HandleAchievementChallengeIndicatorHideEvent(const rc_client_event_t* client_event); static void HandleAchievementProgressIndicatorShowEvent(const rc_client_event_t* client_event); static void HandleGameCompletedEvent(const rc_client_event_t* client_event, rc_client_t* client); + static void HandleResetEvent(const rc_client_event_t* client_event); + static void HandleServerErrorEvent(const rc_client_event_t* client_event); static void Request(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); From 9a40ec06b5e64fbfb8ade8a9a5a99500a1852ee9 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Wed, 3 Apr 2024 15:18:27 -0400 Subject: [PATCH 28/31] Remove Achievements/Leaderboards/RP Enabled settings The RetroAchievements client object now handles these three settings as a singular "spectator" mode that will be added in a future commit. --- Source/Core/Core/AchievementManager.cpp | 2 +- .../Core/Core/Config/AchievementSettings.cpp | 6 -- Source/Core/Core/Config/AchievementSettings.h | 3 - .../Achievements/AchievementHeaderWidget.cpp | 3 +- .../AchievementSettingsWidget.cpp | 60 +------------------ .../Achievements/AchievementSettingsWidget.h | 6 -- 6 files changed, 5 insertions(+), 75 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 32524b7469..a3b20abbfe 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -652,7 +652,7 @@ void AchievementManager::DisplayWelcomeMessage() fmt::format("Hardcore mode is {}", rc_client_get_hardcore_enabled(m_client) ? "ON" : "OFF"), OSD::Duration::VERY_LONG, color); OSD::AddMessage(fmt::format("Leaderboard submissions are {}", - Config::Get(Config::RA_LEADERBOARDS_ENABLED) ? "ON" : "OFF"), + rc_client_get_hardcore_enabled(m_client) ? "ON" : "OFF"), OSD::Duration::VERY_LONG, color); } diff --git a/Source/Core/Core/Config/AchievementSettings.cpp b/Source/Core/Core/Config/AchievementSettings.cpp index 52502b5c20..6a23e827e6 100644 --- a/Source/Core/Core/Config/AchievementSettings.cpp +++ b/Source/Core/Core/Config/AchievementSettings.cpp @@ -15,12 +15,6 @@ const Info RA_ENABLED{{System::Achievements, "Achievements", "Enabled"}, f const Info RA_HOST_URL{{System::Achievements, "Achievements", "HostUrl"}, ""}; const Info RA_USERNAME{{System::Achievements, "Achievements", "Username"}, ""}; const Info RA_API_TOKEN{{System::Achievements, "Achievements", "ApiToken"}, ""}; -const Info RA_ACHIEVEMENTS_ENABLED{ - {System::Achievements, "Achievements", "AchievementsEnabled"}, false}; -const Info RA_LEADERBOARDS_ENABLED{ - {System::Achievements, "Achievements", "LeaderboardsEnabled"}, false}; -const Info RA_RICH_PRESENCE_ENABLED{ - {System::Achievements, "Achievements", "RichPresenceEnabled"}, false}; const Info RA_HARDCORE_ENABLED{{System::Achievements, "Achievements", "HardcoreEnabled"}, false}; const Info RA_PROGRESS_ENABLED{{System::Achievements, "Achievements", "ProgressEnabled"}, diff --git a/Source/Core/Core/Config/AchievementSettings.h b/Source/Core/Core/Config/AchievementSettings.h index 2ee420372c..2b1f4163a3 100644 --- a/Source/Core/Core/Config/AchievementSettings.h +++ b/Source/Core/Core/Config/AchievementSettings.h @@ -14,9 +14,6 @@ extern const Info RA_ENABLED; extern const Info RA_USERNAME; extern const Info RA_HOST_URL; 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_HARDCORE_ENABLED; extern const Info RA_PROGRESS_ENABLED; extern const Info RA_BADGES_ENABLED; diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index 6da62b478b..42b5de602f 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -127,8 +127,7 @@ void AchievementHeaderWidget::UpdateData() m_game_progress->setVisible(true); m_game_progress->setValue(game_summary.num_unlocked_achievements); m_rich_presence->setText(QString::fromUtf8(instance.GetRichPresence().data())); - if (!m_rich_presence->isVisible()) - m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); + m_rich_presence->setVisible(true); } else { diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index ecaee3ff96..1c03be7f35 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -61,18 +61,6 @@ void AchievementSettingsWidget::CreateLayout() m_common_login_failed = new QLabel(tr("Login Failed")); m_common_login_failed->setStyleSheet(QStringLiteral("QLabel { color : red; }")); m_common_login_failed->setVisible(false); - m_common_achievements_enabled_input = new ToolTipCheckBox(tr("Enable Achievements")); - m_common_achievements_enabled_input->SetDescription(tr("Enable unlocking achievements.
")); - m_common_leaderboards_enabled_input = new ToolTipCheckBox(tr("Enable Leaderboards")); - m_common_leaderboards_enabled_input->SetDescription( - tr("Enable competing in RetroAchievements leaderboards.

Hardcore Mode must be enabled " - "to use.")); - m_common_rich_presence_enabled_input = new ToolTipCheckBox(tr("Enable Rich Presence")); - m_common_rich_presence_enabled_input->SetDescription( - tr("Enable detailed rich presence on the RetroAchievements website.

This provides a " - "detailed description of what the player is doing in game to the website. If this is " - "disabled, the website will only report what game is being played.

This has no " - "bearing on Discord rich presence.")); m_common_unofficial_enabled_input = new ToolTipCheckBox(tr("Enable Unofficial Achievements")); m_common_unofficial_enabled_input->SetDescription( tr("Enable unlocking unofficial achievements as well as official " @@ -117,9 +105,6 @@ void AchievementSettingsWidget::CreateLayout() m_common_layout->addWidget(m_common_login_button); m_common_layout->addWidget(m_common_logout_button); m_common_layout->addWidget(m_common_login_failed); - m_common_layout->addWidget(m_common_achievements_enabled_input); - m_common_layout->addWidget(m_common_leaderboards_enabled_input); - m_common_layout->addWidget(m_common_rich_presence_enabled_input); m_common_layout->addWidget(m_common_hardcore_enabled_input); m_common_layout->addWidget(m_common_progress_enabled_input); m_common_layout->addWidget(m_common_badges_enabled_input); @@ -136,12 +121,6 @@ void AchievementSettingsWidget::ConnectWidgets() &AchievementSettingsWidget::ToggleRAIntegration); connect(m_common_login_button, &QPushButton::pressed, this, &AchievementSettingsWidget::Login); connect(m_common_logout_button, &QPushButton::pressed, this, &AchievementSettingsWidget::Logout); - connect(m_common_achievements_enabled_input, &QCheckBox::toggled, this, - &AchievementSettingsWidget::ToggleAchievements); - connect(m_common_leaderboards_enabled_input, &QCheckBox::toggled, this, - &AchievementSettingsWidget::ToggleLeaderboards); - connect(m_common_rich_presence_enabled_input, &QCheckBox::toggled, this, - &AchievementSettingsWidget::ToggleRichPresence); connect(m_common_hardcore_enabled_input, &QCheckBox::toggled, this, &AchievementSettingsWidget::ToggleHardcore); connect(m_common_progress_enabled_input, &QCheckBox::toggled, this, @@ -165,7 +144,6 @@ void AchievementSettingsWidget::OnControllerInterfaceConfigure() void AchievementSettingsWidget::LoadSettings() { bool enabled = Config::Get(Config::RA_ENABLED); - bool achievements_enabled = Config::Get(Config::RA_ACHIEVEMENTS_ENABLED); bool hardcore_enabled = Config::Get(Config::RA_HARDCORE_ENABLED); bool logged_out = Config::Get(Config::RA_API_TOKEN).empty(); std::string username = Config::Get(Config::RA_USERNAME); @@ -184,17 +162,6 @@ void AchievementSettingsWidget::LoadSettings() SignalBlocking(m_common_logout_button)->setVisible(!logged_out); SignalBlocking(m_common_logout_button)->setEnabled(enabled); - SignalBlocking(m_common_achievements_enabled_input)->setChecked(achievements_enabled); - SignalBlocking(m_common_achievements_enabled_input)->setEnabled(enabled); - - SignalBlocking(m_common_leaderboards_enabled_input) - ->setChecked(Config::Get(Config::RA_LEADERBOARDS_ENABLED)); - SignalBlocking(m_common_leaderboards_enabled_input)->setEnabled(enabled && hardcore_enabled); - - SignalBlocking(m_common_rich_presence_enabled_input) - ->setChecked(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); - SignalBlocking(m_common_rich_presence_enabled_input)->setEnabled(enabled); - SignalBlocking(m_common_hardcore_enabled_input) ->setChecked(Config::Get(Config::RA_HARDCORE_ENABLED)); auto& system = Core::System::GetInstance(); @@ -205,17 +172,17 @@ void AchievementSettingsWidget::LoadSettings() SignalBlocking(m_common_progress_enabled_input) ->setChecked(Config::Get(Config::RA_PROGRESS_ENABLED)); - SignalBlocking(m_common_progress_enabled_input)->setEnabled(enabled && achievements_enabled); + SignalBlocking(m_common_progress_enabled_input)->setEnabled(enabled); SignalBlocking(m_common_badges_enabled_input)->setChecked(Config::Get(Config::RA_BADGES_ENABLED)); SignalBlocking(m_common_badges_enabled_input)->setEnabled(enabled); SignalBlocking(m_common_unofficial_enabled_input) ->setChecked(Config::Get(Config::RA_UNOFFICIAL_ENABLED)); - SignalBlocking(m_common_unofficial_enabled_input)->setEnabled(enabled && achievements_enabled); + SignalBlocking(m_common_unofficial_enabled_input)->setEnabled(enabled); SignalBlocking(m_common_encore_enabled_input)->setChecked(Config::Get(Config::RA_ENCORE_ENABLED)); - SignalBlocking(m_common_encore_enabled_input)->setEnabled(enabled && achievements_enabled); + SignalBlocking(m_common_encore_enabled_input)->setEnabled(enabled); } void AchievementSettingsWidget::SaveSettings() @@ -223,12 +190,6 @@ void AchievementSettingsWidget::SaveSettings() Config::ConfigChangeCallbackGuard config_guard; Config::SetBaseOrCurrent(Config::RA_ENABLED, m_common_integration_enabled_input->isChecked()); - Config::SetBaseOrCurrent(Config::RA_ACHIEVEMENTS_ENABLED, - m_common_achievements_enabled_input->isChecked()); - Config::SetBaseOrCurrent(Config::RA_LEADERBOARDS_ENABLED, - m_common_leaderboards_enabled_input->isChecked()); - Config::SetBaseOrCurrent(Config::RA_RICH_PRESENCE_ENABLED, - m_common_rich_presence_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_HARDCORE_ENABLED, m_common_hardcore_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_PROGRESS_ENABLED, @@ -265,21 +226,6 @@ void AchievementSettingsWidget::Logout() SaveSettings(); } -void AchievementSettingsWidget::ToggleAchievements() -{ - SaveSettings(); -} - -void AchievementSettingsWidget::ToggleLeaderboards() -{ - SaveSettings(); -} - -void AchievementSettingsWidget::ToggleRichPresence() -{ - SaveSettings(); -} - void AchievementSettingsWidget::ToggleHardcore() { SaveSettings(); diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h index eb3f237389..1a404e1616 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h @@ -32,9 +32,6 @@ private: void ToggleRAIntegration(); void Login(); void Logout(); - void ToggleAchievements(); - void ToggleLeaderboards(); - void ToggleRichPresence(); void ToggleHardcore(); void ToggleProgress(); void ToggleBadges(); @@ -51,9 +48,6 @@ private: QLineEdit* m_common_password_input; QPushButton* m_common_login_button; QPushButton* m_common_logout_button; - ToolTipCheckBox* m_common_achievements_enabled_input; - ToolTipCheckBox* m_common_leaderboards_enabled_input; - ToolTipCheckBox* m_common_rich_presence_enabled_input; ToolTipCheckBox* m_common_hardcore_enabled_input; ToolTipCheckBox* m_common_progress_enabled_input; ToolTipCheckBox* m_common_badges_enabled_input; From 0883aa114e1269c56989ad5e2e9503690d11fcd0 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Wed, 3 Apr 2024 15:55:00 -0400 Subject: [PATCH 29/31] Update hardcore toggle to use rc_client The client can take care of itself and handle its own hardcore status when it toggles, so I can tell the settings widget to contact the manager directly to set it. Also, gradually reorganizing the settings dialog over the next handful of commits. --- Source/Core/Core/AchievementManager.cpp | 15 +++++++++------ Source/Core/Core/AchievementManager.h | 1 + .../Core/Core/Config/AchievementSettings.cpp | 2 +- .../AchievementSettingsWidget.cpp | 19 +++++++++++-------- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index a3b20abbfe..db1856eee8 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -45,7 +45,7 @@ void AchievementManager::Init() [](const char* message, const rc_client_t* client) { INFO_LOG_FMT(ACHIEVEMENTS, "{}", message); }); - rc_client_set_hardcore_enabled(m_client, 0); + rc_client_set_hardcore_enabled(m_client, Config::Get(Config::RA_HARDCORE_ENABLED)); rc_client_set_unofficial_enabled(m_client, 1); m_queue.Reset("AchievementManagerQueue", [](const std::function& func) { func(); }); m_image_queue.Reset("AchievementManagerImageQueue", @@ -240,16 +240,19 @@ std::recursive_mutex& AchievementManager::GetLock() return m_lock; } +void AchievementManager::SetHardcoreMode() +{ + rc_client_set_hardcore_enabled(m_client, Config::Get(Config::RA_HARDCORE_ENABLED)); +} + bool AchievementManager::IsHardcoreModeActive() const { std::lock_guard lg{m_lock}; - if (!Config::Get(Config::RA_HARDCORE_ENABLED)) + if (!rc_client_get_hardcore_enabled(m_client)) return false; - if (!Core::IsRunning()) + if (!rc_client_get_game_info(m_client)) return true; - if (!IsGameLoaded()) - return false; - return (m_runtime.trigger_count + m_runtime.lboard_count > 0); + return rc_client_is_processing_required(m_client); } std::string_view AchievementManager::GetPlayerDisplayName() const diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 41f09f94b7..2a3658e662 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -103,6 +103,7 @@ public: void DoFrame(); std::recursive_mutex& GetLock(); + void SetHardcoreMode(); bool IsHardcoreModeActive() const; std::string_view GetPlayerDisplayName() const; u32 GetPlayerScore() const; diff --git a/Source/Core/Core/Config/AchievementSettings.cpp b/Source/Core/Core/Config/AchievementSettings.cpp index 6a23e827e6..8b126e3c54 100644 --- a/Source/Core/Core/Config/AchievementSettings.cpp +++ b/Source/Core/Core/Config/AchievementSettings.cpp @@ -16,7 +16,7 @@ const Info RA_HOST_URL{{System::Achievements, "Achievements", "Host const Info RA_USERNAME{{System::Achievements, "Achievements", "Username"}, ""}; const Info RA_API_TOKEN{{System::Achievements, "Achievements", "ApiToken"}, ""}; const Info RA_HARDCORE_ENABLED{{System::Achievements, "Achievements", "HardcoreEnabled"}, - false}; + true}; const Info RA_PROGRESS_ENABLED{{System::Achievements, "Achievements", "ProgressEnabled"}, false}; const Info RA_BADGES_ENABLED{{System::Achievements, "Achievements", "BadgesEnabled"}, false}; diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index 1c03be7f35..159628f6df 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -61,12 +61,6 @@ void AchievementSettingsWidget::CreateLayout() m_common_login_failed = new QLabel(tr("Login Failed")); m_common_login_failed->setStyleSheet(QStringLiteral("QLabel { color : red; }")); m_common_login_failed->setVisible(false); - m_common_unofficial_enabled_input = new ToolTipCheckBox(tr("Enable Unofficial Achievements")); - m_common_unofficial_enabled_input->SetDescription( - tr("Enable unlocking unofficial achievements as well as official " - "achievements.

Unofficial achievements may be optional or unfinished achievements " - "that have not been deemed official by RetroAchievements and may be useful for testing or " - "simply for fun.")); m_common_hardcore_enabled_input = new ToolTipCheckBox(tr("Enable Hardcore Mode")); m_common_hardcore_enabled_input->SetDescription( tr("Enable Hardcore Mode on RetroAchievements.

Hardcore Mode is intended to provide " @@ -81,6 +75,12 @@ void AchievementSettingsWidget::CreateLayout() "playing.
Close your current game before enabling.
Be aware that " "turning Hardcore Mode off while a game is running requires the game to be closed before " "re-enabling.")); + m_common_unofficial_enabled_input = new ToolTipCheckBox(tr("Enable Unofficial Achievements")); + m_common_unofficial_enabled_input->SetDescription( + tr("Enable unlocking unofficial achievements as well as official " + "achievements.

Unofficial achievements may be optional or unfinished achievements " + "that have not been deemed official by RetroAchievements and may be useful for testing or " + "simply for fun.")); m_common_progress_enabled_input = new ToolTipCheckBox(tr("Enable Progress Notifications")); m_common_progress_enabled_input->SetDescription( tr("Enable progress notifications on achievements.

Displays a brief popup message " @@ -105,11 +105,13 @@ void AchievementSettingsWidget::CreateLayout() m_common_layout->addWidget(m_common_login_button); m_common_layout->addWidget(m_common_logout_button); m_common_layout->addWidget(m_common_login_failed); + m_common_layout->addWidget(new QLabel(tr("Function Settings"))); m_common_layout->addWidget(m_common_hardcore_enabled_input); - m_common_layout->addWidget(m_common_progress_enabled_input); - m_common_layout->addWidget(m_common_badges_enabled_input); m_common_layout->addWidget(m_common_unofficial_enabled_input); m_common_layout->addWidget(m_common_encore_enabled_input); + m_common_layout->addWidget(new QLabel(tr("Display Settings"))); + m_common_layout->addWidget(m_common_progress_enabled_input); + m_common_layout->addWidget(m_common_badges_enabled_input); m_common_layout->setAlignment(Qt::AlignTop); setLayout(m_common_layout); @@ -229,6 +231,7 @@ void AchievementSettingsWidget::Logout() void AchievementSettingsWidget::ToggleHardcore() { SaveSettings(); + AchievementManager::GetInstance().SetHardcoreMode(); if (Config::Get(Config::RA_HARDCORE_ENABLED)) { if (Config::Get(Config::MAIN_EMULATION_SPEED) < 1.0f) From fd0de1b01f88f921d904ba0f752a3b368a0c36a0 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Wed, 3 Apr 2024 16:31:18 -0400 Subject: [PATCH 30/31] Updated Unofficial and Encore settings to use rc_client These settings now only are applied at game start and their tooltips have been updated to reflect this. --- Source/Core/Core/AchievementManager.cpp | 3 +- .../Core/Core/Config/AchievementSettings.cpp | 6 +- Source/Core/Core/Config/AchievementSettings.h | 4 +- .../AchievementSettingsWidget.cpp | 61 ++++++++++--------- .../Achievements/AchievementSettingsWidget.h | 8 +-- 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index db1856eee8..ab1afe4321 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -46,7 +46,6 @@ void AchievementManager::Init() INFO_LOG_FMT(ACHIEVEMENTS, "{}", message); }); rc_client_set_hardcore_enabled(m_client, Config::Get(Config::RA_HARDCORE_ENABLED)); - rc_client_set_unofficial_enabled(m_client, 1); m_queue.Reset("AchievementManagerQueue", [](const std::function& func) { func(); }); m_image_queue.Reset("AchievementManagerImageQueue", [](const std::function& func) { func(); }); @@ -110,6 +109,8 @@ void AchievementManager::LoadGame(const std::string& file_path, const DiscIO::Vo "Attempted to load game achievements without achievement client initialized."); return; } + rc_client_set_unofficial_enabled(m_client, Config::Get(Config::RA_UNOFFICIAL_ENABLED)); + rc_client_set_encore_mode_enabled(m_client, Config::Get(Config::RA_ENCORE_ENABLED)); if (volume) { std::lock_guard lg{m_lock}; diff --git a/Source/Core/Core/Config/AchievementSettings.cpp b/Source/Core/Core/Config/AchievementSettings.cpp index 8b126e3c54..5bd08b5677 100644 --- a/Source/Core/Core/Config/AchievementSettings.cpp +++ b/Source/Core/Core/Config/AchievementSettings.cpp @@ -17,12 +17,12 @@ const Info RA_USERNAME{{System::Achievements, "Achievements", "User const Info RA_API_TOKEN{{System::Achievements, "Achievements", "ApiToken"}, ""}; const Info RA_HARDCORE_ENABLED{{System::Achievements, "Achievements", "HardcoreEnabled"}, true}; -const Info RA_PROGRESS_ENABLED{{System::Achievements, "Achievements", "ProgressEnabled"}, - 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}; +const Info RA_PROGRESS_ENABLED{{System::Achievements, "Achievements", "ProgressEnabled"}, + false}; +const Info RA_BADGES_ENABLED{{System::Achievements, "Achievements", "BadgesEnabled"}, false}; } // namespace Config #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/Config/AchievementSettings.h b/Source/Core/Core/Config/AchievementSettings.h index 2b1f4163a3..96a85cae85 100644 --- a/Source/Core/Core/Config/AchievementSettings.h +++ b/Source/Core/Core/Config/AchievementSettings.h @@ -15,10 +15,10 @@ extern const Info RA_USERNAME; extern const Info RA_HOST_URL; extern const Info RA_API_TOKEN; extern const Info RA_HARDCORE_ENABLED; -extern const Info RA_PROGRESS_ENABLED; -extern const Info RA_BADGES_ENABLED; extern const Info RA_UNOFFICIAL_ENABLED; extern const Info RA_ENCORE_ENABLED; +extern const Info RA_PROGRESS_ENABLED; +extern const Info RA_BADGES_ENABLED; } // namespace Config #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index 159628f6df..8d68f9cab6 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -80,7 +80,13 @@ void AchievementSettingsWidget::CreateLayout() tr("Enable unlocking unofficial achievements as well as official " "achievements.

Unofficial achievements may be optional or unfinished achievements " "that have not been deemed official by RetroAchievements and may be useful for testing or " - "simply for fun.")); + "simply for fun.

Setting takes effect on next game load.")); + m_common_encore_enabled_input = new ToolTipCheckBox(tr("Enable Encore Achievements")); + m_common_encore_enabled_input->SetDescription( + tr("Enable unlocking achievements in Encore Mode.

Encore Mode re-enables achievements " + "the player has already unlocked on the site so that the player will be notified if they " + "meet the unlock conditions again, useful for custom speedrun criteria or simply for fun." + "

Setting takes effect on next game load.")); m_common_progress_enabled_input = new ToolTipCheckBox(tr("Enable Progress Notifications")); m_common_progress_enabled_input->SetDescription( tr("Enable progress notifications on achievements.

Displays a brief popup message " @@ -91,11 +97,6 @@ void AchievementSettingsWidget::CreateLayout() tr("Enable achievement badges.

Displays icons for the player, game, and achievements. " "Simple visual option, but will require a small amount of extra memory and time to " "download the images.")); - m_common_encore_enabled_input = new ToolTipCheckBox(tr("Enable Encore Achievements")); - m_common_encore_enabled_input->SetDescription(tr( - "Enable unlocking achievements in Encore Mode.

Encore Mode re-enables achievements " - "the player has already unlocked on the site so that the player will be notified if they " - "meet the unlock conditions again, useful for custom speedrun criteria or simply for fun.")); m_common_layout->addWidget(m_common_integration_enabled_input); m_common_layout->addWidget(m_common_username_label); @@ -125,14 +126,14 @@ void AchievementSettingsWidget::ConnectWidgets() connect(m_common_logout_button, &QPushButton::pressed, this, &AchievementSettingsWidget::Logout); connect(m_common_hardcore_enabled_input, &QCheckBox::toggled, this, &AchievementSettingsWidget::ToggleHardcore); - connect(m_common_progress_enabled_input, &QCheckBox::toggled, this, - &AchievementSettingsWidget::ToggleProgress); - connect(m_common_badges_enabled_input, &QCheckBox::toggled, this, - &AchievementSettingsWidget::ToggleBadges); connect(m_common_unofficial_enabled_input, &QCheckBox::toggled, this, &AchievementSettingsWidget::ToggleUnofficial); connect(m_common_encore_enabled_input, &QCheckBox::toggled, this, &AchievementSettingsWidget::ToggleEncore); + connect(m_common_progress_enabled_input, &QCheckBox::toggled, this, + &AchievementSettingsWidget::ToggleProgress); + connect(m_common_badges_enabled_input, &QCheckBox::toggled, this, + &AchievementSettingsWidget::ToggleBadges); } void AchievementSettingsWidget::OnControllerInterfaceConfigure() @@ -172,19 +173,19 @@ void AchievementSettingsWidget::LoadSettings() (hardcore_enabled || (Core::GetState(system) == Core::State::Uninitialized && !system.GetMovie().IsPlayingInput()))); - SignalBlocking(m_common_progress_enabled_input) - ->setChecked(Config::Get(Config::RA_PROGRESS_ENABLED)); - SignalBlocking(m_common_progress_enabled_input)->setEnabled(enabled); - - SignalBlocking(m_common_badges_enabled_input)->setChecked(Config::Get(Config::RA_BADGES_ENABLED)); - SignalBlocking(m_common_badges_enabled_input)->setEnabled(enabled); - SignalBlocking(m_common_unofficial_enabled_input) ->setChecked(Config::Get(Config::RA_UNOFFICIAL_ENABLED)); SignalBlocking(m_common_unofficial_enabled_input)->setEnabled(enabled); SignalBlocking(m_common_encore_enabled_input)->setChecked(Config::Get(Config::RA_ENCORE_ENABLED)); SignalBlocking(m_common_encore_enabled_input)->setEnabled(enabled); + + SignalBlocking(m_common_progress_enabled_input) + ->setChecked(Config::Get(Config::RA_PROGRESS_ENABLED)); + SignalBlocking(m_common_progress_enabled_input)->setEnabled(enabled); + + SignalBlocking(m_common_badges_enabled_input)->setChecked(Config::Get(Config::RA_BADGES_ENABLED)); + SignalBlocking(m_common_badges_enabled_input)->setEnabled(enabled); } void AchievementSettingsWidget::SaveSettings() @@ -194,12 +195,12 @@ void AchievementSettingsWidget::SaveSettings() Config::SetBaseOrCurrent(Config::RA_ENABLED, m_common_integration_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_HARDCORE_ENABLED, m_common_hardcore_enabled_input->isChecked()); - Config::SetBaseOrCurrent(Config::RA_PROGRESS_ENABLED, - m_common_unofficial_enabled_input->isChecked()); - Config::SetBaseOrCurrent(Config::RA_BADGES_ENABLED, m_common_badges_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_UNOFFICIAL_ENABLED, m_common_unofficial_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_ENCORE_ENABLED, m_common_encore_enabled_input->isChecked()); + Config::SetBaseOrCurrent(Config::RA_PROGRESS_ENABLED, + m_common_progress_enabled_input->isChecked()); + Config::SetBaseOrCurrent(Config::RA_BADGES_ENABLED, m_common_badges_enabled_input->isChecked()); Config::Save(); } @@ -243,6 +244,16 @@ void AchievementSettingsWidget::ToggleHardcore() emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance())); } +void AchievementSettingsWidget::ToggleUnofficial() +{ + SaveSettings(); +} + +void AchievementSettingsWidget::ToggleEncore() +{ + SaveSettings(); +} + void AchievementSettingsWidget::ToggleProgress() { SaveSettings(); @@ -255,14 +266,4 @@ void AchievementSettingsWidget::ToggleBadges() AchievementManager::GetInstance().FetchGameBadges(); } -void AchievementSettingsWidget::ToggleUnofficial() -{ - SaveSettings(); -} - -void AchievementSettingsWidget::ToggleEncore() -{ - SaveSettings(); -} - #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h index 1a404e1616..1ae2bf0fde 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h @@ -33,10 +33,10 @@ private: void Login(); void Logout(); void ToggleHardcore(); - void ToggleProgress(); - void ToggleBadges(); void ToggleUnofficial(); void ToggleEncore(); + void ToggleProgress(); + void ToggleBadges(); QGroupBox* m_common_box; QVBoxLayout* m_common_layout; @@ -49,10 +49,10 @@ private: QPushButton* m_common_login_button; QPushButton* m_common_logout_button; ToolTipCheckBox* m_common_hardcore_enabled_input; - ToolTipCheckBox* m_common_progress_enabled_input; - ToolTipCheckBox* m_common_badges_enabled_input; ToolTipCheckBox* m_common_unofficial_enabled_input; ToolTipCheckBox* m_common_encore_enabled_input; + ToolTipCheckBox* m_common_progress_enabled_input; + ToolTipCheckBox* m_common_badges_enabled_input; }; #endif // USE_RETRO_ACHIEVEMENTS From d8ef3ee6c5850aace8d84fba597c9b0ac3dfe090 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Wed, 3 Apr 2024 16:58:22 -0400 Subject: [PATCH 31/31] Add Spectator Mode to achievement settings Spectator Mode is a new mode added by rc_client that allows for achievement and leaderboard functionality, but does not submit this data to the site, partially allowing for offline achievements. It effectively replaces the former settings for disabling achievements, leaderboards, and RP, which are now always active internally as long as the client is active. --- Source/Core/Core/AchievementManager.cpp | 6 +++++ Source/Core/Core/AchievementManager.h | 1 + .../Core/Core/Config/AchievementSettings.cpp | 2 ++ Source/Core/Core/Config/AchievementSettings.h | 1 + .../AchievementSettingsWidget.cpp | 22 +++++++++++++++++++ .../Achievements/AchievementSettingsWidget.h | 2 ++ 6 files changed, 34 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index ab1afe4321..bf714f0e65 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -111,6 +111,7 @@ void AchievementManager::LoadGame(const std::string& file_path, const DiscIO::Vo } rc_client_set_unofficial_enabled(m_client, Config::Get(Config::RA_UNOFFICIAL_ENABLED)); rc_client_set_encore_mode_enabled(m_client, Config::Get(Config::RA_ENCORE_ENABLED)); + rc_client_set_spectator_mode_enabled(m_client, Config::Get(Config::RA_SPECTATOR_ENABLED)); if (volume) { std::lock_guard lg{m_lock}; @@ -256,6 +257,11 @@ bool AchievementManager::IsHardcoreModeActive() const return rc_client_is_processing_required(m_client); } +void AchievementManager::SetSpectatorMode() +{ + rc_client_set_spectator_mode_enabled(m_client, Config::Get(Config::RA_SPECTATOR_ENABLED)); +} + std::string_view AchievementManager::GetPlayerDisplayName() const { if (!HasAPIToken()) diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 2a3658e662..757daeb86d 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -105,6 +105,7 @@ public: std::recursive_mutex& GetLock(); void SetHardcoreMode(); bool IsHardcoreModeActive() const; + void SetSpectatorMode(); std::string_view GetPlayerDisplayName() const; u32 GetPlayerScore() const; const BadgeStatus& GetPlayerBadge() const; diff --git a/Source/Core/Core/Config/AchievementSettings.cpp b/Source/Core/Core/Config/AchievementSettings.cpp index 5bd08b5677..38a5bb6166 100644 --- a/Source/Core/Core/Config/AchievementSettings.cpp +++ b/Source/Core/Core/Config/AchievementSettings.cpp @@ -20,6 +20,8 @@ const Info RA_HARDCORE_ENABLED{{System::Achievements, "Achievements", "Har const Info RA_UNOFFICIAL_ENABLED{{System::Achievements, "Achievements", "UnofficialEnabled"}, false}; const Info RA_ENCORE_ENABLED{{System::Achievements, "Achievements", "EncoreEnabled"}, false}; +const Info RA_SPECTATOR_ENABLED{{System::Achievements, "Achievements", "SpectatorEnabled"}, + false}; const Info RA_PROGRESS_ENABLED{{System::Achievements, "Achievements", "ProgressEnabled"}, false}; const Info RA_BADGES_ENABLED{{System::Achievements, "Achievements", "BadgesEnabled"}, false}; diff --git a/Source/Core/Core/Config/AchievementSettings.h b/Source/Core/Core/Config/AchievementSettings.h index 96a85cae85..e448054214 100644 --- a/Source/Core/Core/Config/AchievementSettings.h +++ b/Source/Core/Core/Config/AchievementSettings.h @@ -17,6 +17,7 @@ extern const Info RA_API_TOKEN; extern const Info RA_HARDCORE_ENABLED; extern const Info RA_UNOFFICIAL_ENABLED; extern const Info RA_ENCORE_ENABLED; +extern const Info RA_SPECTATOR_ENABLED; extern const Info RA_PROGRESS_ENABLED; extern const Info RA_BADGES_ENABLED; } // namespace Config diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index 8d68f9cab6..cc6a4c1c50 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -87,6 +87,13 @@ void AchievementSettingsWidget::CreateLayout() "the player has already unlocked on the site so that the player will be notified if they " "meet the unlock conditions again, useful for custom speedrun criteria or simply for fun." "

Setting takes effect on next game load.")); + m_common_spectator_enabled_input = new ToolTipCheckBox(tr("Enable Spectator Mode")); + m_common_spectator_enabled_input->SetDescription( + tr("Enable unlocking achievements in Spectator Mode.

While in Spectator Mode, " + "achievements and leaderboards will be processed and displayed on screen, but will not be " + "submitted to the server.

If this is on at game launch, it will not be turned off " + "until game close, because a RetroAchievements session will not be created.

If " + "this is off at game launch, it can be toggled freely while the game is running.")); m_common_progress_enabled_input = new ToolTipCheckBox(tr("Enable Progress Notifications")); m_common_progress_enabled_input->SetDescription( tr("Enable progress notifications on achievements.

Displays a brief popup message " @@ -110,6 +117,7 @@ void AchievementSettingsWidget::CreateLayout() m_common_layout->addWidget(m_common_hardcore_enabled_input); m_common_layout->addWidget(m_common_unofficial_enabled_input); m_common_layout->addWidget(m_common_encore_enabled_input); + m_common_layout->addWidget(m_common_spectator_enabled_input); m_common_layout->addWidget(new QLabel(tr("Display Settings"))); m_common_layout->addWidget(m_common_progress_enabled_input); m_common_layout->addWidget(m_common_badges_enabled_input); @@ -130,6 +138,8 @@ void AchievementSettingsWidget::ConnectWidgets() &AchievementSettingsWidget::ToggleUnofficial); connect(m_common_encore_enabled_input, &QCheckBox::toggled, this, &AchievementSettingsWidget::ToggleEncore); + connect(m_common_spectator_enabled_input, &QCheckBox::toggled, this, + &AchievementSettingsWidget::ToggleSpectator); connect(m_common_progress_enabled_input, &QCheckBox::toggled, this, &AchievementSettingsWidget::ToggleProgress); connect(m_common_badges_enabled_input, &QCheckBox::toggled, this, @@ -180,6 +190,10 @@ void AchievementSettingsWidget::LoadSettings() SignalBlocking(m_common_encore_enabled_input)->setChecked(Config::Get(Config::RA_ENCORE_ENABLED)); SignalBlocking(m_common_encore_enabled_input)->setEnabled(enabled); + SignalBlocking(m_common_spectator_enabled_input) + ->setChecked(Config::Get(Config::RA_SPECTATOR_ENABLED)); + SignalBlocking(m_common_spectator_enabled_input)->setEnabled(enabled); + SignalBlocking(m_common_progress_enabled_input) ->setChecked(Config::Get(Config::RA_PROGRESS_ENABLED)); SignalBlocking(m_common_progress_enabled_input)->setEnabled(enabled); @@ -198,6 +212,8 @@ void AchievementSettingsWidget::SaveSettings() Config::SetBaseOrCurrent(Config::RA_UNOFFICIAL_ENABLED, m_common_unofficial_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_ENCORE_ENABLED, m_common_encore_enabled_input->isChecked()); + Config::SetBaseOrCurrent(Config::RA_SPECTATOR_ENABLED, + m_common_spectator_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_PROGRESS_ENABLED, m_common_progress_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_BADGES_ENABLED, m_common_badges_enabled_input->isChecked()); @@ -254,6 +270,12 @@ void AchievementSettingsWidget::ToggleEncore() SaveSettings(); } +void AchievementSettingsWidget::ToggleSpectator() +{ + SaveSettings(); + AchievementManager::GetInstance().SetSpectatorMode(); +} + void AchievementSettingsWidget::ToggleProgress() { SaveSettings(); diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h index 1ae2bf0fde..68ba658b8f 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h @@ -35,6 +35,7 @@ private: void ToggleHardcore(); void ToggleUnofficial(); void ToggleEncore(); + void ToggleSpectator(); void ToggleProgress(); void ToggleBadges(); @@ -51,6 +52,7 @@ private: ToolTipCheckBox* m_common_hardcore_enabled_input; ToolTipCheckBox* m_common_unofficial_enabled_input; ToolTipCheckBox* m_common_encore_enabled_input; + ToolTipCheckBox* m_common_spectator_enabled_input; ToolTipCheckBox* m_common_progress_enabled_input; ToolTipCheckBox* m_common_badges_enabled_input; };