From 80fda55e9897535094a8018f82e18f459e59b099 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Mon, 2 Jan 2023 11:50:25 +0100 Subject: [PATCH] level end screen and back to main menu * Add a level end screen that shows automatically when dead or level is finished * Lock player controls and GUI before the end screen shows * Loop back to the main menu after finishing a level (or dying) * Nerf combo chain score * Add a placeholder KO animation for the player, and associated logic --- TODO | 14 ++++- assets-cg/hud_panel.png | Bin 7005 -> 7267 bytes assets-cg/player/fxconv-metadata.txt | 2 +- assets-cg/player/player_down.aseprite | Bin 2183 -> 2575 bytes assets-cg/player/player_left.aseprite | Bin 1965 -> 2355 bytes assets-cg/player/player_right.aseprite | Bin 2157 -> 2550 bytes assets-cg/player/player_up.aseprite | Bin 2095 -> 2482 bytes src/anim.c | 2 + src/anim.h | 2 + src/comp/fighter.c | 5 +- src/game.c | 52 ++++++++++++++- src/game.h | 11 ++++ src/main.c | 84 +++++++++++++++++-------- src/render.c | 79 +++++++++++++++++++++++ 14 files changed, 220 insertions(+), 31 deletions(-) diff --git a/TODO b/TODO index a3de8d7..5ac9605 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,13 @@ Pixel art to do =============== +- Player KO animation. The timeline of a KO is: + 1. When the player's HP reaches 0, "KO" is played ("falling" animation) + 2. When "KO" finishes: + (a) "IdleKO" is played in a loop once ("lying down" animation) + (b) Simultaneously, end screen appears + It is possible to include some idle frames in "KO" in order to delay the + appearance of the end screen. - More varied attacks: * Sharp shadow attack (see skill icon for inspiration) * Circular slash? (We already have shock) @@ -19,9 +26,6 @@ Programming to do ================= Core mechanics: -* Compute a score at the end of each level - - Only increases, so we can show its real-time value - - Based on: combo chains, simult-kills, one-shot kills, waves survived * Have a deterministic mode for grinding * Use for items in battle (potions mainly) * Infinite mode for every level @@ -36,6 +40,10 @@ Core mechanics: Content: * Additional skills +Infrastructure: +* Better end screen +* Save and load scores (including with bindings) + Bugfixes: * Fix ability to attack continuously by holding SHIFT diff --git a/assets-cg/hud_panel.png b/assets-cg/hud_panel.png index 1620ce38d748301fe5f8f7180177b92d71642cf0..2de69148d9a7c1fba7739282bb4e90f244afec87 100644 GIT binary patch delta 3731 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 P00000NkvXXu0mjfC}iUJ diff --git a/assets-cg/player/fxconv-metadata.txt b/assets-cg/player/fxconv-metadata.txt index b8149ac..57a3c2b 100644 --- a/assets-cg/player/fxconv-metadata.txt +++ b/assets-cg/player/fxconv-metadata.txt @@ -2,5 +2,5 @@ player_*.aseprite: custom-type: aseprite-anim name_regex: (.*)\.aseprite frames_\1 center: 11, 19 - next: Idle=Idle, Walking=Walking + next: Idle=Idle, Walking=Walking, KO=IdleKO, IdleKO=IdleKO profile: p4 diff --git a/assets-cg/player/player_down.aseprite b/assets-cg/player/player_down.aseprite index b6a22309f1bf82ee295dc85086c1e1e0166c45fb..45b05892d73f1e8a6ac87199959f6b28ed58fd0f 100644 GIT binary patch delta 450 zcmZn{>=$9@=VD-Zu#|rydnKYOL_`t3u@ z__vmP$L{yfo5Jm-e~z(m*~9q!IS%q~LWK`(@!0BCe=lS1dqy>B7KJ*XMaT}x1M)!* zVMTEWFW4crevEt!iX2QErT^D|T)Fd(ia{Hb!cKC$@CieDC=4Dc0FJNF`kWgTmti+hP J*^uc58vuE+4ORdE diff --git a/assets-cg/player/player_left.aseprite b/assets-cg/player/player_left.aseprite index 857a324523f275b48a8fca64be53eb01ad95947e..d5927b51829af3a130993772b3a9a01d7c055c2a 100644 GIT binary patch delta 433 zcmZ3>zgdXgn3I9w!BYN-?3KLoObiUaKJqXqFf%alOziDrpU1$!AfdoE*^M!?o|l0a z$U_254Bq~H417o;Kqec5XG%^gNP>YuK>=tUL-@{5{}H^;=Yo)U|Dq$2cv)wok$6C@ z$ULBv{!f->RN1_q@gHk_2T=CcM@EJdK$;PVD}b0)fq{V;=z#wqTfsnpfuEsbPH4ZQ z&;bPwX5kfl|LY4mwYFI*bT=|G_^b_3@=Z|eoO{uUxbai&so z_tl>>R|g)M-*36evTE*^&c6?3bk6n0TCqPlwd;VC(nkJ}UrcO!;f9%rn?*7-7^AnAg=5G|R$d#%&mU#9Yo1I=a(-Q!c#CIeB delta 53 zcmdliw3eTJEjt6lgQdI^*(-VHGBPmy`pCwhz|6qFHnF#FGB1+~djSIjgMSOJVf4blJr diff --git a/assets-cg/player/player_right.aseprite b/assets-cg/player/player_right.aseprite index fb527c9447dc771a0d2f4c8ddf63ce61ec355891..76a2a7e451d8aa2fede9825c4c9cf9d3637462ff 100644 GIT binary patch delta 457 zcmaDW@J*Qg8z%$9gQffv*(-Ssm>3v-edJ+KU}j+8nb_OMK97NcK|+CTvKwP&Jud?< zkcR}A7`*-Y82FGxfJ`<9&y<{0kOTvRf&$PyhVY%A{v&vw&jlgz{zXS3@v_cFBk_P* zk$FHT{hut&sKQu3`6ILR=F^Oe*y_80YJPoWWHW-rroWXW%3c8M0MrK(Q9^^ zyYbt5Kg%rh@6@Z5k~sHk);3ma+jFnq?yS3WIgtNZTS{^6pDXp>BF=s{s%5-r#==kw zv?QO?`a&7WS$f7IXov4~xqZ)Zcy((}u&{N&-hSRM6}`F|(VG5`!y Bgc<+< delta 53 zcmew+{8oTHmxF=f!BXCd?3KLh7#SFTePm-$U}j)oo7meonU_h0y?}v%K|+CLvJzwF JW<#cZYyh2Y4e9^@ diff --git a/assets-cg/player/player_up.aseprite b/assets-cg/player/player_up.aseprite index 94ea48e65f4fad2c7454a022d137f3ca95760b61..ed899eded709617777860216860f9ea08c29c810 100644 GIT binary patch delta 445 zcmZ24ut}JG6DI@1gQffv*(-Uqm>3v-edJ+KU}j+8nb_OMK97NcK|+CTvKwP&Jud?< zkcR}A7`*-Y82FGxfJ`<9&y<{0kOTvRf&$PyhVY%A{v&vw&jlgz{zXS3@v_cFBk_P* zk$FHT{hut&sIqxIV*^`#8&LMwM@EJdK$;PV%Yc|wfq{Vq=z#wqTfsnpfuEsbPV9M4 zz5@mv%nmurC;k8bXp>54G+&2ew}KMW0YAemD zeSh7^{Zmu%&ug0g$Lx%otGjGBgteVxy0|#KtHP|R{@ZTGr-sZ|tAW-aJ0ly&2RVZc z#Th(cXZZGU^Bqv&aXNVG$=&})4xgVk(c_}i+LUN{-u)p@1=q|9{@uBWXQ$W(l^i8S qJvWenemy->id->anim_hit, 3); } else { - visible_set_anim(e, &anims_player_Hit, 3); + if(f->HP == 0) + visible_set_anim(e, &anims_player_KO, 4); + else + visible_set_anim(e, &anims_player_Hit, 3); } return damage; diff --git a/src/game.c b/src/game.c index 91a0c9d..59d4f38 100644 --- a/src/game.c +++ b/src/game.c @@ -51,6 +51,9 @@ bool game_load(game_t *g, level_t const *level) g->menu_time = fix(0); g->menu_open = false; g->menu_cursor = 0; + g->finish_time = fix(0); + g->victory = false; + g->final_screen_time = fix(-1); g->hud_wave_number_timer = fix(0); memset(&g->score, 0, sizeof g->score); @@ -216,7 +219,12 @@ void game_hud_anim_backpack_close(game_t *g) static int game_score_combo_chain(int chain) { - return (chain * chain) / 2; + if(chain < 3) + return 0; + else if(chain < 30) + return chain * 5; + else + return chain * 10; } static int game_score_simult_kills(int kills) @@ -237,6 +245,45 @@ int game_compute_score(game_t const *g) s->waves_survived * 16; } +bool game_victory_achieved(game_t const *g) +{ + fighter_t *player_f = getcomp(g->player, fighter); + + /* Must be alive. */ + if(player_f->HP <= 0) + return false; + /* All waves must have finished. */ + if(!g->level || g->event < g->level->event_count) + return false; + /* All enemies must be dead. */ + for(int i = 0; i < g->entity_count; i++) { + fighter_t *f = getcomp(g->entities[i], fighter); + if(f && f->enemy) + return false; + } + /* Combo must be finished. */ + if(g->combo_health > 0) + return false; + + return true; +} + +bool game_defeated(game_t const *g) +{ + fighter_t *player_f = getcomp(g->player, fighter); + visible_t *player_v = getcomp(g->player, visible); + + /* Must be dead. */ + if(player_f->HP > 0) + return false; + /* Must have finished the KO animation. */ + for(int i = 0; i < 4; i++) { + if(anim_in(player_v->anim.frame, &anims_player_IdleKO, i)) + return true; + } + return false; +} + //--- // Object management functions //--- @@ -499,6 +546,9 @@ void game_update_animations(game_t *g, fixed_t dt, fixed_t dt_rt) anim_state_update(&g->hud_backpack_anim, dt_rt); g->hud_wave_number_timer = max(0, g->hud_wave_number_timer - dt_rt); + + if(g->final_screen_time >= 0) + g->final_screen_time += dt_rt; } void game_update_effects(game_t *g, fixed_t dt) diff --git a/src/game.h b/src/game.h index 9d3344e..8ebe678 100644 --- a/src/game.h +++ b/src/game.h @@ -89,6 +89,12 @@ typedef struct game { bool menu_open; /* Cursor within the inventory menu */ int menu_cursor; + /* Time when the game was finished (0 while playing) */ + fixed_t finish_time; + /* Whether the game was won (valid when finish_time > 0) */ + bool victory; + /* Final screen time (negative while playing, >= 0 when game finished) */ + fixed_t final_screen_time; /* Current UI message, and how long it stays on (if not overwritten) */ char const *message; @@ -130,6 +136,11 @@ void game_hud_anim_backpack_close(game_t *g); /* Compute total score */ int game_compute_score(game_t const *g); +/* Determine whethey victory was achieved */ +bool game_victory_achieved(game_t const *g); +/* Determine whether players have lost */ +bool game_defeated(game_t const *g); + //--- // Managing dynamic game elements //--- diff --git a/src/main.c b/src/main.c index ae6be20..0178904 100644 --- a/src/main.c +++ b/src/main.c @@ -38,20 +38,8 @@ /* Record USB frames (used by main menu and game loop) */ bool rogue_life_video_capture = false; -int main(void) +static int menu_select_play_repeat(void) { - /* Enable %f etc. in printf()-like functions */ - __printf_enable_fp(); - /* Enable %D for decimal fixed-point in printf()-like functions */ - __printf_enable_fixed(); - /* Initialize the PRNG */ - srand(rtc_ticks()); - /* Initialize the benchmarking/profiling library */ - prof_init(); - /* Open the USB connection (for screenshots with fxlink) */ - usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL }; - usb_open(interfaces, GINT_CALL_NULL); - int lv = menu_level_select(0); if(lv == -1) return 1; @@ -356,7 +344,8 @@ int main(void) if(ev.key == KEY_MENU) gint_osmenu(); - if(ev.key == KEY_EXIT) + if(ev.key == KEY_EXIT && game.finish_time > 0 && + game.time_total >= game.finish_time + fix(2.0)) stop = true; /* Debug settings */ @@ -454,13 +443,13 @@ int main(void) camera_zoom(c, c->zoom - 1); #endif - if(!debug.paused && !game.menu_open && ev.key == KEY_SHIFT - && !debug.dev_menu) + if(!debug.paused && !game.menu_open && !(game.finish_time > 0) + && ev.key == KEY_SHIFT && !debug.dev_menu) attack = true; /* Menus */ - if(!debug.paused && ev.key == KEY_F6 && !debug.dev_menu - && !keydown(KEY_ALPHA)) { + if(!debug.paused && !debug.dev_menu && !(game.finish_time > 0) + && ev.key == KEY_F6 && !keydown(KEY_ALPHA)) { if(game.menu_open) game_hud_anim_backpack_close(&game); else @@ -469,7 +458,7 @@ int main(void) } /* Inventory movement */ - if(!debug.paused && game.menu_open) { + if(!debug.paused && game.menu_open && !(game.finish_time > 0)) { int y = game.menu_cursor / 4, x = game.menu_cursor % 4; y = y + (ev.key == KEY_DOWN) - (ev.key == KEY_UP); x = x + (ev.key == KEY_RIGHT) - (ev.key == KEY_LEFT); @@ -479,7 +468,8 @@ int main(void) } /* Using and equipping items */ - if(!debug.paused && game.menu_open && ev.key == KEY_SHIFT) { + if(!debug.paused && game.menu_open && !(game.finish_time > 0) + && ev.key == KEY_SHIFT) { int item = player_data.inventory[game.menu_cursor]; int slot = item_equipment_slot(item); @@ -525,17 +515,23 @@ int main(void) /* Player movement input */ int input_dir = -1, next_dir = -1; - if(!debug.paused && !game.menu_open && player_f->HP > 0) { + if(!debug.paused && !game.menu_open && !(game.finish_time > 0) + && player_f->HP > 0) { if(keydown(KEY_UP)) input_dir = UP; if(keydown(KEY_DOWN)) input_dir = DOWN; if(keydown(KEY_LEFT)) input_dir = LEFT; if(keydown(KEY_RIGHT)) input_dir = RIGHT; } - next_dir = (input_dir >= 0) ? input_dir : player_p->facing; + if(game.finish_time > 0) + next_dir = -1; + else if(input_dir >= 0) + next_dir = input_dir; + else + next_dir = player_p->facing; /* Player skills (including movement skills) */ bool can_use_skill = !debug.paused && !game.menu_open - && player_f->HP > 0 && !debug.dev_menu; + && !(game.finish_time > 0) && player_f->HP > 0 && !debug.dev_menu; if(can_use_skill && keydown(KEY_F1)) skill_use(&game, player, 1, fdir(next_dir)); @@ -580,8 +576,8 @@ int main(void) } /* Player attack */ - if(!debug.paused && !game.menu_open && player_f->HP > 0 && attack - && !player_f->current_attack) { + if(!debug.paused && !game.menu_open && !(game.finish_time > 0) + && player_f->HP > 0 && attack && !player_f->current_attack) { if(player_f->skills[0] == AOE_SLASH) { int hit_number=0, effect=AOE_SLASH; @@ -676,6 +672,23 @@ int main(void) if(player_f->HP > 0 && game_current_event_finished(&game)) game_next_event(&game); + /* Victory or game over */ + if(!game.finish_time && game_defeated(&game)) { + game.finish_time = game.time_total; + game.victory = false; + game.menu_open = false; + } + else if(!game.finish_time && game_victory_achieved(&game)) { + game.finish_time = game.time_total; + game.victory = true; + game.menu_open = false; + } + /* Start the final screen when everything has settled */ + if(game.finish_time > 0 && !game.menu_time + && game.final_screen_time < 0) { + game.final_screen_time = fix(0); + } + /* Visual pathfinding debug */ if(debug.show_path) { pfg_path_free(&debug.grid_path); @@ -693,6 +706,27 @@ int main(void) } timer_stop(timer_id); + return 0; +} + +int main(void) +{ + /* Enable %f etc. in printf()-like functions */ + __printf_enable_fp(); + /* Enable %D for decimal fixed-point in printf()-like functions */ + __printf_enable_fixed(); + /* Initialize the PRNG */ + srand(rtc_ticks()); + /* Initialize the benchmarking/profiling library */ + prof_init(); + /* Open the USB connection (for screenshots with fxlink) */ + usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL }; + usb_open(interfaces, GINT_CALL_NULL); + + while(1) { + menu_select_play_repeat(); + } + prof_quit(); usb_close(); return 1; diff --git a/src/render.c b/src/render.c index 9ae263f..f559402 100644 --- a/src/render.c +++ b/src/render.c @@ -418,6 +418,42 @@ static void render_panel(int x, int y, bool hflip) dsubimage(x, y, &img_hud_panel, sx, sy, w, 30, DIMAGE_NONE); } +void render_full_panel(int x, int y, int w, int h) +{ + extern bopti_image_t img_hud_panel; + w = max(w, 100); + h = max(h, 60); + + /* Top row */ + dsubimage(x, y, &img_hud_panel, 0, 0, 50, 30, DIMAGE_NONE); + for(int sx = 50; sx < w-50;) { + int frame_w = min(w-50-sx, 44); + dsubimage(x+sx, y, &img_hud_panel, 50, 0, frame_w, 30, DIMAGE_NONE); + sx += frame_w; + } + dsubimage(x+w-50, y, &img_hud_panel, 94, 0, 50, 30, DIMAGE_NONE); + + /* Middle row */ + for(int sy = 30; sy < h-30;) { + int frame_h = min(h-30-sy, 10); + dsubimage(x, y+sy, &img_hud_panel, 0, 30, 50, 10, DIMAGE_NONE); + dsubimage(x+w-50, y+sy, &img_hud_panel, 94, 30, 50, 10, DIMAGE_NONE); + sy += frame_h; + } + if(w > 100 && h > 60) + drect(x+50, y+30, x+w-51, y+h-31, RGB24(0x202828)); + + /* Bottom row */ + dsubimage(x, y+h-30, &img_hud_panel, 0, 40, 50, 30, DIMAGE_NONE); + for(int sx = 50; sx < w-50;) { + int frame_w = min(w-50-sx, 44); + dsubimage(x+sx, y+h-30, &img_hud_panel, 50, 40, frame_w, 30, + DIMAGE_NONE); + sx += frame_w; + } + dsubimage(x+w-50, y+h-30, &img_hud_panel, 94, 40, 50, 30, DIMAGE_NONE); +} + uint32_t time_render_map = 0; uint32_t time_render_hud = 0; @@ -837,6 +873,49 @@ void render_game(game_t const *g, bool show_hitboxes) } } + /* Render final panel */ + if(g->final_screen_time >= 0 && g->victory) { + int PANEL_Y = cubic(-(DHEIGHT-30), 30, g->final_screen_time, fix(2.0)); + render_full_panel(30, PANEL_Y, DWIDTH-30*2, DHEIGHT-30-40); + + dtext_opt(DWIDTH/2, PANEL_Y+12, C_WHITE, C_NONE, DTEXT_CENTER, + DTEXT_TOP, "Victory!", -1); + + dprint(45, PANEL_Y+30, 0x5555, "Score"); + dprint(300, PANEL_Y+30, 0x5555, "%d", game_compute_score(g)); + + dprint(45, PANEL_Y+45, C_WHITE, "Time survived"); + dprint(300, PANEL_Y+45, C_WHITE, "%d", + g->score.waves_survived); + + dprint(45, PANEL_Y+60, C_WHITE, "Kills (%d one-shot)", + g->score.one_shot_kills); + dprint(300, PANEL_Y+60, C_WHITE, "%d", + g->score.kill_number); + + dprint(45, PANEL_Y+75, C_WHITE, "Combos (longest: %d)", + g->score.longest_combo_chain); + dprint(300, PANEL_Y+75, C_WHITE, "%d", + g->score.combo_chains); + + dprint(45, PANEL_Y+90, C_WHITE, "Simult. kills (largest: %d)", + g->score.largest_simult_kill); + dprint(300, PANEL_Y+90, C_WHITE, "%d", + g->score.simult_kills); + + dtext_opt(DWIDTH/2, PANEL_Y+120, C_WHITE, C_NONE, DTEXT_CENTER, + DTEXT_TOP, "EXIT: Back to menu", -1); + } + if(g->final_screen_time >= 0 && !g->victory) { + int PANEL_Y = cubic(-60, DHEIGHT/2-30, g->final_screen_time, fix(2.0)); + render_full_panel(DWIDTH/2-90, PANEL_Y, 180, 60); + + dtext_opt(DWIDTH/2, PANEL_Y+15, C_RED, C_NONE, DTEXT_CENTER, + DTEXT_TOP, "Defeat!", -1); + dtext_opt(DWIDTH/2, PANEL_Y+30, C_WHITE, C_NONE, DTEXT_CENTER, + DTEXT_TOP, "EXIT: Back to menu", -1); + } + prof_leave(ctx); time_render_hud = prof_time(ctx); dfont(old_font);