diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index fd4aabbbac09..21008545b636 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -102,7 +102,9 @@ static rcheevos_locals_t rcheevos_locals = true, /* hardcore_allowed */ false,/* hardcore_requires_reload */ false,/* hardcore_being_enabled */ - true /* core_supports */ + true, /* core_supports */ + false,/* badges_loaded */ + false /* badges_loading */ }; rcheevos_locals_t* get_rcheevos_locals(void) @@ -227,25 +229,24 @@ bool rcheevos_is_pause_allowed(void) return rc_client_can_pause(rcheevos_locals.client, NULL); } -static void rcheevos_show_mastery_placard(void) +static void rcheevos_show_completion_placard(const char* title, const char* badge_name) { const settings_t* settings = config_get_ptr(); if (settings->bools.cheevos_visibility_mastery) { - const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals.client); - char title[256]; - size_t _len = snprintf(title, sizeof(title), + char message[256]; + size_t _len = snprintf(message, sizeof(message), msg_hash_to_str(rc_client_get_hardcore_enabled(rcheevos_locals.client) ? MSG_CHEEVOS_MASTERED_GAME : MSG_CHEEVOS_COMPLETED_GAME), - game->title); - title[sizeof(title) - 1] = '\0'; + title); + message[sizeof(message) - 1] = '\0'; #if defined (HAVE_GFX_WIDGETS) if (gfx_widgets_ready()) { char msg[128]; - char badge_name[32]; + char badge[32]; const char* displayname = rc_client_get_user_info(rcheevos_locals.client)->display_name; const bool content_runtime_log = settings->bools.content_runtime_log; const bool content_runtime_log_aggr = settings->bools.content_runtime_log_aggregate; @@ -276,9 +277,10 @@ static void rcheevos_show_mastery_placard(void) } } - __len = strlcpy(badge_name, "i", sizeof(badge_name)); - strlcpy(badge_name + __len, game->badge_name, sizeof(badge_name) - __len); - gfx_widgets_push_achievement(title, msg, badge_name); + /* badge image = "iBADGENAME" */ + badge[0] = 'i'; + strlcpy(&badge[1], badge_name, sizeof(badge) - 1); + gfx_widgets_push_achievement(title, msg, badge); } else #endif @@ -287,6 +289,22 @@ static void rcheevos_show_mastery_placard(void) } } +static void rcheevos_show_mastery_placard(void) +{ + const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals.client); + rcheevos_show_completion_placard(game->title, game->badge_name); +} + +static void rcheevos_show_subset_completion_placard(const rc_client_subset_t* subset) +{ + char badge[32]; + badge[0] = 'i'; + strlcpy(&badge[1], subset->badge_name, sizeof(badge) - 1); + rcheevos_client_download_badge_from_url(subset->badge_url, badge); + + rcheevos_show_completion_placard(subset->title, subset->badge_name); +} + static void rcheevos_award_achievement(const rc_client_achievement_t* cheevo) { const settings_t* settings = config_get_ptr(); @@ -588,6 +606,9 @@ static void rcheevos_client_event_handler(const rc_client_event_t* event, rc_cli case RC_CLIENT_EVENT_GAME_COMPLETED: rcheevos_show_mastery_placard(); break; + case RC_CLIENT_EVENT_SUBSET_COMPLETED: + rcheevos_show_subset_completion_placard(event->subset); + break; case RC_CLIENT_EVENT_SERVER_ERROR: rcheevos_server_error(event->server_error->api, event->server_error->error_message); break; @@ -703,6 +724,8 @@ bool rcheevos_unload(void) if (was_loaded) { + rcheevos_locals.badges_loaded = rcheevos_locals.badges_loading = false; + #ifdef HAVE_MENU rcheevos_menu_reset_badges(); @@ -1268,9 +1291,8 @@ static void rcheevos_show_game_placard(void) if (gfx_widgets_ready()) { char badge_name[32]; - size_t __len = strlcpy(badge_name, "i", sizeof(badge_name)); - strlcpy(badge_name + __len, game->badge_name, - sizeof(badge_name) - __len); + badge_name[0] = 'i'; + strlcpy(&badge_name[1], game->badge_name, sizeof(badge_name) - 1); gfx_widgets_push_achievement(game->title, msg, badge_name); } else @@ -1406,13 +1428,21 @@ static void rcheevos_finalize_game_load(rc_client_t* client) settings_t* settings = config_get_ptr(); bool want_badges = settings->bools.cheevos_badges_enable; #if !defined(HAVE_GFX_WIDGETS) - /* Then badges are only needed for xmb and ozone menus */ + /* Then badges are only needed for xmb, ozone, and rgui menus */ want_badges = want_badges && ( string_is_equal(settings->arrays.menu_driver, "xmb") - || string_is_equal(settings->arrays.menu_driver, "ozone")); + || string_is_equal(settings->arrays.menu_driver, "ozone") + || string_is_equal(settings->arrays.menu_driver, "rgui")); #endif - if (want_badges) - rcheevos_client_download_achievement_badges(client); + if (want_badges) /* prefetch the game badge */ + { + const rc_client_game_t* game = rc_client_get_game_info(client); + char badge[32]; + + badge[0] = 'i'; + strlcpy(&badge[1], game->badge_name, sizeof(badge) - 1); + rcheevos_client_download_badge_from_url(game->badge_url, badge); + } if (!rc_client_is_processing_required(client)) { diff --git a/cheevos/cheevos_client.c b/cheevos/cheevos_client.c index d9aa33756916..5e255e83a546 100644 --- a/cheevos/cheevos_client.c +++ b/cheevos/cheevos_client.c @@ -34,6 +34,11 @@ #include "../network/presence.h" #endif +#ifdef HAVE_MENU +#include "cheevos_menu.h" +#include "../menu/menu_driver.h" +#endif + #include "../deps/rcheevos/include/rc_api_runtime.h" #include "../deps/rcheevos/include/rc_api_user.h" @@ -405,6 +410,12 @@ static void rcheevos_client_download_task_callback(retro_task_t* task, { CHEEVOS_LOG(RCHEEVOS_TAG "Error writing %s\n", callback_data->badge_fullpath); } +#ifdef HAVE_MENU + else + { + rcheevos_menu_update_badge_references(callback_data->badge_name); + } +#endif if (callback_data->queue) { @@ -490,7 +501,15 @@ static void rcheevos_client_fetch_next_badge(rc_client_download_queue_t* queue) #endif /* if the game is no longer loaded, stop processing the queue */ if (queue->game != rc_client_get_game_info(queue->client)) - queue->pass = 2; + queue->pass = 3; + +#ifdef HAVE_MENU + if (!(menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE)) + { + /* menu is no longer open, stop processing the queue so we don't interrupt emulation */ + queue->pass = 3; + } +#endif while (queue->pass < 2) { @@ -514,19 +533,19 @@ static void rcheevos_client_fetch_next_badge(rc_client_download_queue_t* queue) if (!achievement->badge_name[0]) continue; - if (queue->pass == 0) + if (queue->pass == 1) { - /* First pass - get all unlocked badges */ + /* Second pass - get all unlocked badges */ url = achievement->badge_url; next_badge = achievement->badge_name; } - /* Second pass - don't need locked badge for + /* First pass - don't need locked badge for * achievement player has already unlocked */ else if (achievement->unlock_time) continue; else { - /* Second pass - get locked badge */ + /* First pass - get locked badge */ url = achievement->badge_locked_url; snprintf(badge_name, sizeof(badge_name), "%s_lock", achievement->badge_name); next_badge = badge_name; @@ -538,7 +557,15 @@ static void rcheevos_client_fetch_next_badge(rc_client_download_queue_t* queue) if (!next_badge) { if (--queue->outstanding_requests == 0) - done = true; + { + if (queue->pass == 2) + { + /* all badges successfully loaded */ + get_rcheevos_locals()->badges_loaded = true; + } + + done = true; /* destroy queue */ + } } #ifdef HAVE_THREADS @@ -564,6 +591,8 @@ static void rcheevos_client_fetch_next_badge(rc_client_download_queue_t* queue) } rc_client_destroy_achievement_list(queue->list); + get_rcheevos_locals()->badges_loading = false; + #ifdef HAVE_THREADS slock_free(queue->lock); #endif @@ -580,18 +609,6 @@ void rcheevos_client_download_placeholder_badge(void) rcheevos_client_download_badge(NULL, url, "00000"); } -void rcheevos_client_download_game_badge(const rc_client_game_t* game) -{ - char url[256] = ""; - char badge_name[16]; - - if (game && rc_client_game_get_image_url(game, url, sizeof(url)) == RC_OK) - { - snprintf(badge_name, sizeof(badge_name), "i%s", game->badge_name); - rcheevos_client_download_badge(NULL, url, badge_name); - } -} - void rcheevos_client_download_achievement_badges(rc_client_t* client) { size_t i; diff --git a/cheevos/cheevos_client.h b/cheevos/cheevos_client.h index 21f5b94a5f90..703ed5d7438b 100644 --- a/cheevos/cheevos_client.h +++ b/cheevos/cheevos_client.h @@ -21,7 +21,6 @@ RETRO_BEGIN_DECLS void rcheevos_client_download_placeholder_badge(void); -void rcheevos_client_download_game_badge(const rc_client_game_t* game); void rcheevos_client_download_achievement_badges(rc_client_t* client); void rcheevos_client_download_badge_from_url(const char* url, const char* badge_name); diff --git a/cheevos/cheevos_locals.h b/cheevos/cheevos_locals.h index 69caa7fecfa4..3f70f30399fc 100644 --- a/cheevos/cheevos_locals.h +++ b/cheevos/cheevos_locals.h @@ -102,6 +102,8 @@ typedef struct rcheevos_locals_t bool hardcore_being_enabled; /* allows callers to detect hardcore mode while it's being enabled */ bool core_supports; /* false if core explicitly disables achievements */ + bool badges_loaded; /* true once all badges have been loaded */ + bool badges_loading; /* true if the download queue is running */ } rcheevos_locals_t; rcheevos_locals_t* get_rcheevos_locals(void); diff --git a/cheevos/cheevos_menu.c b/cheevos/cheevos_menu.c index df8366d29109..94b0aae41e14 100644 --- a/cheevos/cheevos_menu.c +++ b/cheevos/cheevos_menu.c @@ -244,6 +244,31 @@ uintptr_t rcheevos_menu_get_badge_texture(unsigned menu_offset) return 0; } +void rcheevos_menu_update_badge_references(const char* badge_name) +{ + rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); + unsigned i; + char unlocked_badge_name[8]; + const size_t badge_name_len = strlen(badge_name); + if (badge_name_len > 6 && badge_name_len < sizeof(unlocked_badge_name) + 5 && + strcmp(&badge_name[badge_name_len - 5], "_lock") == 0) + { + memcpy(unlocked_badge_name, badge_name, badge_name_len - 5); + unlocked_badge_name[badge_name_len - 5] = '\0'; + badge_name = unlocked_badge_name; + } + + for (i = 0; i < rcheevos_locals->menuitem_count; ++i) + { + rcheevos_menuitem_t* menuitem = &rcheevos_locals->menuitems[i]; + if (menuitem->menu_badge_grayscale >= 2 && /* using placeholder */ + strncmp(menuitem->achievement->badge_name, badge_name, badge_name_len) == 0) + { + rcheevos_menu_update_badge(menuitem, false); + } + } +} + void rcheevos_menu_populate_hardcore_pause_submenu(void* data, bool cheevos_hardcore_mode_enable) { const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); @@ -332,6 +357,11 @@ void rcheevos_menu_populate(void* data, bool cheevos_enable, MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU, MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS, 0, 0, NULL); } + + if (!rcheevos_locals->badges_loaded && !rcheevos_locals->badges_loading) { + rcheevos_locals->badges_loading = true; + rcheevos_client_download_achievement_badges(rcheevos_locals->client); + } } for (i = 0; i < list->num_buckets; i++) @@ -396,7 +426,7 @@ void rcheevos_menu_populate(void* data, bool cheevos_enable, break; } - rcheevos_menu_update_badge(menuitem, true); + rcheevos_menu_update_badge(menuitem, false); } } @@ -518,6 +548,31 @@ void rcheevos_menu_populate(void* data, bool cheevos_enable, #endif /* HAVE_MENU */ +static void rcheevos_client_download_subset_badge(const char* badge_name) +{ + rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); + + const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals->client); + if (game && strcmp(game->badge_name, &badge_name[1]) == 0) + { + rcheevos_client_download_badge_from_url(game->badge_url, badge_name); + } + else + { + rc_client_subset_list_t* subset_list = rc_client_create_subset_list(rcheevos_locals->client); + uint32_t i; + for (i = 0; i < subset_list->num_subsets; ++i) + { + if (strcmp(subset_list->subsets[i]->badge_name, &badge_name[1]) == 0) + { + rcheevos_client_download_badge_from_url(subset_list->subsets[i]->badge_url, badge_name); + break; + } + } + rc_client_destroy_subset_list(subset_list); + } +} + static void rcheevos_client_download_achievement_badge(const char* badge_name, bool locked) { /* have to find the achievement associated to badge_name, then fetch either badge_url @@ -591,18 +646,10 @@ uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool downlo { if (download_if_missing) { - const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); - if (badge[0] == 'i') - { - const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals->client); - if (game && strcmp(game->badge_name, &badge[1]) == 0) - rcheevos_client_download_badge_from_url(game->badge_url, badge); - } + rcheevos_client_download_subset_badge(badge); else - { rcheevos_client_download_achievement_badge(badge, locked); - } } return 0; } diff --git a/cheevos/cheevos_menu.h b/cheevos/cheevos_menu.h index 64725ebda7b0..0454359f7274 100644 --- a/cheevos/cheevos_menu.h +++ b/cheevos/cheevos_menu.h @@ -35,6 +35,7 @@ size_t rcheevos_menu_get_state(unsigned menu_offset, char *s, size_t len); size_t rcheevos_menu_get_sublabel(unsigned menu_offset, char *s, size_t len); uintptr_t rcheevos_menu_get_badge_texture(unsigned menu_offset); void rcheevos_menu_reset_badges(void); +void rcheevos_menu_update_badge_references(const char* badge_name); RETRO_END_DECLS diff --git a/gfx/widgets/gfx_widget_leaderboard_display.c b/gfx/widgets/gfx_widget_leaderboard_display.c index 2456495d0aa6..661f4c1c7f3c 100644 --- a/gfx/widgets/gfx_widget_leaderboard_display.c +++ b/gfx/widgets/gfx_widget_leaderboard_display.c @@ -40,6 +40,8 @@ struct challenge_display_info { unsigned id; uintptr_t image; + char badge_name[8]; + retro_time_t badge_retry; }; struct progress_tracker_info @@ -47,6 +49,8 @@ struct progress_tracker_info uintptr_t image; unsigned width; char display[32]; + char badge_name[8]; + retro_time_t badge_retry; retro_time_t show_until; }; @@ -236,6 +240,17 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) if (dispctx && dispctx->blend_end) dispctx->blend_end(video_info->userdata); } + + /* see if real icon is available for next frame */ + { + const retro_time_t next_try = state->challenge_info[i].badge_retry; + const retro_time_t now = cpu_features_get_time_usec(); + if (next_try == 0 || now > next_try) + { + state->challenge_info[i].badge_retry = now + 250000; + state->challenge_info[i].image = rcheevos_get_badge_texture(state->challenge_info[i].badge_name, false, false); + } + } } else { @@ -315,6 +330,16 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) if (dispctx && dispctx->blend_end) dispctx->blend_end(video_info->userdata); } + + /* see if real icon is available for next frame */ + { + const retro_time_t next_try = state->challenge_info[i].badge_retry; + if (next_try == 0 || now > next_try) + { + state->progress_tracker.badge_retry = now + 250000; + state->progress_tracker.image = rcheevos_get_badge_texture(state->progress_tracker.badge_name, true, false); + } + } } else { @@ -554,6 +579,8 @@ void gfx_widgets_set_challenge_display(unsigned id, const char* badge) } state->challenge_info[i].image = badge_id; + strlcpy(state->challenge_info[i].badge_name, badge, sizeof(state->challenge_info[i].badge_name)); + state->challenge_info[i].badge_retry = 0; } } @@ -579,8 +606,11 @@ void gfx_widget_set_achievement_progress(const char* badge, const char* progress else { /* show indicator */ - state->progress_tracker.show_until = cpu_features_get_time_usec() + CHEEVO_PROGRESS_TRACKER_DURATION * 1000; + const retro_time_t now = cpu_features_get_time_usec(); + state->progress_tracker.show_until = now + CHEEVO_PROGRESS_TRACKER_DURATION * 1000; state->progress_tracker.image = rcheevos_get_badge_texture(badge, true, true); + strlcpy(state->progress_tracker.badge_name, badge, sizeof(state->progress_tracker.badge_name)); + state->progress_tracker.badge_retry = now + 100000; snprintf(state->progress_tracker.display, sizeof(state->progress_tracker.display), "%s", progress); state->progress_tracker.width = (uint16_t)font_driver_get_message_width(