/* * This software is licensed under the terms of the MIT License. * See COPYING for further information. * --- * Copyright (c) 2011-2024, Lukas Weber . * Copyright (c) 2012-2024, Andrei Alexeyev . */ #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; }