diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d3f1af..5670453 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(ASSETS # HUD assets-cg/hud.png assets-cg/hud_arcade_font.png + assets-cg/hud_arcade_font2.png assets-cg/hud_backpack.ase assets-cg/hud_combo.png assets-cg/hud_delay.png @@ -71,6 +72,7 @@ set(ASSETS assets-cg/hud_life.png assets-cg/hud_panel.png assets-cg/hud_small.png + assets-cg/hud_top.png assets-cg/hud_xp.ase assets-cg/skillicons.png assets-cg/font_hud.png diff --git a/TODO b/TODO index 866b9ad..a3de8d7 100644 --- a/TODO +++ b/TODO @@ -11,6 +11,7 @@ Pixel art to do * Skeletons * More tifuciles (including mamafucile) * Crypt boss (Dracula / Skeleton archmagus / etc) +- Better dash animation - Environment damage - Particles after monsters' deaths? Slime puddles, dark enemy outlines, ... @@ -35,6 +36,9 @@ Core mechanics: Content: * Additional skills +Bugfixes: +* Fix ability to attack continuously by holding SHIFT + Extra details ============= diff --git a/assets-cg/fxconv-metadata.txt b/assets-cg/fxconv-metadata.txt index 511f6e5..a6873ba 100644 --- a/assets-cg/fxconv-metadata.txt +++ b/assets-cg/fxconv-metadata.txt @@ -17,6 +17,8 @@ hud_backpack.ase: hud_arcade_font.png: profile: p8 +hud_arcade_font2.png: + profile: p8 font_rogue.png: type: font diff --git a/assets-cg/hud_arcade_font2.png b/assets-cg/hud_arcade_font2.png new file mode 100644 index 0000000..3c7e890 Binary files /dev/null and b/assets-cg/hud_arcade_font2.png differ diff --git a/assets-cg/hud_arcade_font2.xcf b/assets-cg/hud_arcade_font2.xcf new file mode 100644 index 0000000..16a92b9 Binary files /dev/null and b/assets-cg/hud_arcade_font2.xcf differ diff --git a/assets-cg/hud_top.png b/assets-cg/hud_top.png new file mode 100644 index 0000000..c1479c1 Binary files /dev/null and b/assets-cg/hud_top.png differ diff --git a/src/comp/fighter.c b/src/comp/fighter.c index 6f963f7..c119c73 100644 --- a/src/comp/fighter.c +++ b/src/comp/fighter.c @@ -61,6 +61,7 @@ int fighter_damage(entity_t *e, int base_damage) if(f->HP == 0) return 0; + bool full_health = (f->HP >= f->HP_max); base_damage = max(base_damage - f->DEF, 0); int variation = (base_damage >= 4) ? (rand() % (base_damage / 4)) : 0; @@ -71,8 +72,11 @@ int fighter_damage(entity_t *e, int base_damage) else f->HP -= damage; if(f->enemy) { - if(f->HP == 0) + if(f->HP == 0) { visible_set_anim(e, f->enemy->id->anim_death, 4); + if(full_health) + f->one_shot_killed = true; + } else visible_set_anim(e, f->enemy->id->anim_hit, 3); } diff --git a/src/comp/fighter.h b/src/comp/fighter.h index 5179b05..1063a00 100644 --- a/src/comp/fighter.h +++ b/src/comp/fighter.h @@ -52,6 +52,8 @@ typedef struct fixed_t stun_delay; fixed_t invulnerability_delay; fixed_t speed_delay; + /* Whether entity was one-shot killed */ + bool one_shot_killed; } fighter_t; diff --git a/src/game.c b/src/game.c index d45d89f..91a0c9d 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->hud_wave_number_timer = fix(0); + + memset(&g->score, 0, sizeof g->score); game_next_event(g); return true; @@ -115,6 +118,11 @@ bool game_current_event_finished(game_t const *g) void game_next_event(game_t *g) { + if(g->event >= 0 && g->level + && g->level->events[g->event].type == LEVEL_EVENT_WAVE) { + g->score.waves_survived++; + } + if(g->event >= g->level->event_count) return; g->event++; @@ -128,6 +136,7 @@ void game_next_event(game_t *g) if(event && event->type == LEVEL_EVENT_WAVE) { g->wave_number++; + g->hud_wave_number_timer = fix(1); /* Copy the amounts of monsters to spawn for the next wave */ g->wave_left = malloc(event->wave->entry_count * sizeof *g->wave_left); @@ -135,8 +144,6 @@ void game_next_event(game_t *g) for(int i = 0; i < event->wave->entry_count; i++) g->wave_left[i] = event->wave->entries[i].amount; - - game_message(g, fix(2.0), "Wave %d incoming!", g->wave_number); } if(event && event->type == LEVEL_EVENT_ITEM) { int x=-1, y=-1; @@ -207,6 +214,29 @@ void game_hud_anim_backpack_close(game_t *g) g->hud_backpack_anim.elapsed = 0; } +static int game_score_combo_chain(int chain) +{ + return (chain * chain) / 2; +} + +static int game_score_simult_kills(int kills) +{ + return (kills < 3) ? 0 : kills * kills; +} + +int game_compute_score(game_t const *g) +{ + score_t const *s = &g->score; + return + s->kill_number * 3 + + s->longest_combo_chain * 8 + + s->combo_chains + + s->largest_simult_kill * 17 + + s->simult_kills + + s->one_shot_kills * 4 + + s->waves_survived * 16; +} + //--- // Object management functions //--- @@ -299,6 +329,16 @@ void game_remove_dead_entities(game_t *g) g->combo++; g->combo_health = fix(1); + /* Update score-related metrics */ + g->score.kill_number++; + g->score.one_shot_kills += (f->one_shot_killed == true); + g->score.longest_combo_chain = max(g->score.longest_combo_chain, + g->combo); + g->score.current_simult_kills++; + g->score.largest_simult_kill = max(g->score.largest_simult_kill, + g->score.current_simult_kills); + g->score.current_simult_kill_timer = fix(1); + entity_mark_to_delete(e); } } @@ -457,6 +497,8 @@ void game_update_animations(game_t *g, fixed_t dt, fixed_t dt_rt) anim_state_update(&g->hud_xp_anim, 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); } void game_update_effects(game_t *g, fixed_t dt) @@ -475,8 +517,31 @@ void game_update_effects(game_t *g, fixed_t dt) g->combo_health -= dt / 5; if(g->combo_health <= 0) { g->combo_health = 0; + + if(g->combo >= 30) + game_message(g, fix(2.0), "Fabulous %d-combo!", g->combo); + else if(g->combo >= 10) + game_message(g, fix(2.0), "Excellent %d-combo!", g->combo); + + g->score.combo_chains += game_score_combo_chain(g->combo); g->combo = 0; } + + g->score.current_simult_kill_timer -= dt * 4; + if(g->score.current_simult_kill_timer <= 0) { + g->score.current_simult_kill_timer = 0; + + if(g->score.current_simult_kills >= 8) + game_message(g, fix(2.0), "\"This is a massacre!\""); + else if(g->score.current_simult_kills >= 5) + game_message(g, fix(2.0), "\"Begone, idiots!\""); + else if(g->score.current_simult_kills >= 3) + game_message(g, fix(2.0), "\"Come and get me!\""); + + g->score.simult_kills += + game_score_simult_kills(g->score.current_simult_kills); + g->score.current_simult_kills = 0; + } } void game_update_aoes(game_t *g, fixed_t dt) diff --git a/src/game.h b/src/game.h index 2e2d8c6..9d3344e 100644 --- a/src/game.h +++ b/src/game.h @@ -13,6 +13,28 @@ #include "comp/entity.h" +typedef struct score { + /* Number of kills */ + int kill_number; + /* Longest combo chain so far */ + int longest_combo_chain; + /* Total (accumulated) combo chain score */ + int combo_chains; + /* Largest simultaneous kill streak */ + int largest_simult_kill; + /* Total (accumulated) kill streak score */ + int simult_kills; + /* Number of one shots kills */ + int one_shot_kills; + /* Number of waves survived */ + int waves_survived; + + /* Current kill streak and its timer (basically a faster combo) */ + int current_simult_kills; + fixed_t current_simult_kill_timer; + +} score_t; + typedef struct game { /* The map's coordinate system is the primary coordinate system in all of this game's code */ @@ -53,6 +75,8 @@ typedef struct game { at a variable rate) */ int combo; fixed_t combo_health; + /* Score information */ + score_t score; /* XP bar animation */ anim_state_t hud_xp_anim; @@ -69,6 +93,8 @@ typedef struct game { /* Current UI message, and how long it stays on (if not overwritten) */ char const *message; fixed_t message_time; + /* Flashing wave number timer */ + fixed_t hud_wave_number_timer; } game_t; @@ -101,6 +127,9 @@ void game_hud_anim_backpack_item(game_t *g); void game_hud_anim_backpack_open(game_t *g); void game_hud_anim_backpack_close(game_t *g); +/* Compute total score */ +int game_compute_score(game_t const *g); + //--- // Managing dynamic game elements //--- diff --git a/src/main.c b/src/main.c index ed5a034..ae6be20 100644 --- a/src/main.c +++ b/src/main.c @@ -194,12 +194,9 @@ int main(void) /* Developer/tweaking menu */ if(debug.show_vars) { uint32_t *vram = (void *)gint_vram; - for(int y = 0; y < 224; y++) { - for(int i = 0; i < 396/4; i++) - vram[i] = (vram[i] & 0xf7def7de) >> 1; - vram += 396/2; + for(int i = 0; i < 396 * 224 / 2; i++) { + vram[i] = (vram[i] & 0xf7def7de) >> 1; } - uint16_t gray = C_RGB(16, 16, 16); dprint(3, 40, C_WHITE, "Player speed: %g tiles/s", @@ -217,6 +214,27 @@ int main(void) dprint(3, 145, C_WHITE, "Dash duration: %g s", f2double(player_data.mechanical_limits.dash_duration)); dprint(15, 160, gray, "[)] -/+ [sin]"); + + dprint(DWIDTH/2, 40, C_WHITE, "Score: %d", + game_compute_score(&game)); + dprint(DWIDTH/2, 55, C_WHITE, "kill_number: %d", + game.score.kill_number); + dprint(DWIDTH/2, 70, C_WHITE, "longest_combo_chain: %d", + game.score.longest_combo_chain); + dprint(DWIDTH/2, 85, C_WHITE, "combo_chains: %d", + game.score.combo_chains); + dprint(DWIDTH/2, 100, C_WHITE, "largest_simult_kill: %d", + game.score.largest_simult_kill); + dprint(DWIDTH/2, 115, C_WHITE, "simult_kills: %d", + game.score.simult_kills); + dprint(DWIDTH/2, 130, C_WHITE, "one_shot_kills: %d", + game.score.one_shot_kills); + dprint(DWIDTH/2, 145, C_WHITE, "waves_survived: %d", + game.score.waves_survived); + dprint(DWIDTH/2, 160, C_WHITE, "current_simult_kills: %d", + game.score.current_simult_kills); + dprint(DWIDTH/2, 175, C_WHITE, "*_timer: %f", + f2double(game.score.current_simult_kill_timer)); } if(debug.dev_menu_view) { diff --git a/src/render.c b/src/render.c index 2863505..9ae263f 100644 --- a/src/render.c +++ b/src/render.c @@ -360,6 +360,14 @@ static void render_info(int x, int y, game_t const *g) snprintf(str, 32, "Wave %d", level_wave_count(g->level)); dsize(str, NULL, &intro_w, NULL); snprintf(str, 32, "Wave %d", g->wave_number); + + int wave_color_r = fround(g->hud_wave_number_timer * 31); + if(g->hud_wave_number_timer) { + dtext(x+8, y-2, C_RGB(wave_color_r, 0, 0), str); + dtext(x+8, y, C_RGB(wave_color_r, 0, 0), str); + dtext(x+7, y-1, C_RGB(wave_color_r, 0, 0), str); + dtext(x+9, y-1, C_RGB(wave_color_r, 0, 0), str); + } dtext(x+8, y-1, C_WHITE, str); x += intro_w + 20; @@ -473,30 +481,34 @@ int render_small_text(int x, int y, int color, char const *text, int size) return 0; } -static int render_arcade_char_size(int c) +static int render_arcade_char_size(int c, int font_size) { if(c < '0' || c > '9') return 0; - return 7 - (c == '1' || c == '7') + (c == '4'); + if(font_size == 1) + return 7 - (c == '1' || c == '7') + (c == '4'); + if(font_size == 2) + return 8 + (c == '0' || c == '4'); + return 0; } - -int render_arcade_dsize(int value) +int render_arcade_dsize(int value, int font_size) { char str[16]; sprintf(str, "%d", value); int pixels = 0; for(int i = 0; str[i]; i++) - pixels += render_arcade_char_size(str[i]); - return pixels; - + pixels += render_arcade_char_size(str[i], font_size); return pixels; } -void render_arcade(int x, int y, int halign, int value, int color_style) +void render_arcade(int x, int y, int halign, int value, int color_style, + int font_size) { extern bopti_image_t img_hud_arcade_font; - int w = render_arcade_dsize(value); + extern bopti_image_t img_hud_arcade_font2; + + int w = render_arcade_dsize(value, font_size); if(halign == DTEXT_RIGHT) x -= w; else if(halign == DTEXT_CENTER) @@ -510,9 +522,14 @@ void render_arcade(int x, int y, int halign, int value, int color_style) sprintf(str, "%d", value); for(int i = 0; str[i]; i++) { - int w = render_arcade_char_size(str[i]); - dsubimage(x, y, &img_hud_arcade_font, 1 + 9 * (str[i] - '0'), - 1 + 9 * color_style, w, 8, DIMAGE_NONE); + int w = render_arcade_char_size(str[i], font_size); + + if(font_size == 1) + dsubimage(x, y, &img_hud_arcade_font, 1 + 9 * (str[i] - '0'), + 1 + 9 * color_style, w, 8, DIMAGE_NONE); + if(font_size == 2) + dsubimage(x, y, &img_hud_arcade_font2, 1 + 10 * (str[i] - '0'), + 1 + 12 * color_style, w, 12, DIMAGE_NONE); x += w; } } @@ -624,12 +641,23 @@ void render_game(game_t const *g, bool show_hitboxes) int HUD_Y = cubic(DHEIGHT+30, DHEIGHT, gui_time, MAX_GUI_TIME); int HEADER_Y = cubic(-15, 2, gui_time, MAX_GUI_TIME); + /* Render score box */ + extern bopti_image_t img_hud_top; + dimage(6, HEADER_Y - 2, &img_hud_top); + int score = game_compute_score(g); + render_arcade(42, HEADER_Y, DTEXT_LEFT, score, + 0 + (score >= 100) + (score >= 1000), 2); + /* Render wave information */ - render_info(0, HEADER_Y, g); + render_info(90, HEADER_Y, g); /* Render current message */ - if(g->message) - dtext(8, HEADER_Y + 13, C_RGB(20, 20, 20), g->message); + if(g->message) { + dtext_opt(DWIDTH - 8, HEADER_Y + 16, RGB24(0x15171a), C_NONE, + DTEXT_RIGHT, DTEXT_TOP, g->message, -1); + dtext_opt(DWIDTH - 8, HEADER_Y + 15, RGB24(0xabb1ba), C_NONE, + DTEXT_RIGHT, DTEXT_TOP, g->message, -1); + } /* Render HUD */ extern bopti_image_t img_hud; @@ -704,7 +732,7 @@ void render_game(game_t const *g, bool show_hitboxes) dsubimage(224, HUD_Y - 9 - fill_height, &img_hud_combo, 0, img_hud_combo.height - fill_height, img_hud_combo.width, fill_height, DIMAGE_NONE); - render_arcade(234, HUD_Y-25, DTEXT_CENTER, g->combo, -1); + render_arcade(234, HUD_Y-25, DTEXT_CENTER, g->combo, -1, 1); if(g->menu_time > 0) { int x1 = cubic(-130, 0, g->menu_time, fix(1)); diff --git a/src/render.h b/src/render.h index 14bb471..a532d5d 100644 --- a/src/render.h +++ b/src/render.h @@ -76,5 +76,6 @@ void render_pfg_all2one(pfg_all2one_t const *paths, camera_t const *c, int render_small_text(int x, int y, int color, char const *text, int size); /* Colored arcade font. */ -int render_arcade_dsize(int value); -void render_arcade(int x, int y, int halign, int value, int color_style); +int render_arcade_dsize(int value, int font_size); +void render_arcade(int x, int y, int halign, int value, int color_style, + int font_size); diff --git a/src/util.c b/src/util.c index ac00017..88e0723 100644 --- a/src/util.c +++ b/src/util.c @@ -212,6 +212,9 @@ void font_damage_print(int x, int y, int color, int align_x, int align_y, int cubic(int start, int end, fixed_t t, fixed_t tmax) { + if(t >= tmax) + return end; + t = fdiv(t, tmax); fixed_t x = fix(1.0) - fmul(fmul(fix(1.0)-t, fix(1.0)-t), fix(1.0)-t); x = fix(start) + fmul(fix(end - start), x);