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:
parent
208be2bccd
commit
b9d5c741ba
5 changed files with 154 additions and 42 deletions
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 },
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
84
src/stage.c
84
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
|
||||
|
|
Loading…
Reference in a new issue