From cba4bb4ab257d369f421c7e78b7fa0965c7fb826 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Wed, 21 Sep 2022 22:02:54 +1000 Subject: [PATCH] Achievements: Show primed/challenge-in-progress achievements --- src/core/achievements.h | 6 +- src/core/system.cpp | 9 ++- src/frontend-common/achievements.cpp | 103 ++++++++++++++++++++++---- src/frontend-common/achievements.h | 13 ++-- src/frontend-common/common_host.cpp | 2 +- src/frontend-common/fullscreen_ui.cpp | 38 ++++++++++ 6 files changed, 143 insertions(+), 28 deletions(-) diff --git a/src/core/achievements.h b/src/core/achievements.h index 51d575019..7df438460 100644 --- a/src/core/achievements.h +++ b/src/core/achievements.h @@ -9,7 +9,8 @@ namespace Achievements { #ifdef WITH_CHEEVOS // Implemented in Host. -extern bool Reset(); +extern bool ConfirmSystemReset(); +extern void ResetRuntime(); extern bool DoState(StateWrapper& sw); extern void GameChanged(const std::string& path, CDImage* image); @@ -28,10 +29,11 @@ extern bool ChallengeModeActive(); #else // Make noops when compiling without cheevos. -static inline bool Reset() +static inline bool ConfirmSystemReset() { return true; } +static inline void ResetRuntime() {} static inline bool DoState(StateWrapper& sw) { return true; diff --git a/src/core/system.cpp b/src/core/system.cpp index c9a0214db..23c9fa89a 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -898,6 +898,11 @@ void System::ResetSystem() if (!IsValid()) return; +#ifdef WITH_CHEEVOS + if (!Achievements::ConfirmSystemReset()) + return; +#endif + InternalReset(); ResetPerformanceCounters(); ResetThrottler(); @@ -1650,7 +1655,7 @@ bool System::DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool u { #ifdef WITH_CHEEVOS // loading an old state without cheevos, so reset the runtime - Achievements::Reset(); + Achievements::ResetRuntime(); #endif } } @@ -1686,7 +1691,7 @@ void System::InternalReset() ResetPerformanceCounters(); #ifdef WITH_CHEEVOS - Achievements::Reset(); + Achievements::ResetRuntime(); #endif g_gpu->ResetGraphicsAPIState(); diff --git a/src/frontend-common/achievements.cpp b/src/frontend-common/achievements.cpp index 21754bbfa..ba1fe3250 100644 --- a/src/frontend-common/achievements.cpp +++ b/src/frontend-common/achievements.cpp @@ -27,6 +27,7 @@ #include "util/cd_image.h" #include "util/state_wrapper.h" #include +#include #include #include #include @@ -60,6 +61,10 @@ static unsigned PeekMemory(unsigned address, unsigned num_bytes, void* ud); static void ActivateLockedAchievements(); static bool ActivateAchievement(Achievement* achievement); static void DeactivateAchievement(Achievement* achievement); +static void UnlockAchievement(u32 achievement_id, bool add_notification = true); +static void AchievementPrimed(u32 achievement_id); +static void AchievementUnprimed(u32 achievement_id); +static void SubmitLeaderboard(u32 leaderboard_id, int value); static void SendPing(); static void SendPlaying(); static void UpdateRichPresence(); @@ -115,6 +120,7 @@ static std::string s_game_title; static std::string s_game_icon; static std::vector s_achievements; static std::vector s_leaderboards; +static std::atomic s_primed_achievement_count{0}; static bool s_has_rich_presence = false; static std::string s_rich_presence_string; @@ -328,6 +334,7 @@ void Achievements::ClearGameInfo(bool clear_achievements, bool clear_leaderboard DeactivateAchievement(&ach); s_achievements.pop_back(); } + s_primed_achievement_count.store(0, std::memory_order_release); } if (clear_leaderboards) { @@ -459,7 +466,7 @@ void Achievements::UpdateSettings(const Settings& old_config) if (!g_settings.achievements_enabled) { // we're done here - Shutdown(); + OnSystemShutdown(); return; } @@ -491,7 +498,7 @@ void Achievements::UpdateSettings(const Settings& old_config) g_settings.achievements_use_first_disc_from_playlist != old_config.achievements_use_first_disc_from_playlist || g_settings.achievements_rich_presence != old_config.achievements_rich_presence) { - Shutdown(); + OnSystemShutdown(); Initialize(); return; } @@ -584,7 +591,7 @@ void Achievements::SetChallengeMode(bool enabled) GetUserUnlocks(); } -bool Achievements::Shutdown() +bool Achievements::OnSystemShutdown() { #ifdef WITH_RAINTEGRATION if (IsUsingRAIntegration()) @@ -619,29 +626,35 @@ bool Achievements::Shutdown() return true; } -bool Achievements::Reset() +bool Achievements::ConfirmSystemReset() +{ +#ifdef WITH_RAINTEGRATION + if (IsUsingRAIntegration()) + return RA_ConfirmLoadNewRom(false); +#endif + + return true; +} + +void Achievements::ResetRuntime() { #ifdef WITH_RAINTEGRATION if (IsUsingRAIntegration()) { - if (!RA_ConfirmLoadNewRom(false)) - return false; - RA_OnReset(); - return true; + return; } #endif if (!s_active) - return true; + return; std::unique_lock lock(s_achievements_mutex); Log_DevPrint("Resetting rcheevos state..."); rc_runtime_reset(&s_rcheevos_runtime); - return true; } -void Achievements::OnPaused(bool paused) +void Achievements::OnSystemPaused(bool paused) { #ifdef WITH_RAINTEGRATION if (IsUsingRAIntegration()) @@ -1131,6 +1144,7 @@ void Achievements::GetPatchesCallback(s32 status_code, std::string content_type, cheevo.badge_name = defn.badge_name; cheevo.locked = true; cheevo.active = false; + cheevo.primed = false; cheevo.points = defn.points; cheevo.category = static_cast(defn.category); s_achievements.push_back(std::move(cheevo)); @@ -1352,7 +1366,9 @@ void Achievements::GameChanged(const std::string& path, CDImage* image) if (s_http_downloader->HasAnyRequests()) { - Host::DisplayLoadingScreen("Downloading achievements data..."); + if (image && System::IsValid()) + Host::DisplayLoadingScreen("Downloading achievements data..."); + s_http_downloader->WaitForAllRequests(); } @@ -1655,6 +1671,12 @@ void Achievements::DeactivateAchievement(Achievement* achievement) rc_runtime_deactivate_achievement(&s_rcheevos_runtime, achievement->id); achievement->active = false; + if (achievement->primed) + { + achievement->primed = false; + s_primed_achievement_count.fetch_sub(std::memory_order_acq_rel); + } + Log_DevPrintf("Deactivated achievement %s (%u)", achievement->title.c_str(), achievement->id); } @@ -1711,6 +1733,8 @@ void Achievements::SubmitLeaderboardCallback(s32 status_code, std::string conten void Achievements::UnlockAchievement(u32 achievement_id, bool add_notification /* = true*/) { + std::unique_lock lock(s_achievements_mutex); + Achievement* achievement = GetMutableAchievementByID(achievement_id); if (!achievement) { @@ -1783,6 +1807,8 @@ void Achievements::SubmitLeaderboard(u32 leaderboard_id, int value) return; } + std::unique_lock lock(s_achievements_mutex); + s_submitting_lboard_id = leaderboard_id; RAPIRequest request; @@ -1794,6 +1820,28 @@ void Achievements::SubmitLeaderboard(u32 leaderboard_id, int value) request.Send(SubmitLeaderboardCallback); } +void Achievements::AchievementPrimed(u32 achievement_id) +{ + std::unique_lock lock(s_achievements_mutex); + Achievement* achievement = GetMutableAchievementByID(achievement_id); + if (!achievement || achievement->primed) + return; + + achievement->primed = true; + s_primed_achievement_count.fetch_add(std::memory_order_acq_rel); +} + +void Achievements::AchievementUnprimed(u32 achievement_id) +{ + std::unique_lock lock(s_achievements_mutex); + Achievement* achievement = GetMutableAchievementByID(achievement_id); + if (!achievement || !achievement->primed) + return; + + achievement->primed = false; + s_primed_achievement_count.fetch_sub(std::memory_order_acq_rel); +} + std::pair Achievements::GetAchievementProgress(const Achievement& achievement) { std::pair result; @@ -1841,6 +1889,12 @@ std::string Achievements::GetAchievementBadgeURL(const Achievement& achievement) return request.GetURL(); } +u32 Achievements::GetPrimedAchievementCount() +{ + // Relaxed is fine here, worst that happens is we draw the triggers one frame late. + return s_primed_achievement_count.load(std::memory_order_relaxed); +} + void Achievements::CheevosEventHandler(const rc_runtime_event_t* runtime_event) { static const char* events[] = {"RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED", "RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED", @@ -1853,10 +1907,27 @@ void Achievements::CheevosEventHandler(const rc_runtime_event_t* runtime_event) ((unsigned)runtime_event->type >= countof(events)) ? "unknown" : events[(unsigned)runtime_event->type]; Log_DevPrintf("Cheevos Event %s for %u", event_text, runtime_event->id); - if (runtime_event->type == RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED) - UnlockAchievement(runtime_event->id); - else if (runtime_event->type == RC_RUNTIME_EVENT_LBOARD_TRIGGERED) - SubmitLeaderboard(runtime_event->id, runtime_event->value); + switch (runtime_event->type) + { + case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED: + UnlockAchievement(runtime_event->id); + break; + + case RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED: + AchievementPrimed(runtime_event->id); + break; + + case RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED: + AchievementUnprimed(runtime_event->id); + break; + + case RC_RUNTIME_EVENT_LBOARD_TRIGGERED: + SubmitLeaderboard(runtime_event->id, runtime_event->value); + break; + + default: + break; + } } unsigned Achievements::PeekMemory(unsigned address, unsigned num_bytes, void* ud) diff --git a/src/frontend-common/achievements.h b/src/frontend-common/achievements.h index 0f5459c50..91c77cc2b 100644 --- a/src/frontend-common/achievements.h +++ b/src/frontend-common/achievements.h @@ -13,7 +13,7 @@ class CDImage; class StateWrapper; namespace Achievements { -enum class AchievementCategory : u32 +enum class AchievementCategory : u8 { Local = 0, Core = 3, @@ -36,6 +36,7 @@ struct Achievement AchievementCategory category; bool locked; bool active; + bool primed; }; struct Leaderboard @@ -85,13 +86,13 @@ void Initialize(); void UpdateSettings(const Settings& old_config); /// Called when the system is being reset. If it returns false, the reset should be aborted. -bool Reset(); +bool ConfirmSystemReset(); /// Called when the system is being shut down. If Shutdown() returns false, the shutdown should be aborted. -bool Shutdown(); +bool OnSystemShutdown(); /// Called when the system is being paused and resumed. -void OnPaused(bool paused); +void OnSystemPaused(bool paused); /// Called once a frame at vsync time on the CPU thread. void FrameUpdate(); @@ -141,6 +142,7 @@ std::optional TryEnumerateLeaderboardEntries(u32 id, std::function GetAchievementProgress(const Achievement& achievement); @@ -148,9 +150,6 @@ std::string GetAchievementProgressText(const Achievement& achievement); const std::string& GetAchievementBadgePath(const Achievement& achievement, bool download_if_missing = true); std::string GetAchievementBadgeURL(const Achievement& achievement); -void UnlockAchievement(u32 achievement_id, bool add_notification = true); -void SubmitLeaderboard(u32 leaderboard_id, int value); - #ifdef WITH_RAINTEGRATION void SwitchToRAIntegration(); diff --git a/src/frontend-common/common_host.cpp b/src/frontend-common/common_host.cpp index 4fabc836b..fbad4a136 100644 --- a/src/frontend-common/common_host.cpp +++ b/src/frontend-common/common_host.cpp @@ -110,7 +110,7 @@ void CommonHost::Shutdown() #endif #ifdef WITH_CHEEVOS - Achievements::Shutdown(); + Achievements::OnSystemShutdown(); #endif InputManager::CloseSources(); diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index ce53e69f3..1a34751bf 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -430,6 +430,7 @@ static GameListPage s_game_list_page = GameListPage::Grid; ////////////////////////////////////////////////////////////////////////// static void DrawAchievementsWindow(); static void DrawAchievement(const Achievements::Achievement& cheevo); +static void DrawPrimedAchievements(); static void DrawLeaderboardsWindow(); static void DrawLeaderboardListEntry(const Achievements::Leaderboard& lboard); static void DrawLeaderboardEntry(const Achievements::LeaderboardEntry& lbEntry, float rank_column_width, @@ -710,6 +711,12 @@ void FullscreenUI::Render() ImGuiFullscreen::BeginLayout(); +#ifdef WITH_CHEEVOS + // Primed achievements must come first, because we don't want the pause screen to be behind them. + if (Achievements::GetPrimedAchievementCount() > 0) + DrawPrimedAchievements(); +#endif + switch (s_current_main_window) { case MainWindowType::Landing: @@ -5556,6 +5563,37 @@ void FullscreenUI::DrawAchievementsWindow() EndFullscreenWindow(); } +void FullscreenUI::DrawPrimedAchievements() +{ + const ImVec2 image_size(LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT, LAYOUT_MENU_BUTTON_HEIGHT)); + const float spacing = LayoutScale(10.0f); + const float padding = LayoutScale(10.0f); + + const ImGuiIO& io = ImGui::GetIO(); + const float x_advance = image_size.x + spacing; + ImVec2 position(io.DisplaySize.x - padding - image_size.x, io.DisplaySize.y - padding - image_size.y); + + auto lock = Achievements::GetLock(); + Achievements::EnumerateAchievements( + [&image_size, &x_advance, &position](const Achievements::Achievement& achievement) { + if (!achievement.primed) + return true; + + const std::string& badge_path = Achievements::GetAchievementBadgePath(achievement); + if (badge_path.empty()) + return true; + + HostDisplayTexture* badge = GetCachedTextureAsync(badge_path.c_str()); + if (!badge) + return true; + + ImDrawList* dl = ImGui::GetBackgroundDrawList(); + dl->AddImage(badge->GetHandle(), position, position + image_size); + position.x -= x_advance; + return true; + }); +} + bool FullscreenUI::OpenLeaderboardsWindow() { if (!System::IsValid() || !Achievements::HasActiveGame() || Achievements::GetLeaderboardCount() == 0 || !Initialize())