diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index 755091e9e6c..83ae381de7e 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -305,6 +305,74 @@ static void rcheevos_show_subset_completion_placard(const rc_client_subset_t* su rcheevos_show_completion_placard(subset->title, subset->badge_name); } +#if defined(HAVE_GFX_WIDGETS) + +static void rcheevos_show_achievement_popup(const rc_client_achievement_t* cheevo, float rarity) +{ + char title[128], subtitle[96]; + + if (rarity >= 10.0) + snprintf(title, sizeof(title), "%s - %0.2f%%", + msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), rarity); + else if (rarity > 0.0) + snprintf(title, sizeof(title), "%s - %0.2f%%", + msg_hash_to_str(MSG_RARE_ACHIEVEMENT_UNLOCKED), rarity); + else + strlcpy(title, + msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), sizeof(title)); + + snprintf(subtitle, sizeof(subtitle), "%s (%lu)", cheevo->title, (unsigned long)cheevo->points); + + gfx_widgets_push_achievement(title, subtitle, cheevo->badge_name); + + /* if all badges haven't been loaded, preload the next one assuming it will be the next needed */ + if (!rcheevos_locals.badges_loaded) + { + const rc_client_achievement_t* next_locked_achievement = + rc_client_get_next_achievement_info(rcheevos_locals.client, cheevo, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED); + if (next_locked_achievement) + rcheevos_client_download_badge_from_url(next_locked_achievement->badge_url, next_locked_achievement->badge_name); + } +} + +struct rcheevos_retry_achievement_info_t +{ + uint32_t achievement_id; + float rarity; +}; + +static void rcheevos_retry_achievement_popup(retro_task_t* task) +{ + struct rcheevos_retry_achievement_info_t* info = (struct rcheevos_retry_achievement_info_t*)task->user_data; + const rc_client_achievement_t* cheevo = rc_client_get_achievement_info(rcheevos_locals.client, info->achievement_id); + if (!cheevo) + { + /* achievement not found, assume game unloaded and don't show the popup */ + } + else if (task->progress > 4 || rcheevos_is_badge_available(cheevo->badge_name, false)) + { + /* badge is available now, or we've reached the retry limit. show the popup */ + rcheevos_show_achievement_popup(cheevo, info->rarity); + } + else + { + /* second retry in 200ms, third is 400ms, fourth in 800ms. if not available after 1500ms + * (100+200+400+800), then just show the popup with the placeholder. */ + task->progress <<= 1; + task->when = cpu_features_get_time_usec() + 100000 * task->progress; /* first retry in 100ms */ + return; + } + + /* cleanup the user data */ + task->user_data = NULL; + free(info); + + /* mark task as complete so it will get cleaned up */ + task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); +} + +#endif /* HAVE_GFX_WIDGETS */ + static void rcheevos_award_achievement(const rc_client_achievement_t* cheevo) { const settings_t* settings = config_get_ptr(); @@ -318,31 +386,37 @@ static void rcheevos_award_achievement(const rc_client_achievement_t* cheevo) #if defined(HAVE_GFX_WIDGETS) if (gfx_widgets_ready()) { - char title[128], subtitle[96]; float rarity = rc_client_get_hardcore_enabled(rcheevos_locals.client) ? cheevo->rarity_hardcore : cheevo->rarity; - if (rarity >= 10.0) - snprintf(title, sizeof(title), "%s - %0.2f%%", - msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), rarity); - else if (rarity > 0.0) - snprintf(title, sizeof(title), "%s - %0.2f%%", - msg_hash_to_str(MSG_RARE_ACHIEVEMENT_UNLOCKED), rarity); + if (rcheevos_locals.badges_loaded || rcheevos_is_badge_available(cheevo->badge_name, false)) + { + rcheevos_show_achievement_popup(cheevo, rarity); + } else - strlcpy(title, - msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), sizeof(title)); + { + retro_task_t* task; + rcheevos_client_download_badge_from_url(cheevo->badge_url, cheevo->badge_name); - snprintf(subtitle, sizeof(subtitle), "%s (%lu)", cheevo->title, (unsigned long)cheevo->points); + task = task_init(); + if (!task) + { + rcheevos_show_achievement_popup(cheevo, rarity); + } + else + { + struct rcheevos_retry_achievement_info_t* info; + info = (struct rcheevos_retry_achievement_info_t*)malloc(sizeof(*info)); + info->achievement_id = cheevo->id; + info->rarity = rarity; - gfx_widgets_push_achievement(title, subtitle, cheevo->badge_name); + task->handler = rcheevos_retry_achievement_popup; + task->user_data = info; + task->progress = 1; + task->when = cpu_features_get_time_usec() + 100000; /* first retry in 100ms */ - /* if all badges haven't been loaded, preload the next one assuming it will be the next needed */ - if (!rcheevos_locals.badges_loaded) - { - const rc_client_achievement_t* next_locked_achievement = - rc_client_get_next_achievement_info(rcheevos_locals.client, cheevo, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED); - if (next_locked_achievement) - rcheevos_client_download_badge_from_url(next_locked_achievement->badge_url, next_locked_achievement->badge_name); + task_queue_push(task); + } } } else @@ -1432,6 +1506,60 @@ static void rcheevos_client_login_callback(int result, } } +#ifdef HAVE_THREADS + +void rcheevos_download_next_badge(retro_task_t* task) +{ + /* progress: 0 = unlocked images for achievements player hasn't earned + * 1 = locked images for achievements player hasn't earned + * 2 = unlocked images for achievements player has earned + */ + const int bucket = (task->progress == 2) ? RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED : RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED; + const rc_client_achievement_t* first_locked_achievement = + rc_client_get_next_achievement_info(rcheevos_locals.client, task->user_data, bucket); + + while (first_locked_achievement) + { + bool result; + + if (task->progress == 1) + { + char locked_name[24]; + snprintf(locked_name, sizeof(locked_name), "%s_lock", first_locked_achievement->badge_name); + result = rcheevos_client_download_badge_from_url(first_locked_achievement->badge_locked_url, locked_name); + } + else + { + result = rcheevos_client_download_badge_from_url(first_locked_achievement->badge_url, first_locked_achievement->badge_name); + } + + if (result) + break; + + first_locked_achievement = + rc_client_get_next_achievement_info(rcheevos_locals.client, first_locked_achievement, bucket); + } + + if (!first_locked_achievement) + { + if (task->progress == 2 || !rcheevos_is_game_loaded()) + { + /* mark task as complete so it will get cleaned up */ + task_set_flags(task, RETRO_TASK_FLG_FINISHED, true); + return; + } + + task->user_data = NULL; /* restart list */ + task->progress++; + } + + /* wait 10 seconds, then download the next badge */ + task->user_data = (void*)first_locked_achievement; + task->when = cpu_features_get_time_usec() + 10 * 1000000; +} + +#endif + static void rcheevos_finalize_game_load(rc_client_t* client) { settings_t* settings = config_get_ptr(); @@ -1448,6 +1576,9 @@ static void rcheevos_finalize_game_load(rc_client_t* client) const rc_client_achievement_t* first_locked_achievement = NULL; const rc_client_game_t* game = rc_client_get_game_info(client); char badge[32]; +#ifdef HAVE_THREADS + retro_task_t* task; +#endif badge[0] = 'i'; strlcpy(&badge[1], game->badge_name, sizeof(badge) - 1); @@ -1457,6 +1588,17 @@ static void rcheevos_finalize_game_load(rc_client_t* client) first_locked_achievement = rc_client_get_next_achievement_info(client, NULL, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED); if (first_locked_achievement) rcheevos_client_download_badge_from_url(first_locked_achievement->badge_url, first_locked_achievement->badge_name); + +#ifdef HAVE_THREADS + task = task_init(); + if (task) + { + task->handler = rcheevos_download_next_badge; + task->user_data = (void*)first_locked_achievement; + task->when = cpu_features_get_time_usec() + 5*60*1000000; /* five minutes */ + task_queue_push(task); + } +#endif } if (!rc_client_is_processing_required(client)) diff --git a/cheevos/cheevos.h b/cheevos/cheevos.h index b534d4fd9ec..47fdc54a249 100644 --- a/cheevos/cheevos.h +++ b/cheevos/cheevos.h @@ -59,6 +59,7 @@ const char* rcheevos_get_hash(void); int rcheevos_get_richpresence(char *s, size_t len); int rcheevos_get_game_badge_url(char *s, size_t len); uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool download_if_missing); +bool rcheevos_is_badge_available(const char* badge, bool locked); uint8_t* rcheevos_patch_address(unsigned address); diff --git a/cheevos/cheevos_client.c b/cheevos/cheevos_client.c index 4dd42a1689d..93cee7fccd4 100644 --- a/cheevos/cheevos_client.c +++ b/cheevos/cheevos_client.c @@ -478,9 +478,9 @@ bool rcheevos_client_download_badge(rc_client_download_queue_t* queue, return true; } -void rcheevos_client_download_badge_from_url(const char* url, const char* badge_name) +bool rcheevos_client_download_badge_from_url(const char* url, const char* badge_name) { - rcheevos_client_download_badge(NULL, url, badge_name); + return rcheevos_client_download_badge(NULL, url, badge_name); } static void rcheevos_client_fetch_next_badge(rc_client_download_queue_t* queue) diff --git a/cheevos/cheevos_client.h b/cheevos/cheevos_client.h index 703ed5d7438..24ad44a9652 100644 --- a/cheevos/cheevos_client.h +++ b/cheevos/cheevos_client.h @@ -22,7 +22,7 @@ RETRO_BEGIN_DECLS void rcheevos_client_download_placeholder_badge(void); void rcheevos_client_download_achievement_badges(rc_client_t* client); -void rcheevos_client_download_badge_from_url(const char* url, const char* badge_name); +bool rcheevos_client_download_badge_from_url(const char* url, const char* badge_name); void rcheevos_client_server_call(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); diff --git a/cheevos/cheevos_menu.c b/cheevos/cheevos_menu.c index 94b0aae41e1..45cdb8f093c 100644 --- a/cheevos/cheevos_menu.c +++ b/cheevos/cheevos_menu.c @@ -614,9 +614,30 @@ static void rcheevos_client_download_achievement_badge(const char* badge_name, b } } +static void rcheevos_get_local_badge_filename(char badge_file[], size_t badge_file_size, const char* badge, bool locked) +{ + size_t _len = strlcpy(badge_file, badge, badge_file_size); + if (locked) + _len += strlcpy(badge_file + _len, "_lock", badge_file_size - _len); + strlcpy(badge_file + _len, FILE_PATH_PNG_EXTENSION, badge_file_size - _len); +} + +bool rcheevos_is_badge_available(const char* badge, bool locked) +{ + char badge_file[24]; + char fullpath[PATH_MAX_LENGTH]; + + rcheevos_get_local_badge_filename(badge_file, sizeof(badge_file), badge, locked); + + fill_pathname_application_special(fullpath, sizeof(fullpath), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); + fill_pathname_join(fullpath, fullpath, badge_file, sizeof(fullpath)); + + return path_is_valid(fullpath); +} + uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool download_if_missing) { - size_t _len; char badge_file[24]; char fullpath[PATH_MAX_LENGTH]; uintptr_t tex = 0; @@ -634,9 +655,7 @@ uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool downlo return 0; #endif - _len = strlcpy(badge_file, badge, sizeof(badge_file)); - _len += strlcpy(badge_file + _len, locked ? "_lock" : "", sizeof(badge_file) - _len); - strlcpy(badge_file + _len, FILE_PATH_PNG_EXTENSION, sizeof(badge_file) - _len); + rcheevos_get_local_badge_filename(badge_file, sizeof(badge_file), badge, locked); fill_pathname_application_special(fullpath, sizeof(fullpath), APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES);