taisei/src/menu/common.c

304 lines
8.1 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "common.h"
#include "audio/audio.h"
#include "charselect.h"
#include "credits.h"
#include "cutscenes/cutscene.h"
#include "difficultyselect.h"
#include "global.h"
#include "mainmenu.h"
#include "menu.h"
#include "progress.h"
#include "replay/state.h"
#include "replay/struct.h"
#include "resource/font.h"
#include "savereplay.h"
#include "stage.h"
#include "video.h"
typedef struct StartGameContext {
StageInfo *restart_stage;
StageInfo *current_stage;
MenuData *diff_menu;
MenuData *char_menu;
Replay replay;
Difficulty difficulty;
ResourceGroup rg;
} StartGameContext;
static void start_game_do_pick_character(CallChainResult ccr);
static void start_game_do_enter_stage(CallChainResult ccr);
static void start_game_do_leave_stage(CallChainResult ccr);
static void start_game_do_show_ending(CallChainResult ccr);
static void start_game_do_show_credits(CallChainResult ccr);
static void start_game_do_cleanup(CallChainResult ccr);
static void start_game_internal(MenuData *menu, StageInfo *info, bool difficulty_menu) {
auto ctx = ALLOC(StartGameContext);
res_group_init(&ctx->rg);
if(info == NULL) {
global.is_practice_mode = false;
ctx->current_stage = ctx->restart_stage = stageinfo_get_by_id(1);
} else {
global.is_practice_mode = (info->type != STAGE_EXTRA);
ctx->current_stage = info;
ctx->restart_stage = info;
}
Difficulty stagediff = info ? info->difficulty : D_Any;
CallChain cc_pick_character = CALLCHAIN(start_game_do_pick_character, ctx);
if(stagediff == D_Any) {
if(difficulty_menu) {
ctx->diff_menu = create_difficulty_menu();
enter_menu(ctx->diff_menu, cc_pick_character);
} else {
ctx->difficulty = progress.game_settings.difficulty;
run_call_chain(&cc_pick_character, NULL);
}
} else {
ctx->difficulty = stagediff;
run_call_chain(&cc_pick_character, NULL);
}
}
static void start_game_do_pick_character(CallChainResult ccr) {
StartGameContext *ctx = ccr.ctx;
MenuData *prev_menu = ccr.result;
if(prev_menu) {
if(prev_menu->state == MS_Dead) {
start_game_do_cleanup(ccr);
return;
}
// came here from the difficulty menu - update our setting
ctx->difficulty = progress.game_settings.difficulty;
}
assert(ctx->char_menu == NULL);
ctx->char_menu = create_char_menu();
enter_menu(ctx->char_menu, CALLCHAIN(start_game_do_enter_stage, ctx));
}
static void reset_game(StartGameContext *ctx) {
ctx->current_stage = ctx->restart_stage;
global.gameover = GAMEOVER_NONE;
replay_reset(&ctx->replay);
replay_state_init_record(&global.replay.output, &ctx->replay);
player_init(&global.plr);
stats_init(&global.plr.stats);
global.plr.mode = plrmode_find(
progress.game_settings.character,
progress.game_settings.shotmode
);
global.diff = ctx->difficulty;
assert(global.plr.mode != NULL);
}
static void kill_aux_menus(StartGameContext *ctx) {
kill_menu(ctx->char_menu);
kill_menu(ctx->diff_menu);
ctx->char_menu = ctx->diff_menu = NULL;
}
static void enter_stage_now(StartGameContext *ctx) {
res_group_release(&ctx->rg);
stage_enter(ctx->current_stage, &ctx->rg, CALLCHAIN(start_game_do_leave_stage, ctx));
}
static void start_game_do_enter_stage(CallChainResult ccr) {
StartGameContext *ctx = ccr.ctx;
MenuData *prev_menu = ccr.result;
if(prev_menu && prev_menu->state == MS_Dead) {
assert(prev_menu == ctx->char_menu);
ctx->char_menu = NULL;
if(!ctx->diff_menu) {
start_game_do_cleanup(ccr);
}
return;
}
taisei_commit_persistent_data();
kill_aux_menus(ctx);
reset_game(ctx);
enter_stage_now(ctx);
}
static void start_game_do_leave_stage(CallChainResult ccr) {
StartGameContext *ctx = ccr.ctx;
if(global.gameover == GAMEOVER_RESTART) {
reset_game(ctx);
enter_stage_now(ctx);
return;
}
if(ctx->current_stage->type == STAGE_STORY && !global.is_practice_mode) {
++ctx->current_stage;
if(ctx->current_stage->type == STAGE_STORY) {
enter_stage_now(ctx);
} else {
CallChain cc;
if(global.gameover == GAMEOVER_WIN) {
res_group_release(&ctx->rg);
res_purge();
credits_preload(&ctx->rg);
cc = CALLCHAIN(start_game_do_show_ending, ctx);
} else {
cc = CALLCHAIN(start_game_do_cleanup, ctx);
}
ask_save_replay(&ctx->replay, cc);
}
} else {
ask_save_replay(&ctx->replay, CALLCHAIN(start_game_do_cleanup, ctx));
}
}
static void start_game_do_show_ending(CallChainResult ccr) {
Player *plr = &global.plr;
PlayerCharacter *character = plr->mode->character;
PlrCharEndingCutscene *ec_id;
if (plr->stats.total.continues_used) {
ec_id = &character->ending.bad;
} else {
ec_id = &character->ending.good;
}
progress_track_ending(ec_id->ending_id);
cutscene_enter(CALLCHAIN(start_game_do_show_credits, ccr.ctx), ec_id->cutscene_id);
}
static void start_game_do_show_credits(CallChainResult ccr) {
credits_enter(CALLCHAIN(start_game_do_cleanup, ccr.ctx));
}
static void start_game_do_cleanup(CallChainResult ccr) {
StartGameContext *ctx = ccr.ctx;
replay_reset(&ctx->replay);
kill_aux_menus(ctx);
res_group_release(&ctx->rg);
mem_free(ctx);
global.gameover = GAMEOVER_NONE;
replay_state_deinit(&global.replay.output);
main_menu_update_practice_menus();
audio_bgm_play(res_bgm("menu"), true, 0, 0);
}
void start_game(MenuData *m, void *arg) {
start_game_internal(m, (StageInfo*)arg, true);
}
void start_game_no_difficulty_menu(MenuData *m, void *arg) {
start_game_internal(m, (StageInfo*)arg, false);
}
void draw_menu_selector(float x, float y, float w, float h, float t) {
Sprite *bg = res_sprite("part/smoke");
r_mat_mv_push();
r_mat_mv_translate(x, y, 0);
r_mat_mv_scale(w / bg->w, h / bg->h, 1);
r_mat_mv_rotate(t * 2 * DEG2RAD, 0, 0, 1);
r_draw_sprite(&(SpriteParams) {
.sprite_ptr = bg,
.color = RGBA(0, 0, 0, 0.5 * (1 - transition.fade)),
.shader_ptr = res_shader("sprite_default"),
});
r_mat_mv_pop();
}
void draw_menu_title(MenuData *m, const char *title) {
text_draw(title, &(TextParams) {
.pos = { (text_width(res_font("big"), title, 0) + 10) * (1.0 - menu_fade(m)), 30 },
.align = ALIGN_RIGHT,
.font = "big",
.color = RGB(1, 1, 1),
.shader_ptr = res_shader("text_default"),
});
}
void draw_menu_list(MenuData *m, float x, float y, void (*draw)(MenuEntry*, int, int, void*), float scroll_threshold, void *userdata) {
r_mat_mv_push();
float offset = smoothmin(0, scroll_threshold * 0.8 - y - m->drawdata[2], 80);
r_mat_mv_translate(x, y + offset, 0);
draw_menu_selector(m->drawdata[0], m->drawdata[2], m->drawdata[1], 34, m->frames);
ShaderProgram *text_shader = res_shader("text_default");
dynarray_foreach(&m->entries, int i, MenuEntry *e, {
float p = offset + 20*i;
if(p < -y-10 || p > SCREEN_H+10)
continue;
float a = e->drawdata * 0.1;
float o = (p < 0? 1-p/(-y-10) : 1);
Color clr;
if(e->action == NULL) {
clr = *RGBA_MUL_ALPHA(0.5, 0.5, 0.5, 0.5*o);
} else {
float ia = 1-a;
clr = *RGBA_MUL_ALPHA(0.9 + ia * 0.1, 0.6 + ia * 0.4, 0.2 + ia * 0.8, (0.7 + 0.3 * a)*o);
}
r_color(&clr);
if(draw && i < m->entries.num_elements-1) {
draw(e, i, m->entries.num_elements, userdata);
} else if(e->name) {
text_draw(e->name, &(TextParams) {
.pos = { 20 - e->drawdata, 20*i },
.shader_ptr = text_shader,
});
}
});
r_mat_mv_pop();
}
void animate_menu_list_entry(MenuData *m, int i) {
MenuEntry *e = dynarray_get_ptr(&m->entries, i);
fapproach_asymptotic_p(&e->drawdata, 10 * (i == m->cursor), 0.2, 1e-4);
}
void animate_menu_list_entries(MenuData *m) {
dynarray_foreach_idx(&m->entries, int i, {
animate_menu_list_entry(m, i);
});
}
void animate_menu_list(MenuData *m) {
MenuEntry *s = dynarray_get_ptr(&m->entries, m->cursor);
int w = text_width(res_font("standard"), s->name, 0);
fapproach_asymptotic_p(&m->drawdata[0], 10 + w * 0.5, 0.1, 1e-5);
fapproach_asymptotic_p(&m->drawdata[1], w * 2, 0.1, 1e-5);
fapproach_asymptotic_p(&m->drawdata[2], 20 * m->cursor, 0.1, 1e-5);
animate_menu_list_entries(m);
}
void menu_action_close(MenuData *menu, void *arg) {
menu->state = MS_Dead;
}