add core scoring mechanics and score computation

This commit is contained in:
Lephenixnoir 2022-12-31 18:26:57 +01:00
parent 03e9a822e3
commit c1122f511f
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
14 changed files with 184 additions and 26 deletions

View File

@ -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

4
TODO
View File

@ -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
=============

View File

@ -17,6 +17,8 @@ hud_backpack.ase:
hud_arcade_font.png:
profile: p8
hud_arcade_font2.png:
profile: p8
font_rogue.png:
type: font

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

BIN
assets-cg/hud_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -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);
}

View File

@ -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;

View File

@ -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)

View File

@ -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
//---

View File

@ -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) {

View File

@ -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));

View File

@ -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);

View File

@ -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);