diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index a566ae6a63..84329b1f10 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -14,6 +14,8 @@ #include "Core/Core.h" #include "DiscIO/Volume.h" +static constexpr bool hardcore_mode_enabled = false; + AchievementManager* AchievementManager::GetInstance() { static AchievementManager s_instance; @@ -138,6 +140,7 @@ void AchievementManager::CloseGame() m_is_game_loaded = false; m_game_id = 0; m_queue.Cancel(); + m_unlock_map.clear(); } void AchievementManager::Logout() @@ -212,6 +215,52 @@ AchievementManager::ResponseType AchievementManager::FetchGameData() rc_api_process_fetch_game_data_response); } +void AchievementManager::ActivateDeactivateAchievement(AchievementId id, bool enabled, + bool unofficial, bool encore) +{ + auto it = m_unlock_map.find(id); + if (it == m_unlock_map.end()) + return; + const UnlockStatus& status = it->second; + u32 index = status.game_data_index; + bool active = (rc_runtime_get_achievement(&m_runtime, id) != nullptr); + + // Deactivate achievements if game is not loaded + bool activate = m_is_game_loaded; + // Activate achievements only if achievements are enabled + if (activate && !enabled) + activate = false; + // Deactivate if achievement is unofficial, unless unofficial achievements are enabled + if (activate && !unofficial && + m_game_data.achievements[index].category == RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL) + { + activate = false; + } + // If encore mode is on, activate/deactivate regardless of current unlock status + if (activate && !encore) + { + // Encore is off, achievement has been unlocked in this session, deactivate + activate = (status.session_unlock_count == 0); + // Encore is off, achievement has been hardcore unlocked on site, deactivate + if (activate && status.remote_unlock_status == UnlockStatus::UnlockType::HARDCORE) + activate = false; + // Encore is off, hardcore is off, achievement has been softcore unlocked on site, deactivate + if (activate && !hardcore_mode_enabled && + status.remote_unlock_status == UnlockStatus::UnlockType::SOFTCORE) + { + activate = false; + } + } + + if (!active && activate) + { + rc_runtime_activate_achievement(&m_runtime, id, m_game_data.achievements[index].definition, + nullptr, 0); + } + if (active && !activate) + rc_runtime_deactivate_achievement(&m_runtime, id); +} + // Every RetroAchievements API call, with only a partial exception for fetch_image, follows // the same design pattern (here, X is the name of the call): // Create a specific rc_api_X_request_t struct and populate with the necessary values diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index ecbf236d60..511a573d23 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,8 @@ #include "Common/Event.h" #include "Common/WorkQueueThread.h" +using AchievementId = u32; + class AchievementManager { public: @@ -50,6 +53,8 @@ private: ResponseType StartRASession(); ResponseType FetchGameData(); + void ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore); + template ResponseType Request(RcRequest rc_request, RcResponse* rc_response, const std::function& init_request, @@ -61,6 +66,19 @@ private: rc_api_fetch_game_data_response_t m_game_data{}; bool m_is_game_loaded = false; + struct UnlockStatus + { + AchievementId game_data_index = 0; + enum class UnlockType + { + LOCKED, + SOFTCORE, + HARDCORE + } remote_unlock_status = UnlockType::LOCKED; + int session_unlock_count = 0; + }; + std::unordered_map m_unlock_map; + Common::WorkQueueThread> m_queue; }; // class AchievementManager diff --git a/Source/Core/Core/Config/AchievementSettings.cpp b/Source/Core/Core/Config/AchievementSettings.cpp index f7ec642881..92a761f6a6 100644 --- a/Source/Core/Core/Config/AchievementSettings.cpp +++ b/Source/Core/Core/Config/AchievementSettings.cpp @@ -13,4 +13,9 @@ namespace Config const Info RA_ENABLED{{System::Achievements, "Achievements", "Enabled"}, false}; const Info RA_USERNAME{{System::Achievements, "Achievements", "Username"}, ""}; const Info RA_API_TOKEN{{System::Achievements, "Achievements", "ApiToken"}, ""}; +const Info RA_ACHIEVEMENTS_ENABLED{ + {System::Achievements, "Achievements", "AchievementsEnabled"}, false}; +const Info RA_UNOFFICIAL_ENABLED{{System::Achievements, "Achievements", "UnofficialEnabled"}, + false}; +const Info RA_ENCORE_ENABLED{{System::Achievements, "Achievements", "EncoreEnabled"}, false}; } // namespace Config diff --git a/Source/Core/Core/Config/AchievementSettings.h b/Source/Core/Core/Config/AchievementSettings.h index 8f26769d7f..13da23eafa 100644 --- a/Source/Core/Core/Config/AchievementSettings.h +++ b/Source/Core/Core/Config/AchievementSettings.h @@ -11,4 +11,7 @@ namespace Config extern const Info RA_ENABLED; extern const Info RA_USERNAME; extern const Info RA_API_TOKEN; +extern const Info RA_ACHIEVEMENTS_ENABLED; +extern const Info RA_UNOFFICIAL_ENABLED; +extern const Info RA_ENCORE_ENABLED; } // namespace Config