diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 77661bb43e..1cf02daa5d 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); @@ -426,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); } @@ -447,10 +714,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 +730,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."); @@ -944,4 +1217,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..f7866f0926 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -56,6 +56,13 @@ public: using FormattedValue = std::array; static constexpr size_t RP_SIZE = 256; using RichPresence = std::array; + using Badge = std::vector; + + struct BadgeStatus + { + std::string name = ""; + Badge badge{}; + }; struct UnlockStatus { @@ -68,8 +75,14 @@ public: } remote_unlock_status = UnlockType::LOCKED; u32 session_unlock_count = 0; u32 points = 0; + BadgeStatus locked_badge; + 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); @@ -83,6 +96,7 @@ public: void ActivateDeactivateAchievements(); void ActivateDeactivateLeaderboards(); void ActivateDeactivateRichPresence(); + void FetchBadges(); void DoFrame(); u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud); @@ -91,10 +105,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(); @@ -129,6 +145,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{}; @@ -136,16 +153,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(), }; diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index 8e7977b504..63d3375d9b 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,89 @@ 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(); + 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); + } + } + } + + 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 diff --git a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp index a883f0d675..0ec17930c2 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp @@ -30,12 +30,17 @@ #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(); m_common_layout = new QVBoxLayout(); - UpdateData(); + { + std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()}; + UpdateData(); + } m_common_box->setLayout(m_common_layout); @@ -51,12 +56,53 @@ 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; + 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; AchievementManager::GetInstance()->GetAchievementProgress(achievement->id, &value, &target); @@ -77,7 +123,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); 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; };