diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index ac9de0344e..a8c84706e5 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -1180,6 +1180,22 @@ static void rcheevos_lboard_updated(rcheevos_ralboard_t* lboard, int value, gfx_widgets_set_leaderboard_display(lboard->id, buffer); } } + +static void rcheevos_challenge_started(rcheevos_racheevo_t* cheevo, int value, + bool widgets_ready) +{ + settings_t* settings = config_get_ptr(); + if (cheevo && widgets_ready && settings->bools.cheevos_challenge_indicators) + gfx_widgets_set_challenge_display(cheevo->id, cheevo->badge); +} + +static void rcheevos_challenge_ended(rcheevos_racheevo_t* cheevo, int value, + bool widgets_ready) +{ + if (cheevo && widgets_ready) + gfx_widgets_set_challenge_display(cheevo->id, NULL); +} + #endif int rcheevos_get_richpresence(char buffer[], int buffer_size) @@ -1704,6 +1720,20 @@ static void rcheevos_runtime_event_handler(const rc_runtime_event_t* runtime_eve switch (runtime_event->type) { +#if defined(HAVE_GFX_WIDGETS) + case RC_RUNTIME_EVENT_LBOARD_UPDATED: + rcheevos_lboard_updated(rcheevos_find_lboard(runtime_event->id), runtime_event->value, widgets_ready); + break; + + case RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED: + rcheevos_challenge_started(rcheevos_find_cheevo(runtime_event->id), runtime_event->value, widgets_ready); + break; + + case RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED: + rcheevos_challenge_ended(rcheevos_find_cheevo(runtime_event->id), runtime_event->value, widgets_ready); + break; +#endif + case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED: rcheevos_award_achievement(&rcheevos_locals, rcheevos_find_cheevo(runtime_event->id), widgets_ready); break; @@ -1712,12 +1742,6 @@ static void rcheevos_runtime_event_handler(const rc_runtime_event_t* runtime_eve rcheevos_lboard_started(rcheevos_find_lboard(runtime_event->id), runtime_event->value, widgets_ready); break; -#if defined(HAVE_GFX_WIDGETS) - case RC_RUNTIME_EVENT_LBOARD_UPDATED: - rcheevos_lboard_updated(rcheevos_find_lboard(runtime_event->id), runtime_event->value, widgets_ready); - break; -#endif - case RC_RUNTIME_EVENT_LBOARD_CANCELED: rcheevos_lboard_canceled(rcheevos_find_lboard(runtime_event->id), widgets_ready); diff --git a/configuration.c b/configuration.c index 0d5f9a835b..e8dc20a76e 100644 --- a/configuration.c +++ b/configuration.c @@ -1743,6 +1743,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("cheevos_enable", &settings->bools.cheevos_enable, true, DEFAULT_CHEEVOS_ENABLE, false); SETTING_BOOL("cheevos_test_unofficial", &settings->bools.cheevos_test_unofficial, true, false, false); SETTING_BOOL("cheevos_hardcore_mode_enable", &settings->bools.cheevos_hardcore_mode_enable, true, false, false); + SETTING_BOOL("cheevos_challenge_indicators", &settings->bools.cheevos_challenge_indicators, true, true, false); SETTING_BOOL("cheevos_richpresence_enable", &settings->bools.cheevos_richpresence_enable, true, true, false); SETTING_BOOL("cheevos_unlock_sound_enable", &settings->bools.cheevos_unlock_sound_enable, true, false, false); SETTING_BOOL("cheevos_verbose_enable", &settings->bools.cheevos_verbose_enable, true, false, false); diff --git a/configuration.h b/configuration.h index 5ea88e1ff1..81a2f3cd81 100644 --- a/configuration.h +++ b/configuration.h @@ -735,6 +735,7 @@ typedef struct settings bool cheevos_auto_screenshot; bool cheevos_start_active; bool cheevos_unlock_sound_enable; + bool cheevos_challenge_indicators; /* Camera */ bool camera_allow; diff --git a/deps/rcheevos/include/rc_runtime.h b/deps/rcheevos/include/rc_runtime.h index 695d0a3703..56fa62312d 100644 --- a/deps/rcheevos/include/rc_runtime.h +++ b/deps/rcheevos/include/rc_runtime.h @@ -102,7 +102,7 @@ int rc_runtime_format_lboard_value(char* buffer, int size, int value, int format int rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx); -int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L); +int rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, unsigned buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L); enum { RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */ @@ -115,7 +115,8 @@ enum { RC_RUNTIME_EVENT_LBOARD_UPDATED, RC_RUNTIME_EVENT_LBOARD_TRIGGERED, RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED, - RC_RUNTIME_EVENT_LBOARD_DISABLED + RC_RUNTIME_EVENT_LBOARD_DISABLED, + RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED }; typedef struct rc_runtime_event_t { diff --git a/deps/rcheevos/src/rcheevos/rc_internal.h b/deps/rcheevos/src/rcheevos/rc_internal.h index 928afaaf14..98ee2c7789 100644 --- a/deps/rcheevos/src/rcheevos/rc_internal.h +++ b/deps/rcheevos/src/rcheevos/rc_internal.h @@ -119,6 +119,7 @@ unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_stat unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type); void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse); +int rc_trigger_state_active(int state); rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value); int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state); diff --git a/deps/rcheevos/src/rcheevos/runtime.c b/deps/rcheevos/src/rcheevos/runtime.c index 4d1a0a8d3e..2e1536cf44 100644 --- a/deps/rcheevos/src/rcheevos/runtime.c +++ b/deps/rcheevos/src/rcheevos/runtime.c @@ -229,19 +229,13 @@ int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id return 0; } - switch (trigger->state) - { - case RC_TRIGGER_STATE_DISABLED: - case RC_TRIGGER_STATE_INACTIVE: - case RC_TRIGGER_STATE_TRIGGERED: - /* don't report measured information for inactive triggers */ - *measured_value = *measured_target = 0; - break; - - default: - *measured_value = trigger->measured_value; - *measured_target = trigger->measured_target; - break; + if (rc_trigger_state_active(trigger->state)) { + *measured_value = trigger->measured_value; + *measured_target = trigger->measured_target; + } + else { + /* don't report measured information for inactive triggers */ + *measured_value = *measured_target = 0; } return 1; @@ -457,7 +451,7 @@ int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua return RC_OK; } -int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) { +int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L) { if (self->richpresence && self->richpresence->richpresence) return rc_get_richpresence_display_string(self->richpresence->richpresence, buffer, buffersize, peek, peek_ud, L); @@ -465,7 +459,7 @@ int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned return 0; } -void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_peek_t peek, void* ud, lua_State* L) { +void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L) { rc_runtime_event_t runtime_event; int i; @@ -476,7 +470,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha for (i = self->trigger_count - 1; i >= 0; --i) { rc_trigger_t* trigger = self->triggers[i].trigger; - int trigger_state; + int old_state, new_state; if (!trigger) continue; @@ -495,8 +489,18 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha continue; } - trigger_state = trigger->state; - switch (rc_evaluate_trigger(trigger, peek, ud, L)) + old_state = trigger->state; + new_state = rc_evaluate_trigger(trigger, peek, ud, L); + if (new_state == old_state) + continue; + + if (old_state == RC_TRIGGER_STATE_PRIMED) { + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED; + runtime_event.id = self->triggers[i].id; + event_handler(&runtime_event); + } + + switch (new_state) { case RC_TRIGGER_STATE_RESET: runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET; @@ -511,23 +515,19 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha break; case RC_TRIGGER_STATE_PAUSED: - if (trigger_state != RC_TRIGGER_STATE_PAUSED) { - runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED; - runtime_event.id = self->triggers[i].id; - event_handler(&runtime_event); - } + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED; + runtime_event.id = self->triggers[i].id; + event_handler(&runtime_event); break; case RC_TRIGGER_STATE_PRIMED: - if (trigger_state != RC_TRIGGER_STATE_PRIMED) { - runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED; - runtime_event.id = self->triggers[i].id; - event_handler(&runtime_event); - } + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED; + runtime_event.id = self->triggers[i].id; + event_handler(&runtime_event); break; case RC_TRIGGER_STATE_ACTIVE: - if (trigger_state != RC_TRIGGER_STATE_ACTIVE) { + if (old_state == RC_TRIGGER_STATE_WAITING || old_state == RC_TRIGGER_STATE_PAUSED) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); diff --git a/deps/rcheevos/src/rcheevos/runtime_progress.c b/deps/rcheevos/src/rcheevos/runtime_progress.c index 5eba099e83..32081d65d9 100644 --- a/deps/rcheevos/src/rcheevos/runtime_progress.c +++ b/deps/rcheevos/src/rcheevos/runtime_progress.c @@ -337,17 +337,9 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres if (!runtime_trigger->trigger) continue; - switch (runtime_trigger->trigger->state) - { - case RC_TRIGGER_STATE_DISABLED: - case RC_TRIGGER_STATE_INACTIVE: - case RC_TRIGGER_STATE_TRIGGERED: - /* don't store state for inactive or triggered achievements */ - continue; - - default: - break; - } + /* don't store state for inactive or triggered achievements */ + if (!rc_trigger_state_active(runtime_trigger->trigger->state)) + continue; if (!progress->buffer) { if (runtime_trigger->serialized_size) { @@ -470,19 +462,12 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char* for (i = 0; i < runtime->trigger_count; ++i) { rc_runtime_trigger_t* runtime_trigger = &runtime->triggers[i]; if (runtime_trigger->trigger) { - switch (runtime_trigger->trigger->state) + /* don't update state for inactive or triggered achievements */ + if (rc_trigger_state_active(runtime_trigger->trigger->state)) { - case RC_TRIGGER_STATE_DISABLED: - case RC_TRIGGER_STATE_INACTIVE: - case RC_TRIGGER_STATE_TRIGGERED: - /* don't update state for inactive or triggered achievements */ - break; - - default: - /* mark active achievements as unupdated. anything that's still unupdated - * after deserializing the progress will be reset to waiting */ - runtime_trigger->trigger->state = RC_TRIGGER_STATE_UNUPDATED; - break; + /* mark active achievements as unupdated. anything that's still unupdated + * after deserializing the progress will be reset to waiting */ + runtime_trigger->trigger->state = RC_TRIGGER_STATE_UNUPDATED; } } } diff --git a/deps/rcheevos/src/rcheevos/trigger.c b/deps/rcheevos/src/rcheevos/trigger.c index 602b988007..368b5410cc 100644 --- a/deps/rcheevos/src/rcheevos/trigger.c +++ b/deps/rcheevos/src/rcheevos/trigger.c @@ -80,6 +80,20 @@ rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, return (parse.offset >= 0) ? self : NULL; } +int rc_trigger_state_active(int state) +{ + switch (state) + { + case RC_TRIGGER_STATE_DISABLED: + case RC_TRIGGER_STATE_INACTIVE: + case RC_TRIGGER_STATE_TRIGGERED: + return 0; + + default: + return 1; + } +} + static void rc_reset_trigger_hitcounts(rc_trigger_t* self) { rc_condset_t* condset; @@ -102,21 +116,28 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* char is_paused; char is_primed; - /* previously triggered, do nothing - return INACTIVE so caller doesn't think it triggered again */ - if (self->state == RC_TRIGGER_STATE_TRIGGERED) - return RC_TRIGGER_STATE_INACTIVE; + switch (self->state) + { + case RC_TRIGGER_STATE_TRIGGERED: + /* previously triggered. do nothing - return INACTIVE so caller doesn't think it triggered again */ + return RC_TRIGGER_STATE_INACTIVE; - /* unsupported, do nothing - return INACTIVE */ - if (self->state == RC_TRIGGER_STATE_DISABLED) - return RC_TRIGGER_STATE_INACTIVE; + case RC_TRIGGER_STATE_DISABLED: + /* unsupported. do nothing - return INACTIVE */ + return RC_TRIGGER_STATE_INACTIVE; + + case RC_TRIGGER_STATE_INACTIVE: + /* not yet active. update the memrefs so deltas are correct when it becomes active, then return INACTIVE */ + rc_update_memref_values(self->memrefs, peek, ud); + return RC_TRIGGER_STATE_INACTIVE; + + default: + break; + } /* update the memory references */ rc_update_memref_values(self->memrefs, peek, ud); - /* not yet active, only update the memrefs so deltas are correct when it becomes active */ - if (self->state == RC_TRIGGER_STATE_INACTIVE) - return RC_TRIGGER_STATE_INACTIVE; - /* process the trigger */ memset(&eval_state, 0, sizeof(eval_state)); eval_state.peek = peek; @@ -178,6 +199,11 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* /* if there were hit counts to clear, return RESET, but don't change the state */ if (self->has_hits) { self->has_hits = 0; + + /* cannot be PRIMED while ResetIf is true */ + if (self->state == RC_TRIGGER_STATE_PRIMED) + self->state = RC_TRIGGER_STATE_ACTIVE; + return RC_TRIGGER_STATE_RESET; } diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index db2ec92a4b..2dd5b8461d 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -354,6 +354,7 @@ void gfx_widgets_ai_service_overlay_unload(dispgfx_widget_t *p_dispwidget); #ifdef HAVE_CHEEVOS void gfx_widgets_push_achievement(const char *title, const char *badge); void gfx_widgets_set_leaderboard_display(unsigned id, const char* value); +void gfx_widgets_set_challenge_display(unsigned id, const char* badge); #endif /* Warning: not thread safe! */ diff --git a/gfx/widgets/gfx_widget_leaderboard_display.c b/gfx/widgets/gfx_widget_leaderboard_display.c index ad86d2066f..284efdf5ea 100644 --- a/gfx/widgets/gfx_widget_leaderboard_display.c +++ b/gfx/widgets/gfx_widget_leaderboard_display.c @@ -27,6 +27,7 @@ #endif #define CHEEVO_LBOARD_ARRAY_SIZE 4 +#define CHEEVO_CHALLENGE_ARRAY_SIZE 8 #define CHEEVO_LBOARD_DISPLAY_PADDING 3 @@ -37,13 +38,21 @@ struct leaderboard_display_info char display[24]; /* should never exceed 12 bytes, but aligns the structure at 32 bytes */ }; +struct challenge_display_info +{ + unsigned id; + uintptr_t image; +}; + struct gfx_widget_leaderboard_display_state { #ifdef HAVE_THREADS slock_t* array_lock; #endif - struct leaderboard_display_info info[CHEEVO_LBOARD_ARRAY_SIZE]; - int count; + struct leaderboard_display_info tracker_info[CHEEVO_LBOARD_ARRAY_SIZE]; + unsigned tracker_count; + struct challenge_display_info challenge_info[CHEEVO_CHALLENGE_ARRAY_SIZE]; + unsigned challenge_count; }; typedef struct gfx_widget_leaderboard_display_state gfx_widget_leaderboard_display_state_t; @@ -66,7 +75,8 @@ static void gfx_widget_leaderboard_display_free(void) { gfx_widget_leaderboard_display_state_t *state = &p_w_leaderboard_display_st; - state->count = 0; + state->tracker_count = 0; + state->challenge_count = 0; #ifdef HAVE_THREADS slock_free(state->array_lock); state->array_lock = NULL; @@ -76,7 +86,8 @@ static void gfx_widget_leaderboard_display_free(void) static void gfx_widget_leaderboard_display_context_destroy(void) { gfx_widget_leaderboard_display_state_t *state = &p_w_leaderboard_display_st; - state->count = 0; + state->tracker_count = 0; + state->challenge_count = 0; } static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) @@ -84,7 +95,7 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) gfx_widget_leaderboard_display_state_t *state = &p_w_leaderboard_display_st; /* if there's nothing to display, just bail */ - if (state->count == 0) + if (state->tracker_count == 0 && state->challenge_count == 0) return; SLOCK_LOCK(state->array_lock); @@ -108,9 +119,53 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) gfx_display_set_alpha(p_dispwidget->backdrop_orig, DEFAULT_BACKDROP); gfx_display_set_alpha(pure_white, 1.0f); - for (i = 0; i < (unsigned)state->count; ++i) + if (state->challenge_count) { - const unsigned widget_width = state->info[i].width; + const unsigned widget_size = spacing * 4; + + x = video_width; + y -= (widget_size + spacing); + + for (i = 0; i < state->challenge_count; ++i) + { + x -= (widget_size + spacing); + + if (!state->challenge_info[i].image) + { + /* default icon */ + if (p_dispwidget->gfx_widgets_icons_textures[ + MENU_WIDGETS_ICON_ACHIEVEMENT]) + { + gfx_display_ctx_driver_t* dispctx = p_disp->dispctx; + if (dispctx && dispctx->blend_begin) + dispctx->blend_begin(video_info->userdata); + + gfx_widgets_draw_icon(video_info->userdata, + p_disp, video_width, video_height, + widget_size, widget_size, + p_dispwidget->gfx_widgets_icons_textures[ + MENU_WIDGETS_ICON_ACHIEVEMENT], + x, y, 0, 1, pure_white); + + if (dispctx && dispctx->blend_end) + dispctx->blend_end(video_info->userdata); + } + } + else + { + /* achievement badge */ + gfx_widgets_draw_icon(video_info->userdata, + p_disp, video_width, video_height, + widget_size, widget_size, + state->challenge_info[i].image, + x, y, 0, 1, pure_white); + } + } + } + + for (i = 0; i < state->tracker_count; ++i) + { + const unsigned widget_width = state->tracker_info[i].width; x = video_width - widget_width - spacing; y -= (widget_height + spacing); @@ -125,7 +180,7 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata) /* Text */ gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.regular, - state->info[i].display, + state->tracker_info[i].display, (float)(x + CHEEVO_LBOARD_DISPLAY_PADDING), (float)(y + widget_height - (CHEEVO_LBOARD_DISPLAY_PADDING - 1) - p_dispwidget->gfx_widget_fonts.regular.line_descender), @@ -146,9 +201,9 @@ void gfx_widgets_set_leaderboard_display(unsigned id, const char* value) SLOCK_LOCK(state->array_lock); - for (i = 0; i < state->count; ++i) + for (i = 0; i < state->tracker_count; ++i) { - if (state->info[i].id == id) + if (state->tracker_info[i].id == id) break; } @@ -157,13 +212,13 @@ void gfx_widgets_set_leaderboard_display(unsigned id, const char* value) if (value == NULL) { /* hide display */ - if (i < state->count) + if (i < state->tracker_count) { - --state->count; - if (i < state->count) + --state->tracker_count; + if (i < state->tracker_count) { - memcpy(&state->info[i], &state->info[i + 1], - (state->count - i) * sizeof(state->info[i])); + memcpy(&state->tracker_info[i], &state->tracker_info[i + 1], + (state->tracker_count - i) * sizeof(state->tracker_info[i])); } } } @@ -172,20 +227,81 @@ void gfx_widgets_set_leaderboard_display(unsigned id, const char* value) /* show or update display */ const dispgfx_widget_t* p_dispwidget = (const dispgfx_widget_t*)dispwidget_get_ptr(); - if (i == state->count) - state->info[state->count++].id = id; + if (i == state->tracker_count) + state->tracker_info[state->tracker_count++].id = id; - strncpy(state->info[i].display, value, sizeof(state->info[i].display)); - state->info[i].width = font_driver_get_message_width( + strncpy(state->tracker_info[i].display, value, sizeof(state->tracker_info[i].display)); + state->tracker_info[i].width = font_driver_get_message_width( p_dispwidget->gfx_widget_fonts.regular.font, - state->info[i].display, 0, 1); - state->info[i].width += CHEEVO_LBOARD_DISPLAY_PADDING * 2; + state->tracker_info[i].display, 0, 1); + state->tracker_info[i].width += CHEEVO_LBOARD_DISPLAY_PADDING * 2; } } SLOCK_UNLOCK(state->array_lock); } +void gfx_widgets_set_challenge_display(unsigned id, const char* badge) +{ + int i; + gfx_widget_leaderboard_display_state_t* state = &p_w_leaderboard_display_st; + + /* important - this must be done outside the lock because it has the potential to need to + * lock the video thread, which may be waiting for the popup queue lock to render popups */ + uintptr_t badge_id = badge ? rcheevos_get_badge_texture(badge, 0) : NULL; + uintptr_t old_badge_id = NULL; + + SLOCK_LOCK(state->array_lock); + + for (i = 0; i < state->challenge_count; ++i) + { + if (state->challenge_info[i].id == id) + break; + } + + if (i < CHEEVO_CHALLENGE_ARRAY_SIZE) + { + if (badge == NULL) + { + /* hide indicator */ + if (i < state->challenge_count) + { + old_badge_id = state->challenge_info[i].image; + + --state->challenge_count; + if (i < state->challenge_count) + { + memcpy(&state->challenge_info[i], &state->challenge_info[i + 1], + (state->challenge_count - i) * sizeof(state->challenge_info[i])); + } + + state->challenge_info[state->challenge_count].image = 0; + } + } + else + { + /* show indicator */ + if (i == state->challenge_count) + { + /* new indicator, assign id */ + state->challenge_info[state->challenge_count++].id = id; + } + else if (state->challenge_info[i].image) + { + /* existing indicator, free old image */ + old_badge_id = state->challenge_info[i].image; + } + + state->challenge_info[i].image = badge_id; + } + } + + SLOCK_UNLOCK(state->array_lock); + + if (old_badge_id) + video_driver_texture_unload(&old_badge_id); +} + const gfx_widget_t gfx_widget_leaderboard_display = { &gfx_widget_leaderboard_display_init, &gfx_widget_leaderboard_display_free, diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 0d48d4b155..6f98595fc8 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -396,6 +396,10 @@ MSG_HASH( MENU_ENUM_LABEL_CHEEVOS_START_ACTIVE, "cheevos_start_active" ) +MSG_HASH( + MENU_ENUM_LABEL_CHEEVOS_CHALLENGE_INDICATORS, + "cheevos_challenge_indicators" + ) MSG_HASH( MENU_ENUM_LABEL_CLOSE_CONTENT, "unload_core" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 80a4f6cd87..c6741f7e4a 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -4988,6 +4988,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_CHEEVOS_START_ACTIVE, "Start the session with all achievements active (even the ones previously unlocked)." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CHEEVOS_CHALLENGE_INDICATORS, + "Challenge Indicators" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CHEEVOS_CHALLENGE_INDICATORS, + "Allows achievements to display an on-screen indicator while the achievement can be earned." + ) /* Settings > Network */ diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 3506c0f45f..bc828a9091 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -183,6 +183,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_enable, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_test_unofficial, MENU_ENUM_SUBLABEL_CHEEVOS_TEST_UNOFFICIAL) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_hardcore_mode_enable, MENU_ENUM_SUBLABEL_CHEEVOS_HARDCORE_MODE_ENABLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_leaderboards_enable, MENU_ENUM_SUBLABEL_CHEEVOS_LEADERBOARDS_ENABLE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_challenge_indicators, MENU_ENUM_SUBLABEL_CHEEVOS_CHALLENGE_INDICATORS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_richpresence_enable, MENU_ENUM_SUBLABEL_CHEEVOS_RICHPRESENCE_ENABLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_badges_enable, MENU_ENUM_SUBLABEL_CHEEVOS_BADGES_ENABLE) #if defined(HAVE_AUDIOMIXER) @@ -3558,6 +3559,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_CHEEVOS_LEADERBOARDS_ENABLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cheevos_leaderboards_enable); break; + case MENU_ENUM_LABEL_CHEEVOS_CHALLENGE_INDICATORS: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cheevos_challenge_indicators); + break; case MENU_ENUM_LABEL_CHEEVOS_RICHPRESENCE_ENABLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cheevos_richpresence_enable); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index e8388a309a..dd17fa8919 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -7382,6 +7382,7 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_CHEEVOS_PASSWORD, PARSE_ONLY_STRING, false }, {MENU_ENUM_LABEL_CHEEVOS_HARDCORE_MODE_ENABLE, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_CHEEVOS_LEADERBOARDS_ENABLE, PARSE_ONLY_STRING_OPTIONS, false }, + {MENU_ENUM_LABEL_CHEEVOS_CHALLENGE_INDICATORS, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_CHEEVOS_RICHPRESENCE_ENABLE, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_CHEEVOS_BADGES_ENABLE, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_CHEEVOS_TEST_UNOFFICIAL, PARSE_ONLY_BOOL, false }, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index fd26c65e07..57a747f20e 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -17930,6 +17930,22 @@ static bool setting_append_list( (*list)[list_info->index - 1].get_string_representation = achievement_leaderboards_get_string_representation; (*list)[list_info->index - 1].free_flags &= ~SD_FREE_FLAG_VALUES; + CONFIG_BOOL( + list, list_info, + &settings->bools.cheevos_challenge_indicators, + MENU_ENUM_LABEL_CHEEVOS_CHALLENGE_INDICATORS, + MENU_ENUM_LABEL_VALUE_CHEEVOS_CHALLENGE_INDICATORS, + true, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE + ); + CONFIG_BOOL( list, list_info, &settings->bools.cheevos_richpresence_enable, diff --git a/msg_hash.h b/msg_hash.h index 30ffecbec4..5482a54df0 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1405,6 +1405,7 @@ enum msg_hash_enums MENU_LABEL(CHEEVOS_UNLOCK_SOUND_ENABLE), MENU_LABEL(CHEEVOS_AUTO_SCREENSHOT), MENU_LABEL(CHEEVOS_START_ACTIVE), + MENU_LABEL(CHEEVOS_CHALLENGE_INDICATORS), MENU_LABEL(CHEEVOS_ENABLE), MENU_LABEL(CHEEVOS_DESCRIPTION), MENU_LABEL(ACCOUNTS_RETRO_ACHIEVEMENTS), diff --git a/retroarch.cfg b/retroarch.cfg index 03895f1115..8c55924f23 100644 --- a/retroarch.cfg +++ b/retroarch.cfg @@ -884,6 +884,11 @@ # compete for high-scores, speedruns, etc. # cheevos_leaderboards_enable = false +# Show an on-screen indicator when attempting challenging achievements +# to provide feedback when the attempt has failed (for achievements that +# support it) +# cheevos_challenge_indicators = true + # Send some messages to the RetroAchievements.org saying, for example, # where you are in the game, how many lives you have, your score, etc. # cheevos_richpresence_enable = true