From 6541aa206d15e664a31f790dc4d35e712efad037 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 15 Jun 2023 00:26:54 -0400 Subject: [PATCH 1/6] Added RequestImage to AchievementManager Image requests from RetroAchievements have a slightly different format from other requests, on account of being GET calls that return strings of image data, so this is a separate function that makes such calls. To handle this, I create a BadgeStatus object that ties each badge to a boolean flag for whether it is loaded and thus usable. --- Source/Core/Core/AchievementManager.cpp | 26 +++++++++++++++++++++++++ Source/Core/Core/AchievementManager.h | 2 ++ 2 files changed, 28 insertions(+) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 77661bb43e..5ea4f253b9 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -944,4 +944,30 @@ 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; + } +} + #endif // USE_RETRO_ACHIEVEMENTS diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index a2ada5f0ee..9b67c1e018 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -56,6 +56,7 @@ public: using FormattedValue = std::array; static constexpr size_t RP_SIZE = 256; using RichPresence = std::array; + using Badge = std::vector; struct UnlockStatus { @@ -129,6 +130,7 @@ 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); rc_runtime_t m_runtime{}; Core::System* m_system{}; From fd50b1fda67dec87326164c4d9a2b1c867305b55 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 15 Jun 2023 03:24:34 -0400 Subject: [PATCH 2/6] Added FetchBadges to AchievementManager FetchBadges fetches all available badges (player, game, achievements) and stores them in AchievementManager's memory. New fields and accessors have been added as necessary. Badges for individual achievements are stored in the UnlockStatus map. The method is public so that the settings dialog can call it if badges are turned on after a game is started. Badges are deleted at game close and logout. --- Source/Core/Core/AchievementManager.cpp | 277 +++++++++++++++++- Source/Core/Core/AchievementManager.h | 16 +- .../Core/Core/Config/AchievementSettings.cpp | 1 + Source/Core/Core/Config/AchievementSettings.h | 1 + .../Core/ConfigLoaders/IsSettingSaveable.cpp | 1 + 5 files changed, 292 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 5ea4f253b9..35287d3560 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -35,6 +35,8 @@ void AchievementManager::Init() rc_runtime_init(&m_runtime); m_is_runtime_initialized = true; m_queue.Reset("AchievementManagerQueue", [](const std::function& func) { func(); }); + m_image_queue.Reset("AchievementManagerImageQueue", + [](const std::function& func) { func(); }); LoginAsync("", [](ResponseType r_type) {}); INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager Initialized"); } @@ -55,6 +57,7 @@ AchievementManager::ResponseType AchievementManager::Login(const std::string& pa return AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED; } AchievementManager::ResponseType r_type = VerifyCredentials(password); + FetchBadges(); if (m_update_callback) m_update_callback(); return r_type; @@ -71,6 +74,7 @@ void AchievementManager::LoginAsync(const std::string& password, const ResponseC } m_queue.EmplaceItem([this, password, callback] { callback(VerifyCredentials(password)); + FetchBadges(); if (m_update_callback) m_update_callback(); }); @@ -207,6 +211,7 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path, } ActivateDeactivateLeaderboards(); ActivateDeactivateRichPresence(); + FetchBadges(); // Reset this to zero so that RP immediately triggers on the first frame m_last_ping_time = 0; INFO_LOG_FMT(ACHIEVEMENTS, "RetroAchievements successfully loaded for {}.", m_game_data.title); @@ -286,6 +291,256 @@ void AchievementManager::ActivateDeactivateRichPresence() INFO_LOG_FMT(ACHIEVEMENTS, "Rich presence (de)activated."); } +void AchievementManager::FetchBadges() +{ + if (!m_is_runtime_initialized || !IsLoggedIn() || !Config::Get(Config::RA_BADGES_ENABLED)) + { + if (m_update_callback) + m_update_callback(); + return; + } + m_image_queue.Cancel(); + + if (m_player_badge.name != m_display_name) + { + m_image_queue.EmplaceItem([this] { + std::string name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_display_name == m_player_badge.name) + return; + name_to_fetch = m_display_name; + } + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_USER}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded player badge id {}.", name_to_fetch); + std::lock_guard lg{m_lock}; + if (name_to_fetch != m_display_name) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {} for player id {}.", + name_to_fetch, m_display_name); + return; + } + m_player_badge.badge = std::move(fetched_badge); + m_player_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download player badge id {}.", name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + + if (!IsGameLoaded()) + { + if (m_update_callback) + m_update_callback(); + return; + } + + int badgematch = 0; + { + std::lock_guard lg{m_lock}; + badgematch = m_game_badge.name.compare(m_game_data.image_name); + } + if (badgematch != 0) + { + m_image_queue.EmplaceItem([this] { + std::string name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_badge.name.compare(m_game_data.image_name) == 0) + return; + name_to_fetch.assign(m_game_data.image_name); + } + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_GAME}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded game badge id {}.", name_to_fetch); + std::lock_guard lg{m_lock}; + if (name_to_fetch.compare(m_game_data.image_name) != 0) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Requested outdated badge id {} for game id {}.", + name_to_fetch, m_game_data.image_name); + return; + } + m_game_badge.badge = std::move(fetched_badge); + m_game_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download game badge id {}.", name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + + unsigned num_achievements = m_game_data.num_achievements; + for (size_t index = 0; index < num_achievements; index++) + { + std::lock_guard lg{m_lock}; + // In case the number of achievements changes since the loop started; I just don't want + // to lock for the ENTIRE loop so instead I reclaim the lock each cycle + if (num_achievements != m_game_data.num_achievements) + break; + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + std::string name_to_fetch(achievement.badge_name); + const UnlockStatus& unlock_status = m_unlock_map[achievement.id]; + if (unlock_status.unlocked_badge.name != name_to_fetch) + { + m_image_queue.EmplaceItem([this, index] { + std::string current_name, name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch unlocked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch unlocked badge for achievement id {} not in unlock map.", + index); + return; + } + name_to_fetch.assign(achievement.badge_name); + current_name = unlock_itr->second.unlocked_badge.name; + } + if (current_name == name_to_fetch) + return; + rc_api_fetch_image_request_t icon_request = {.image_name = name_to_fetch.c_str(), + .image_type = RC_IMAGE_TYPE_ACHIEVEMENT}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded unlocked achievement badge id {}.", + name_to_fetch); + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Fetched unlocked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT(ACHIEVEMENTS, + "Fetched unlocked badge for achievement id {} not in unlock map.", index); + return; + } + if (name_to_fetch.compare(achievement.badge_name) != 0) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Requested outdated unlocked achievement badge id {} for achievement id {}.", + name_to_fetch, current_name); + return; + } + unlock_itr->second.unlocked_badge.badge = std::move(fetched_badge); + unlock_itr->second.unlocked_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download unlocked achievement badge id {}.", + name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + if (unlock_status.locked_badge.name != name_to_fetch) + { + m_image_queue.EmplaceItem([this, index] { + std::string current_name, name_to_fetch; + { + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch locked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT( + ACHIEVEMENTS, + "Attempted to fetch locked badge for achievement id {} not in unlock map.", index); + return; + } + name_to_fetch.assign(achievement.badge_name); + current_name = unlock_itr->second.locked_badge.name; + } + if (current_name == name_to_fetch) + return; + rc_api_fetch_image_request_t icon_request = { + .image_name = name_to_fetch.c_str(), .image_type = RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED}; + Badge fetched_badge; + if (RequestImage(icon_request, &fetched_badge) == ResponseType::SUCCESS) + { + INFO_LOG_FMT(ACHIEVEMENTS, "Successfully downloaded locked achievement badge id {}.", + name_to_fetch); + std::lock_guard lg{m_lock}; + if (m_game_data.num_achievements <= index) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Fetched locked badge for index {} after achievement list cleared.", + index); + return; + } + rc_api_achievement_definition_t& achievement = m_game_data.achievements[index]; + auto unlock_itr = m_unlock_map.find(achievement.id); + if (unlock_itr == m_unlock_map.end()) + { + ERROR_LOG_FMT(ACHIEVEMENTS, + "Fetched locked badge for achievement id {} not in unlock map.", index); + return; + } + if (name_to_fetch.compare(achievement.badge_name) != 0) + { + INFO_LOG_FMT(ACHIEVEMENTS, + "Requested outdated locked achievement badge id {} for achievement id {}.", + name_to_fetch, current_name); + return; + } + unlock_itr->second.locked_badge.badge = std::move(fetched_badge); + unlock_itr->second.locked_badge.name = std::move(name_to_fetch); + } + else + { + WARN_LOG_FMT(ACHIEVEMENTS, "Failed to download locked achievement badge id {}.", + name_to_fetch); + } + if (m_update_callback) + m_update_callback(); + }); + } + } + if (m_update_callback) + m_update_callback(); +} + void AchievementManager::DoFrame() { if (!m_is_game_loaded) @@ -381,6 +636,11 @@ u32 AchievementManager::GetPlayerScore() const return IsLoggedIn() ? m_player_score : 0; } +const AchievementManager::BadgeStatus& AchievementManager::GetPlayerBadge() const +{ + return m_player_badge; +} + std::string AchievementManager::GetGameDisplayName() const { return IsGameLoaded() ? m_game_data.title : ""; @@ -417,7 +677,12 @@ rc_api_fetch_game_data_response_t* AchievementManager::GetGameData() return &m_game_data; } -AchievementManager::UnlockStatus +const AchievementManager::BadgeStatus& AchievementManager::GetGameBadge() const +{ + return m_game_badge; +} + +const AchievementManager::UnlockStatus& AchievementManager::GetUnlockStatus(AchievementId achievement_id) const { return m_unlock_map.at(achievement_id); @@ -447,10 +712,12 @@ void AchievementManager::CloseGame() ActivateDeactivateLeaderboards(); ActivateDeactivateRichPresence(); m_game_id = 0; + m_game_badge.name = ""; m_unlock_map.clear(); rc_api_destroy_fetch_game_data_response(&m_game_data); std::memset(&m_game_data, 0, sizeof(m_game_data)); m_queue.Cancel(); + m_image_queue.Cancel(); m_system = nullptr; } } @@ -461,8 +728,12 @@ void AchievementManager::CloseGame() void AchievementManager::Logout() { - CloseGame(); - Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); + { + std::lock_guard lg{m_lock}; + CloseGame(); + m_player_badge.name = ""; + Config::SetBaseOrCurrent(Config::RA_API_TOKEN, ""); + } if (m_update_callback) m_update_callback(); INFO_LOG_FMT(ACHIEVEMENTS, "Logged out from server."); diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 9b67c1e018..6379cee94a 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -58,6 +58,12 @@ public: using RichPresence = std::array; using Badge = std::vector; + struct BadgeStatus + { + std::string name = ""; + Badge badge{}; + }; + struct UnlockStatus { AchievementId game_data_index = 0; @@ -69,6 +75,8 @@ public: } remote_unlock_status = UnlockType::LOCKED; u32 session_unlock_count = 0; u32 points = 0; + BadgeStatus locked_badge; + BadgeStatus unlocked_badge; }; static AchievementManager* GetInstance(); @@ -84,6 +92,7 @@ public: void ActivateDeactivateAchievements(); void ActivateDeactivateLeaderboards(); void ActivateDeactivateRichPresence(); + void FetchBadges(); void DoFrame(); u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud); @@ -92,10 +101,12 @@ public: std::recursive_mutex* GetLock(); std::string GetPlayerDisplayName() const; u32 GetPlayerScore() const; + const BadgeStatus& GetPlayerBadge() const; std::string GetGameDisplayName() const; PointSpread TallyScore() const; rc_api_fetch_game_data_response_t* GetGameData(); - UnlockStatus GetUnlockStatus(AchievementId achievement_id) const; + const BadgeStatus& GetGameBadge() const; + const UnlockStatus& GetUnlockStatus(AchievementId achievement_id) const; void GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target); RichPresence GetRichPresence(); @@ -138,16 +149,19 @@ private: UpdateCallback m_update_callback; std::string m_display_name; u32 m_player_score = 0; + BadgeStatus m_player_badge; std::array m_game_hash{}; u32 m_game_id = 0; rc_api_fetch_game_data_response_t m_game_data{}; bool m_is_game_loaded = false; + BadgeStatus m_game_badge; RichPresence m_rich_presence; time_t m_last_ping_time = 0; std::unordered_map m_unlock_map; Common::WorkQueueThread> m_queue; + Common::WorkQueueThread> m_image_queue; std::recursive_mutex m_lock; }; // class AchievementManager diff --git a/Source/Core/Core/Config/AchievementSettings.cpp b/Source/Core/Core/Config/AchievementSettings.cpp index 3499fcc791..9211fb712e 100644 --- a/Source/Core/Core/Config/AchievementSettings.cpp +++ b/Source/Core/Core/Config/AchievementSettings.cpp @@ -19,6 +19,7 @@ const Info RA_LEADERBOARDS_ENABLED{ {System::Achievements, "Achievements", "LeaderboardsEnabled"}, false}; const Info RA_RICH_PRESENCE_ENABLED{ {System::Achievements, "Achievements", "RichPresenceEnabled"}, false}; +const Info RA_BADGES_ENABLED{{System::Achievements, "Achievements", "BadgesEnabled"}, false}; const Info RA_UNOFFICIAL_ENABLED{{System::Achievements, "Achievements", "UnofficialEnabled"}, false}; const Info RA_ENCORE_ENABLED{{System::Achievements, "Achievements", "EncoreEnabled"}, false}; diff --git a/Source/Core/Core/Config/AchievementSettings.h b/Source/Core/Core/Config/AchievementSettings.h index 33318f7400..c3dec06d01 100644 --- a/Source/Core/Core/Config/AchievementSettings.h +++ b/Source/Core/Core/Config/AchievementSettings.h @@ -14,6 +14,7 @@ extern const Info RA_API_TOKEN; extern const Info RA_ACHIEVEMENTS_ENABLED; extern const Info RA_LEADERBOARDS_ENABLED; extern const Info RA_RICH_PRESENCE_ENABLED; +extern const Info RA_BADGES_ENABLED; extern const Info RA_UNOFFICIAL_ENABLED; extern const Info RA_ENCORE_ENABLED; } // namespace Config diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index 8ec6278aae..7d0b63f9bb 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -47,6 +47,7 @@ bool IsSettingSaveable(const Config::Location& config_location) &Config::RA_ACHIEVEMENTS_ENABLED.GetLocation(), &Config::RA_LEADERBOARDS_ENABLED.GetLocation(), &Config::RA_RICH_PRESENCE_ENABLED.GetLocation(), + &Config::RA_BADGES_ENABLED.GetLocation(), &Config::RA_UNOFFICIAL_ENABLED.GetLocation(), &Config::RA_ENCORE_ENABLED.GetLocation(), }; From 0715da2d684dd9fd7f51d24635c972845048350e Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 15 Jun 2023 03:57:58 -0400 Subject: [PATCH 3/6] Added player and game badges to Achievements dialog header Provided badges are turned on, if there's a player logged in their RetroAchievements icon will appear next to their player info in the header of the Achievements dialog. If they're playing a game, so will the icon for the game. Also performed some refactoring and reorganizing to the header as a whole so that it looks consistent whether a game is running or not. --- .../Achievements/AchievementHeaderWidget.cpp | 152 +++++++++++------- .../Achievements/AchievementHeaderWidget.h | 12 +- 2 files changed, 103 insertions(+), 61 deletions(-) diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index 8e7977b504..b6eeb09897 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -30,45 +30,44 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(parent) { - m_user_name = new QLabel(); - m_user_points = new QLabel(); - m_game_name = new QLabel(); - m_game_points = new QLabel(); + m_user_icon = new QLabel(); + 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_rich_presence = new QLabel(); - QVBoxLayout* m_user_right_col = new QVBoxLayout(); - m_user_right_col->addWidget(m_user_name); - m_user_right_col->addWidget(m_user_points); - QHBoxLayout* m_user_layout = new QHBoxLayout(); - // TODO: player badge goes here - m_user_layout->addLayout(m_user_right_col); - m_user_box = new QGroupBox(); - m_user_box->setLayout(m_user_layout); + QSizePolicy sp_retain = m_game_progress_hard->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); - QVBoxLayout* m_game_right_col = new QVBoxLayout(); - m_game_right_col->addWidget(m_game_name); - m_game_right_col->addWidget(m_game_points); - m_game_right_col->addWidget(m_game_progress_hard); - m_game_right_col->addWidget(m_game_progress_soft); - QHBoxLayout* m_game_upper_row = new QHBoxLayout(); - // TODO: player badge and game badge go here - m_game_upper_row->addLayout(m_game_right_col); - QVBoxLayout* m_game_layout = new QVBoxLayout(); - m_game_layout->addLayout(m_game_upper_row); - m_game_layout->addWidget(m_rich_presence); - m_game_box = new QGroupBox(); - m_game_box->setLayout(m_game_layout); + QVBoxLayout* icon_col = new QVBoxLayout(); + icon_col->addWidget(m_user_icon); + icon_col->addWidget(m_game_icon); + 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_rich_presence); + QHBoxLayout* header_layout = new QHBoxLayout(); + header_layout->addLayout(icon_col); + header_layout->addLayout(text_col); + m_header_box = new QGroupBox(); + m_header_box->setLayout(header_layout); QVBoxLayout* m_total = new QVBoxLayout(); - m_total->addWidget(m_user_box); - m_total->addWidget(m_game_box); + m_total->addWidget(m_header_box); m_total->setContentsMargins(0, 0, 0, 0); m_total->setAlignment(Qt::AlignTop); setLayout(m_total); + std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()}; UpdateData(); } @@ -76,38 +75,83 @@ void AchievementHeaderWidget::UpdateData() { if (!AchievementManager::GetInstance()->IsLoggedIn()) { - m_user_box->setVisible(false); - m_game_box->setVisible(false); - return; - } - - QString user_name = - QString::fromStdString(AchievementManager::GetInstance()->GetPlayerDisplayName()); - m_user_name->setText(user_name); - m_user_points->setText(tr("%1 points").arg(AchievementManager::GetInstance()->GetPlayerScore())); - - if (!AchievementManager::GetInstance()->IsGameLoaded()) - { - m_user_box->setVisible(true); - m_game_box->setVisible(false); + m_header_box->setVisible(false); return; } AchievementManager::PointSpread point_spread = AchievementManager::GetInstance()->TallyScore(); - m_game_name->setText( - QString::fromStdString(AchievementManager::GetInstance()->GetGameDisplayName())); - m_game_points->setText(GetPointsString(user_name, point_spread)); - m_game_progress_hard = new QProgressBar(); - m_game_progress_hard->setRange(0, point_spread.total_count); - m_game_progress_soft->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); - m_rich_presence->setText( - QString::fromUtf8(AchievementManager::GetInstance()->GetRichPresence().data())); - m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); + QString user_name = + QString::fromStdString(AchievementManager::GetInstance()->GetPlayerDisplayName()); + QString game_name = + QString::fromStdString(AchievementManager::GetInstance()->GetGameDisplayName()); + AchievementManager::BadgeStatus player_badge = + AchievementManager::GetInstance()->GetPlayerBadge(); + AchievementManager::BadgeStatus game_badge = AchievementManager::GetInstance()->GetGameBadge(); - m_user_box->setVisible(false); - m_game_box->setVisible(true); + m_user_icon->setVisible(false); + m_user_icon->clear(); + m_user_icon->setText({}); + if (Config::Get(Config::RA_BADGES_ENABLED)) + { + if (player_badge.name != "") + { + 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_game_icon->setVisible(false); + m_game_icon->clear(); + m_game_icon->setText({}); + if (Config::Get(Config::RA_BADGES_ENABLED)) + { + if (game_badge.name != "") + { + QImage i_game_icon{}; + if (i_game_icon.loadFromData(&game_badge.badge.front(), (int)game_badge.badge.size())) + { + m_game_icon->setPixmap(QPixmap::fromImage(i_game_icon) + .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + m_game_icon->adjustSize(); + m_game_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent")); + 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_game_progress_hard->setRange(0, point_spread.total_count); + if (!m_game_progress_hard->isVisible()) + m_game_progress_hard->setVisible(true); + m_game_progress_soft->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_rich_presence->setText( + QString::fromUtf8(AchievementManager::GetInstance()->GetRichPresence().data())); + if (!m_rich_presence->isVisible()) + m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); + } + else + { + m_name->setText(user_name); + m_points->setText(tr("%1 points").arg(AchievementManager::GetInstance()->GetPlayerScore())); + + m_game_progress_hard->setVisible(false); + m_game_progress_soft->setVisible(false); + m_rich_presence->setVisible(false); + } } QString diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h index b99457f2f1..b8cbf9c9e1 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h @@ -27,16 +27,14 @@ private: QGroupBox* m_common_box; QVBoxLayout* m_common_layout; - QLabel* m_user_name; - QLabel* m_user_points; - QLabel* m_game_name; - QLabel* m_game_points; + QLabel* m_user_icon; + QLabel* m_game_icon; + QLabel* m_name; + QLabel* m_points; QProgressBar* m_game_progress_hard; QProgressBar* m_game_progress_soft; QLabel* m_rich_presence; - - QGroupBox* m_user_box; - QGroupBox* m_game_box; + QGroupBox* m_header_box; }; #endif // USE_RETRO_ACHIEVEMENTS From ba83efded689fa0dbf526b0885e28ed71105fe73 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 4 Jul 2023 16:50:54 -0400 Subject: [PATCH 4/6] Added badges to achievement progress tab Provided the badges are turned on in the settings, each achievement will have a badge next to it on the progress tab. There are different badges for locked and unlocked (usually locked is grayscale while unlocked is in color but not necessarily) and the badge chosen depends on the player's current unlock and hardcore status. --- Source/Core/Core/AchievementManager.cpp | 2 + .../AchievementProgressWidget.cpp | 41 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 35287d3560..1cf02daa5d 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -691,6 +691,8 @@ AchievementManager::GetUnlockStatus(AchievementId achievement_id) const void AchievementManager::GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target) { + if (!IsGameLoaded()) + return; rc_runtime_get_achievement_measured(&m_runtime, achievement_id, value, target); } diff --git a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp index a883f0d675..8879f940f2 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp @@ -35,7 +35,10 @@ AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget( m_common_box = new QGroupBox(); m_common_layout = new QVBoxLayout(); - UpdateData(); + { + std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()}; + UpdateData(); + } m_common_box->setLayout(m_common_layout); @@ -51,12 +54,46 @@ AchievementProgressWidget::CreateAchievementBox(const rc_api_achievement_definit { if (!AchievementManager::GetInstance()->IsGameLoaded()) return new QGroupBox(); + QLabel* a_badge = new QLabel(); + const auto unlock_status = AchievementManager::GetInstance()->GetUnlockStatus(achievement->id); + const AchievementManager::BadgeStatus* badge = &unlock_status.locked_badge; + if (unlock_status.remote_unlock_status == AchievementManager::UnlockStatus::UnlockType::HARDCORE) + { + badge = &unlock_status.unlocked_badge; + } + else if (hardcore_mode_enabled && unlock_status.session_unlock_count > 1) + { + badge = &unlock_status.unlocked_badge; + } + else if (unlock_status.remote_unlock_status == + AchievementManager::UnlockStatus::UnlockType::SOFTCORE) + { + badge = &unlock_status.unlocked_badge; + } + else if (unlock_status.session_unlock_count > 1) + { + badge = &unlock_status.unlocked_badge; + } + 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(); + } + } + 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; AchievementManager::GetInstance()->GetAchievementProgress(achievement->id, &value, &target); @@ -77,7 +114,7 @@ AchievementProgressWidget::CreateAchievementBox(const rc_api_achievement_definit a_col_right->addWidget(a_status); a_col_right->addWidget(a_progress_bar); QHBoxLayout* a_total = new QHBoxLayout(); - // TODO: achievement badge goes here + a_total->addWidget(a_badge); a_total->addLayout(a_col_right); QGroupBox* a_group_box = new QGroupBox(); a_group_box->setLayout(a_total); From 80d77cfdadc48a0cd05a4977d5a18478b24177b4 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Tue, 4 Jul 2023 16:17:10 -0400 Subject: [PATCH 5/6] Added colored borders to badges in achievements dialog The achievement badges will now have a blue or gold border to identify whether they have been unlocked in softcore or hardcore mode. Similarly, the game badge will have a blue border if all achievements have been unlocked in either mode or a gold border if all achievements have been unlocked in hardcore mode. --- Source/Core/Core/AchievementManager.h | 4 ++++ .../DolphinQt/Achievements/AchievementHeaderWidget.cpp | 8 +++++++- .../DolphinQt/Achievements/AchievementProgressWidget.cpp | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 6379cee94a..f7866f0926 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -79,6 +79,10 @@ public: BadgeStatus unlocked_badge; }; + static constexpr std::string_view GRAY = "transparent"; + static constexpr std::string_view GOLD = "#FFD700"; + static constexpr std::string_view BLUE = "#0B71C1"; + static AchievementManager* GetInstance(); void Init(); void SetUpdateCallback(UpdateCallback callback); diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index b6eeb09897..63d3375d9b 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -119,7 +119,13 @@ void AchievementHeaderWidget::UpdateData() m_game_icon->setPixmap(QPixmap::fromImage(i_game_icon) .scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); m_game_icon->adjustSize(); - m_game_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent")); + 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; + m_game_icon->setStyleSheet( + QStringLiteral("border: 4px solid %1").arg(QString::fromStdString(std::string(color)))); m_game_icon->setVisible(true); } } diff --git a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp index 8879f940f2..0ec17930c2 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp @@ -30,6 +30,8 @@ #include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/Settings.h" +static constexpr bool hardcore_mode_enabled = false; + AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget(parent) { m_common_box = new QGroupBox(); @@ -57,22 +59,27 @@ AchievementProgressWidget::CreateAchievementBox(const rc_api_achievement_definit QLabel* a_badge = new QLabel(); const auto unlock_status = AchievementManager::GetInstance()->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 != "") { @@ -82,6 +89,8 @@ AchievementProgressWidget::CreateAchievementBox(const rc_api_achievement_definit 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)))); } } From 63c407ad7cfa082aa920bcbca39ee477b4aeb030 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 15 Jun 2023 04:49:56 -0400 Subject: [PATCH 6/6] Add toggle button for badges to achievement settings Button toggles whether badges are downloaded. When turned on, makes a call to AchievementManager to download badges. --- .../Achievements/AchievementSettingsWidget.cpp | 18 ++++++++++++++++++ .../Achievements/AchievementSettingsWidget.h | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp index f82c27925d..fb8b0cb662 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp @@ -75,6 +75,11 @@ void AchievementSettingsWidget::CreateLayout() "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_badges_enabled_input = new ToolTipCheckBox(tr("Enable Achievement Badges")); + m_common_badges_enabled_input->SetDescription( + 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 " @@ -92,6 +97,7 @@ void AchievementSettingsWidget::CreateLayout() 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_badges_enabled_input); m_common_layout->addWidget(m_common_unofficial_enabled_input); m_common_layout->addWidget(m_common_encore_enabled_input); @@ -111,6 +117,8 @@ void AchievementSettingsWidget::ConnectWidgets() &AchievementSettingsWidget::ToggleLeaderboards); connect(m_common_rich_presence_enabled_input, &QCheckBox::toggled, this, &AchievementSettingsWidget::ToggleRichPresence); + 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, @@ -157,6 +165,9 @@ void AchievementSettingsWidget::LoadSettings() ->setChecked(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); SignalBlocking(m_common_rich_presence_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); @@ -176,6 +187,7 @@ void AchievementSettingsWidget::SaveSettings() m_common_leaderboards_enabled_input->isChecked()); Config::SetBaseOrCurrent(Config::RA_RICH_PRESENCE_ENABLED, m_common_rich_presence_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()); @@ -224,6 +236,12 @@ void AchievementSettingsWidget::ToggleRichPresence() AchievementManager::GetInstance()->ActivateDeactivateRichPresence(); } +void AchievementSettingsWidget::ToggleBadges() +{ + SaveSettings(); + AchievementManager::GetInstance()->FetchBadges(); +} + void AchievementSettingsWidget::ToggleUnofficial() { SaveSettings(); diff --git a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h index ed23c53e7a..c086b3380b 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h +++ b/Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.h @@ -36,7 +36,7 @@ private: void ToggleLeaderboards(); void ToggleRichPresence(); void ToggleHardcore(); - void ToggleBadgeIcons(); + void ToggleBadges(); void ToggleUnofficial(); void ToggleEncore(); @@ -55,6 +55,7 @@ private: ToolTipCheckBox* m_common_achievements_enabled_input; ToolTipCheckBox* m_common_leaderboards_enabled_input; ToolTipCheckBox* m_common_rich_presence_enabled_input; + ToolTipCheckBox* m_common_badges_enabled_input; ToolTipCheckBox* m_common_unofficial_enabled_input; ToolTipCheckBox* m_common_encore_enabled_input; };