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;