menu/gameovermenu: add "Load Quicksave" entry when available

Also move the logic of action choice handling out of the menu and into
the stage.
This commit is contained in:
Andrei Alexeyev 2024-05-03 04:18:27 +02:00
parent 208be2bccd
commit b9d5c741ba
No known key found for this signature in database
GPG key ID: 72D26128040B9690
5 changed files with 154 additions and 42 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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 },
});

View file

@ -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);

View file

@ -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