diff --git a/src/menu/gameovermenu.c b/src/menu/gameovermenu.c index 92a70160..5d6d61a8 100644 --- a/src/menu/gameovermenu.c +++ b/src/menu/gameovermenu.c @@ -14,40 +14,79 @@ #include "stats.h" #include "global.h" -static void continue_game(MenuData *m, void *arg) { - log_info("The game is being continued..."); - player_event(&global.plr, &global.replay.input, &global.replay.output, EV_CONTINUE, 0); +typedef struct GameoverMenuContext { + IngameMenuContext base; + GameoverMenuAction *output; +} GameoverMenuContext; + +static void set_action(MenuData *m, void *arg) { + GameoverMenuContext *ctx = m->context; + *ctx->output = (uintptr_t)arg; } -static void give_up(MenuData *m, void *arg) { - global.gameover = (MAX_CONTINUES - global.plr.stats.total.continues_used) ? GAMEOVER_ABORT : GAMEOVER_DEFEAT; +static MenuEntry *add_action_entry(MenuData *m, char *text, GameoverMenuAction action, bool enabled) { + return add_menu_entry(m, text, enabled ? set_action : NULL, (void*)action); } -MenuData *create_gameover_menu(void) { +static MenuEntry *add_quickload_entry(MenuData *m, const GameoverMenuParams *params) { + if(!params->quickload_shown) { + return NULL; + } + + return add_action_entry(m, "Load Quicksave", GAMEOVERMENU_ACTION_QUICKLOAD, params->quickload_enabled); +} + +static void free_gameover_menu(MenuData *m) { + mem_free(m->context); +} + +MenuData *create_gameover_menu(const GameoverMenuParams *params) { MenuData *m = alloc_menu(); + auto ctx = ALLOC(GameoverMenuContext, { + .output = params->output, + }); + + *ctx->output = GAMEOVERMENU_ACTION_DEFAULT; + m->draw = draw_ingame_menu; m->logic = update_ingame_menu; + m->end = free_gameover_menu; m->flags = MF_Transient | MF_AlwaysProcessInput; m->transition = TransEmpty; + m->context = ctx; + + add_quickload_entry(m, params); if(global.stage->type == STAGE_SPELL) { - m->context = "Spell Failed"; + ctx->base.title = "Spell Failed"; - add_menu_entry(m, "Retry", restart_game, NULL)->transition = TransFadeBlack; - add_menu_entry(m, "Give up", give_up, NULL)->transition = TransFadeBlack; + add_action_entry(m, "Restart", GAMEOVERMENU_ACTION_RESTART, true) + ->transition = TransFadeBlack; + add_action_entry(m, "Give up", GAMEOVERMENU_ACTION_QUIT, true) + ->transition = TransFadeBlack; } else { - m->context = "Game Over"; + ctx->base.title = "Game Over"; char s[64]; int c = MAX_CONTINUES - global.plr.stats.total.continues_used; - snprintf(s, sizeof(s), "Continue (%i)", c); - add_menu_entry(m, s, c ? continue_game : NULL, NULL); - add_menu_entry(m, "Restart the Game", restart_game, NULL)->transition = TransFadeBlack; - add_menu_entry(m, c? "Give up" : "Return to Title", give_up, NULL)->transition = TransFadeBlack; + bool have_continues = c > 0; - if(!c) - m->cursor = 1; + if(have_continues) { + snprintf(s, sizeof(s), "Continue (%i)", c); + } else { + snprintf(s, sizeof(s), "Continue"); + } + + add_action_entry(m, s, GAMEOVERMENU_ACTION_CONTINUE, have_continues); + add_action_entry(m, "Restart the Game", GAMEOVERMENU_ACTION_RESTART, true) + ->transition = TransFadeBlack; + add_action_entry(m, have_continues ? "Give up" : "Return to Title", GAMEOVERMENU_ACTION_QUIT, true) + ->transition = TransFadeBlack; + } + + while(!dynarray_get(&m->entries, m->cursor).action) { + ++m->cursor; } set_transition(TransEmpty, 0, m->transition_out_time, NO_CALLCHAIN); diff --git a/src/menu/gameovermenu.h b/src/menu/gameovermenu.h index a04e074d..8c177aaa 100644 --- a/src/menu/gameovermenu.h +++ b/src/menu/gameovermenu.h @@ -11,4 +11,19 @@ #include "menu.h" -MenuData *create_gameover_menu(void); +typedef enum GameoverMenuAction { + GAMEOVERMENU_ACTION_DEFAULT, + GAMEOVERMENU_ACTION_RESTART, + GAMEOVERMENU_ACTION_QUIT, + GAMEOVERMENU_ACTION_CONTINUE, + GAMEOVERMENU_ACTION_QUICKLOAD, +} GameoverMenuAction; + +typedef struct GameoverMenuParams { + GameoverMenuAction *output; + bool quickload_shown; + bool quickload_enabled; +} GameoverMenuParams; + +MenuData *create_gameover_menu(const GameoverMenuParams *params) + attr_nonnull_all attr_returns_allocated; diff --git a/src/menu/ingamemenu.c b/src/menu/ingamemenu.c index 9150f4d5..ac72d07b 100644 --- a/src/menu/ingamemenu.c +++ b/src/menu/ingamemenu.c @@ -113,16 +113,23 @@ static void ingame_menu_input(MenuData *m) { }, EFLAG_MENU); } +static void free_ingame_menu(MenuData *m) { + mem_free(m->context); +} + MenuData *create_ingame_menu(void) { MenuData *m = alloc_menu(); m->draw = draw_ingame_menu; m->logic = update_ingame_menu; m->input = ingame_menu_input; + m->end = free_ingame_menu; m->flags = MF_Abortable | MF_AlwaysProcessInput; m->transition = TransEmpty; m->cursor = 1; - m->context = "Game Paused"; + m->context = ALLOC(IngameMenuContext, { + .title = "Game Paused", + }); add_menu_entry(m, "Options", menu_action_enter_options, NULL)->transition = TransFadeBlack; add_menu_entry(m, "Return to Game", menu_action_close, NULL); add_menu_entry(m, "Restart the Game", restart_game, NULL)->transition = TransFadeBlack; @@ -142,10 +149,13 @@ MenuData *create_ingame_menu_replay(void) { m->draw = draw_ingame_menu; m->logic = update_ingame_menu; + m->end = free_ingame_menu; m->flags = MF_Abortable | MF_AlwaysProcessInput; m->transition = TransEmpty; m->cursor = 1; - m->context = "Replay Paused"; + m->context = ALLOC(IngameMenuContext, { + .title = "Replay Paused", + }); add_menu_entry(m, "Options", menu_action_enter_options, NULL)->transition = TransFadeBlack; add_menu_entry(m, "Continue Watching", menu_action_close, NULL); add_menu_entry(m, "Restart the Stage", restart_game, NULL)->transition = TransFadeBlack; @@ -186,10 +196,12 @@ void draw_ingame_menu(MenuData *menu) { r_shader("text_default"); - if(menu->context) { + IngameMenuContext *ctx = menu->context; + + if(ctx && ctx->title) { float s = 0.3 + 0.2 * sin(menu->frames/10.0); r_color(RGBA_MUL_ALPHA(1-s/2, 1-s/2, 1-s, 1-menu_fade(menu))); - text_draw(menu->context, &(TextParams) { + text_draw(ctx->title, &(TextParams) { .align = ALIGN_CENTER, .pos = { 0, -2 * 35 }, }); diff --git a/src/menu/ingamemenu.h b/src/menu/ingamemenu.h index 9dfe3c6c..6a1939b3 100644 --- a/src/menu/ingamemenu.h +++ b/src/menu/ingamemenu.h @@ -11,6 +11,10 @@ #include "menu.h" +typedef struct IngameMenuContext { + const char *title; +} IngameMenuContext; + void draw_ingame_menu_bg(MenuData *menu, float f); void draw_ingame_menu(MenuData *menu); diff --git a/src/stage.c b/src/stage.c index 5ab94a61..9ed1c670 100644 --- a/src/stage.c +++ b/src/stage.c @@ -189,14 +189,28 @@ static bool ingame_menu_interrupts_bgm(void) { return global.stage->type != STAGE_SPELL; } -typedef struct IngameMenuContext { +static inline bool is_quicksave_allowed(void) { +#ifndef DEBUG + if(!global.is_practice_mode) { + return false; + } +#endif + + if(global.gameover != GAMEOVER_NONE) { + return false; + } + + return true; +} + +typedef struct IngameMenuCallContext { CallChain next; BGM *saved_bgm; double saved_bgm_pos; bool bgm_interrupted; -} IngameMenuContext; +} IngameMenuCallContext; -static void setup_ingame_menu_bgm(IngameMenuContext *ctx, BGM *bgm) { +static void setup_ingame_menu_bgm(IngameMenuCallContext *ctx, BGM *bgm) { if(ingame_menu_interrupts_bgm()) { ctx->bgm_interrupted = true; @@ -210,7 +224,7 @@ static void setup_ingame_menu_bgm(IngameMenuContext *ctx, BGM *bgm) { } } -static void resume_bgm(IngameMenuContext *ctx) { +static void resume_bgm(IngameMenuCallContext *ctx) { if(ctx->bgm_interrupted) { if(ctx->saved_bgm) { audio_bgm_play(ctx->saved_bgm, true, ctx->saved_bgm_pos, 0.5); @@ -224,7 +238,7 @@ static void resume_bgm(IngameMenuContext *ctx) { } static void stage_leave_ingame_menu(CallChainResult ccr) { - IngameMenuContext *ctx = ccr.ctx; + IngameMenuCallContext *ctx = ccr.ctx; MenuData *m = ccr.result; if(m->state != MS_Dead) { @@ -250,7 +264,7 @@ static void stage_leave_ingame_menu(CallChainResult ccr) { static void stage_enter_ingame_menu(MenuData *m, BGM *bgm, CallChain next) { events_emit(TE_GAME_PAUSE_STATE_CHANGED, true, NULL, NULL); - auto ctx = ALLOC(IngameMenuContext, { .next = next }); + auto ctx = ALLOC(IngameMenuCallContext, { .next = next }); setup_ingame_menu_bgm(ctx, bgm); pause_all_sfx(); enter_menu(m, CALLCHAIN(stage_leave_ingame_menu, ctx)); @@ -285,6 +299,41 @@ static void stage_pause(StageFrameState *fstate) { static void stage_do_quickload(StageFrameState *fstate); +static void stage_continue_game(void) { + log_info("The game is being continued..."); + player_event(&global.plr, &global.replay.input, &global.replay.output, EV_CONTINUE, 0); +} + +static void gameover_menu_result(CallChainResult ccr) { + GameoverMenuAction *_action = ccr.ctx; + auto action = *_action; + mem_free(_action); + + switch(action) { + case GAMEOVERMENU_ACTION_QUIT: + if(global.plr.stats.total.continues_used >= MAX_CONTINUES) { + global.gameover = GAMEOVER_DEFEAT; + } else { + global.gameover = GAMEOVER_ABORT; + } + break; + + case GAMEOVERMENU_ACTION_CONTINUE: + stage_continue_game(); + break; + + case GAMEOVERMENU_ACTION_RESTART: + global.gameover = GAMEOVER_RESTART; + break; + + case GAMEOVERMENU_ACTION_QUICKLOAD: + stage_do_quickload(_current_stage_state); + break; + + default: UNREACHABLE; + } +} + void stage_gameover(void) { if(global.stage->type == STAGE_SPELL && config_get_int(CONFIG_SPELLSTAGE_AUTORESTART)) { auto fstate = _current_stage_state; @@ -303,7 +352,14 @@ void stage_gameover(void) { progress_unlock_bgm("gameover"); } - stage_enter_ingame_menu(create_gameover_menu(), bgm, NO_CALLCHAIN); + auto action = ALLOC(GameoverMenuAction); + auto menu = create_gameover_menu(&(GameoverMenuParams) { + .output = action, + .quickload_shown = is_quicksave_allowed(), + .quickload_enabled = _current_stage_state->quicksave != NULL, + }); + + stage_enter_ingame_menu(menu, bgm, CALLCHAIN(gameover_menu_result, action)); } static bool stage_input_common(SDL_Event *event, void *arg) { @@ -397,20 +453,6 @@ static Replay *create_quicksave_replay(ReplayStage *rstg_src) { return rpy; } -static inline bool is_quicksave_allowed(void) { -#ifndef DEBUG - if(!global.is_practice_mode) { - return false; - } -#endif - - if(global.gameover != GAMEOVER_NONE) { - return false; - } - - return true; -} - static void stage_do_quicksave(StageFrameState *fstate, bool isauto) { if(isauto && fstate->quicksave && !fstate->quicksave_is_automatic) { // Do not overwrite a manual quicksave with an auto quicksave