diff --git a/CMakeLists.txt b/CMakeLists.txt index 47bce2c..bad56c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ set(SOURCES src/main.c src/map.c src/pathfinding.c + src/player.c src/render.c src/util.c @@ -45,9 +46,9 @@ set(ASSETS # HUD assets-cg/hud.png assets-cg/hud_life.png - assets-cg/hud_wave.png - assets-cg/hud_wave2.png + assets-cg/hud_xp.png assets-cg/skillicons.png + assets-cg/font_hud.png # Player animations assets-cg/player/player_up.aseprite assets-cg/player/player_right.aseprite diff --git a/assets-cg/enemies/fxconv-metadata.txt b/assets-cg/enemies/fxconv-metadata.txt index 68a698d..8c92719 100644 --- a/assets-cg/enemies/fxconv-metadata.txt +++ b/assets-cg/enemies/fxconv-metadata.txt @@ -1,7 +1,7 @@ *.aseprite: custom-type: aseprite-anim name_regex: (.*)\.aseprite frames_\1 - profile: p8 + profile: p4 next: Idle=Idle, Walking=Walking, Hit=Idle slime_left.aseprite: diff --git a/assets-cg/font_hud.png b/assets-cg/font_hud.png new file mode 100644 index 0000000..d4f721a Binary files /dev/null and b/assets-cg/font_hud.png differ diff --git a/assets-cg/fxconv-metadata.txt b/assets-cg/fxconv-metadata.txt index b46e19b..35b7a99 100644 --- a/assets-cg/fxconv-metadata.txt +++ b/assets-cg/fxconv-metadata.txt @@ -3,6 +3,15 @@ name_regex: (.*)\.png img_\1 profile: p8 +hud.png: + profile: p4 +hud_life.png: + profile: p4 +hud_xp.png: + profile: p4 +skillicons.png: + profile: p4 + font_rogue.png: type: font name: font_rogue @@ -12,3 +21,13 @@ font_rogue.png: grid.border: 0 proportional: true height: 9 + +font_hud.png: + type: font + name: font_hud + charset: numeric + grid.size: 4x5 + grid.padding: 1 + grid.border: -1 + proportional: true + height: 5 diff --git a/assets-cg/hud.png b/assets-cg/hud.png index 8cfaaae..60abba1 100644 Binary files a/assets-cg/hud.png and b/assets-cg/hud.png differ diff --git a/assets-cg/hud_xp.png b/assets-cg/hud_xp.png new file mode 100644 index 0000000..d23fd45 Binary files /dev/null and b/assets-cg/hud_xp.png differ diff --git a/assets-cg/player/fxconv-metadata.txt b/assets-cg/player/fxconv-metadata.txt index cee55d8..b8149ac 100644 --- a/assets-cg/player/fxconv-metadata.txt +++ b/assets-cg/player/fxconv-metadata.txt @@ -3,4 +3,4 @@ player_*.aseprite: name_regex: (.*)\.aseprite frames_\1 center: 11, 19 next: Idle=Idle, Walking=Walking - profile: p8 + profile: p4 diff --git a/assets-cg/tilesets/fxconv-metadata.txt b/assets-cg/tilesets/fxconv-metadata.txt index 236e7a8..2f10498 100644 --- a/assets-cg/tilesets/fxconv-metadata.txt +++ b/assets-cg/tilesets/fxconv-metadata.txt @@ -1,3 +1,4 @@ *.tsx: custom-type: tiled-tileset name_regex: (.*)\.tsx tileset_\1 + profile: p4 diff --git a/src/comp/fighter.c b/src/comp/fighter.c index b439534..26e9032 100644 --- a/src/comp/fighter.c +++ b/src/comp/fighter.c @@ -17,10 +17,9 @@ void fighter_set_stats(fighter_t *f, fighter_stat_model_t const *model, int level, fixed_t factor) { f->HP_max = instantiate_stat(model->HP, fmul(fix(2.0), factor), level); - f->ATK = instantiate_stat(model->ATK, fmul(fix(1.6), factor), level); - f->MAG = instantiate_stat(model->MAG, fmul(fix(1.6), factor), level); - f->DEF = instantiate_stat(model->DEF, fmul(fix(0.7), factor), level); - f->HP = f->HP_max; + f->ATK = instantiate_stat(model->ATK, fix(1.6), level); + f->MAG = instantiate_stat(model->MAG, fix(1.6), level); + f->DEF = instantiate_stat(model->DEF, fix(0.7), level); } int fighter_damage(entity_t *e, int base_damage) diff --git a/src/enemies.c b/src/enemies.c index 6a666c2..459cf84 100644 --- a/src/enemies.c +++ b/src/enemies.c @@ -30,6 +30,7 @@ static enemy_t const slime_1 = { .DEF = fix(1.4), }, .shadow_size = 4, + .xp = 2, .z = 0, }; @@ -49,6 +50,7 @@ static enemy_t const bat_2 = { .DEF = fix(1.2), }, .shadow_size = 4, + .xp = 6, .z = fix(0.75), }; @@ -68,6 +70,7 @@ static enemy_t const fire_slime_4 = { .DEF = fix(1.4), }, .shadow_size = 4, + .xp = 14, .z = 0, }; @@ -87,6 +90,7 @@ static enemy_t const gunslinger_8 = { .DEF = fix(1.2), }, .shadow_size = 4, + .xp = 30, .z = fix(0.375), }; @@ -140,6 +144,7 @@ entity_t *enemy_make(int enemy_id) f->enemy_data = data; fighter_set_stats(f, &data->stats, data->level, fix(1.0)); + f->HP = f->HP_max; f->combo_length = 1; return e; diff --git a/src/enemies.h b/src/enemies.h index 249a6b8..8e3988f 100644 --- a/src/enemies.h +++ b/src/enemies.h @@ -27,6 +27,8 @@ typedef struct enemy uint8_t level; /* Shadow size */ uint8_t shadow_size; + /* XP points attributed on death */ + uint16_t xp; /* Rendering elevation */ fixed_t z; diff --git a/src/game.c b/src/game.c index 6d7649e..3b7c6cc 100644 --- a/src/game.c +++ b/src/game.c @@ -1,6 +1,7 @@ #include "game.h" #include "util.h" #include "enemies.h" +#include "player.h" #include "comp/fighter.h" #include "comp/physical.h" @@ -16,9 +17,6 @@ bool game_load(game_t *g, level_t const *level) g->map = level->map; -// TODO: Tiled converter: specify solid tiles -// t->solid = (t->base == 0) || (t->base >= 16); - camera_init(&g->camera, g->map); g->time_total = fix(0); @@ -157,6 +155,9 @@ void game_remove_dead_entities(game_t *g) aoe_t *aoe = getcomp(f->current_attack, aoe); aoe->origin = NULL; } + /* Give XP points to player */ + player_add_xp(g->player_data, f->enemy_data->xp); + entity_mark_to_delete(e); } diff --git a/src/game.h b/src/game.h index 6816371..f47951a 100644 --- a/src/game.h +++ b/src/game.h @@ -8,6 +8,7 @@ #include "render.h" #include "level.h" #include "pathfinding.h" +#include "player.h" #include "comp/entity.h" @@ -27,6 +28,8 @@ typedef struct game { int entity_count; /* Player; this must be one of the entities loaded in the game */ entity_t *player; + /* Associated player data */ + player_t *player_data; /* Field of movement to reach the player (used by most enemy AIs) */ pfg_all2one_t paths_to_player; diff --git a/src/main.c b/src/main.c index f286340..8744776 100644 --- a/src/main.c +++ b/src/main.c @@ -13,6 +13,7 @@ #include "level.h" #include "map.h" #include "pathfinding.h" +#include "player.h" #include "render.h" #include @@ -92,6 +93,10 @@ int main(void) pfc_path_t continuous_path; /* Show performance metrics */ bool show_perf; + /* Coordinates of a point of interest */ + int some_x, some_y; + /* Game is paused */ + bool paused; } debug; memset(&debug, 0, sizeof debug); @@ -100,21 +105,30 @@ int main(void) // Spawn player //--- - mechanical_limits_t ml_player = { - .max_speed = fix(4.5), - .friction = fix(0.7), - .dash_speed = fix(20), - .dash_duration = fix(1) / 8, - .dash_cooldown = fix(1), - }; - fighter_stat_model_t stats_player = { - .HP = fix(1.5), - .ATK = fix(1.5), - .DEF = fix(1.5), - .MAG = fix(1.5), + player_t player_data = { + .entity = NULL, + .mechanical_limits = { + .max_speed = fix(4.5), + .friction = fix(0.7), + .dash_speed = fix(20), + .dash_duration = fix(1) / 8, + .dash_cooldown = fix(1), + }, + .stat_model = { + .HP = fix(1.5), + .ATK = fix(1.5), + .DEF = fix(1.5), + .MAG = fix(1.5), + }, + .xp_level = 0, + .xp_to_next_level = 0, + .xp_current = 0, }; + game.player_data = &player_data; entity_t *player = entity_make(physical, visible, mechanical, fighter); + player_data.entity = player; + physical_t *player_p = getcomp(player, physical); visible_t *player_v = getcomp(player, visible); mechanical_t *player_m = getcomp(player, mechanical); @@ -124,14 +138,16 @@ int main(void) player_f->combo_next = 0; player_f->combo_delay = fix(0); player_f->enemy_data = NULL; - fighter_set_stats(player_f, &stats_player, 1, fix(1.5)); + /* Initialize stats. This will level up to level 1 */ + player_add_xp(&player_data, 0); + player_f->HP = player_f->HP_max; player_p->x = fix(game.level->player_spawn_x) + fix(0.5); player_p->y = fix(game.level->player_spawn_y) + fix(0.5); player_p->facing = DOWN; player_p->hitbox = (rect){ -fix(5)/16, fix(5)/16, -fix(2)/16, fix(4)/16 }; - player_m->limits = &ml_player; + player_m->limits = &player_data.mechanical_limits; player_v->sprite_plane = VERTICAL; player_v->shadow_size = 4; @@ -164,6 +180,8 @@ int main(void) /* This assumes the frame is not late; can do better with libprof */ fixed_t dt = fix(1) / FRAME_RATE; + if(debug.paused) + dt = 0; perf_render = prof_make(); prof_enter(perf_render); @@ -172,7 +190,7 @@ int main(void) /* kmalloc_arena_t *_uram = kmalloc_get_arena("_uram"); kmalloc_gint_stats_t *_uram_stats = kmalloc_get_gint_stats(_uram); - dprint(1, 1, C_WHITE, "Memory: %d", _uram_stats->used_memory); */ + dprint(1, 15, C_WHITE, "Memory: %d", _uram_stats->used_memory); */ /* Developer/tweaking menu */ if(debug.show_vars) { @@ -186,23 +204,23 @@ int main(void) uint16_t gray = C_RGB(16, 16, 16); dprint(3, 40, C_WHITE, "Player speed: %g tiles/s", - f2double(ml_player.max_speed)); + f2double(player_data.mechanical_limits.max_speed)); dprint(15, 55, gray, "[frac] -/+ [X,0,T]"); dprint(3, 70, C_WHITE, "Friction: %g", - f2double(ml_player.friction)); + f2double(player_data.mechanical_limits.friction)); dprint(15, 85, gray, "[F<>D] -/+ [log]"); dprint(3, 115, C_WHITE, "Dash speed: %g tiles/s", - f2double(ml_player.dash_speed)); + f2double(player_data.mechanical_limits.dash_speed)); dprint(15, 130, gray, "[(] -/+ [ln]"); dprint(3, 145, C_WHITE, "Dash duration: %g s", - f2double(ml_player.dash_duration)); + f2double(player_data.mechanical_limits.dash_duration)); dprint(15, 160, gray, "[)] -/+ [sin]"); dprint(3, 175, C_WHITE, "Dash cooldown: %g s", - f2double(ml_player.dash_cooldown)); + f2double(player_data.mechanical_limits.dash_cooldown)); dprint(15, 190, gray, "[,] -/+ [cos]"); } @@ -224,8 +242,13 @@ int main(void) dline(j.x, j.y, k.x, k.y, C_RGB(0, 31, 31)); } + debug.some_x = game.level->spawner_count > 0 ? + game.level->spawner_x[0] : game.map->width / 2; + debug.some_y = game.level->spawner_count > 0 ? + game.level->spawner_y[0] : game.map->height / 2; + vec2 p = physical_pos(player); - vec2 q = vec_i2f_center((ivec2){ 6, 9 }); + vec2 q = vec_i2f_center((ivec2){ debug.some_x, debug.some_y }); bool clear = raycast_clear_hitbox(game.map,p,q,player_p->hitbox); ivec2 j = camera_map2screen(c, p); @@ -265,10 +288,12 @@ int main(void) fkey_button(3, "BFS", debug.show_bfs_field ? fg : C_WHITE); fkey_button(4, "PATHS", debug.show_path ? fg : C_WHITE); fkey_button(5, "PERF", debug.show_perf ? fg : C_WHITE); + fkey_button(6, "PAUSE", debug.paused ? fg : C_WHITE); } static bool record = false; - if(keydown(KEY_F6) && !keydown(KEY_VARS) && usb_is_open()) { + if(keydown(KEY_F6) && keydown(KEY_ALPHA) && !keydown(KEY_VARS) + && usb_is_open()) { record = !record; } if(record) { @@ -310,54 +335,58 @@ int main(void) debug.show_path ^= 1; if(ev.key == KEY_F5 && keydown(KEY_VARS)) debug.show_perf ^= 1; + if(ev.key == KEY_F6 && keydown(KEY_VARS)) + debug.paused ^= 1; if(ev.key == KEY_XOT) - ml_player.max_speed += fix(1)/8; + player_data.mechanical_limits.max_speed += fix(1)/8; if(ev.key == KEY_FRAC) { - ml_player.max_speed -= fix(1)/8; - if(ml_player.max_speed < 0) - ml_player.max_speed = 0; + player_data.mechanical_limits.max_speed -= fix(1)/8; + if(player_data.mechanical_limits.max_speed < 0) + player_data.mechanical_limits.max_speed = 0; } if(ev.key == KEY_LOG) { - ml_player.friction += fix(1) / 32; - if(ml_player.friction > 1) - ml_player.friction = 1; + player_data.mechanical_limits.friction += fix(1) / 32; + if(player_data.mechanical_limits.friction > 1) + player_data.mechanical_limits.friction = 1; } if(ev.key == KEY_FD) { - ml_player.friction -= fix(1) / 32; - if(ml_player.friction <= 0) - ml_player.friction = 0; + player_data.mechanical_limits.friction -= fix(1) / 32; + if(player_data.mechanical_limits.friction <= 0) + player_data.mechanical_limits.friction = 0; } if(ev.key == KEY_LN) - ml_player.dash_speed += fix(0.5); + player_data.mechanical_limits.dash_speed += fix(0.5); if(ev.key == KEY_LEFTP) { - ml_player.dash_speed -= fix(0.5); - if(ml_player.dash_speed <= 0) - ml_player.dash_speed = 0; + player_data.mechanical_limits.dash_speed -= fix(0.5); + if(player_data.mechanical_limits.dash_speed <= 0) + player_data.mechanical_limits.dash_speed = 0; } if(ev.key == KEY_SIN) - ml_player.dash_duration += fix(1) / 64; + player_data.mechanical_limits.dash_duration += fix(1) / 64; if(ev.key == KEY_RIGHTP) { - ml_player.dash_duration -= fix(1) / 64; - if(ml_player.dash_duration <= 0) - ml_player.dash_duration = 0; + player_data.mechanical_limits.dash_duration -= fix(1) / 64; + if(player_data.mechanical_limits.dash_duration <= 0) + player_data.mechanical_limits.dash_duration = 0; } if(ev.key == KEY_COS) - ml_player.dash_cooldown += fix(1) / 8; + player_data.mechanical_limits.dash_cooldown += fix(1) / 8; if(ev.key == KEY_COMMA) { - ml_player.dash_cooldown -= fix(1) / 8; - if(ml_player.dash_cooldown <= 0) - ml_player.dash_cooldown = 0; + player_data.mechanical_limits.dash_cooldown -= fix(1) / 8; + if(player_data.mechanical_limits.dash_cooldown <= 0) + player_data.mechanical_limits.dash_cooldown = 0; } + #if 0 if(ev.key == KEY_PLUS) camera_zoom(c, c->zoom + 1); if(ev.key == KEY_MINUS) camera_zoom(c, c->zoom - 1); + #endif if(ev.key == KEY_SHIFT) attack = true; } @@ -378,7 +407,7 @@ int main(void) #endif /* Player movement */ - if(player_f->HP > 0) { + if(!debug.paused && player_f->HP > 0) { int dir = -1; if(keydown(KEY_UP)) dir = UP; if(keydown(KEY_DOWN)) dir = DOWN; @@ -561,7 +590,7 @@ int main(void) pfg_path_free(&debug.grid_path); pfc_path_free(&debug.continuous_path); - vec2 target = vec_i2f_center((ivec2){ 6, 9 }); + vec2 target = vec_i2f_center((ivec2){ debug.some_x,debug.some_y }); debug.grid_path = pfg_bfs_outwards(&game.paths_to_player, vec_f2i(target)); debug.continuous_path = pfc_shortcut_full(&debug.grid_path, diff --git a/src/player.c b/src/player.c new file mode 100644 index 0000000..2a39a29 --- /dev/null +++ b/src/player.c @@ -0,0 +1,31 @@ +#include "player.h" +#include + +static int xp_to_next_level(int level) +{ + return 3 * (level + 2) * (level + 2); +} + +bool player_add_xp(player_t *p, int points) +{ + bool leveled_up = false; + p->xp_current += max(points, 0); + + while(p->xp_current >= p->xp_to_next_level) { + p->xp_current -= p->xp_to_next_level; + p->xp_level++; + p->xp_to_next_level = xp_to_next_level(p->xp_level); + leveled_up = true; + } + + /* Recompute statistics */ + if(leveled_up) { + fighter_t *f = getcomp(p->entity, fighter); + int previous_HP_max = f->HP_max; + fighter_set_stats(getcomp(p->entity, fighter), &p->stat_model, + p->xp_level, fix(2.0)); + f->HP += (f->HP_max - previous_HP_max); + } + + return leveled_up; +} diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..1ad7258 --- /dev/null +++ b/src/player.h @@ -0,0 +1,27 @@ +//--- +// player: Dynamic player information +//--- + +#pragma once + +#include "comp/entity.h" +#include "comp/mechanical.h" +#include "comp/fighter.h" +#include + +typedef struct { + /* Associated entity */ + entity_t *entity; + /* Mechanical limits (dynamically recomputed) */ + mechanical_limits_t mechanical_limits; + /* Fighter model (dynamically recomputed) */ + fighter_stat_model_t stat_model; + /* Experience level, total to next level, current points (within level) */ + int xp_level; + int xp_to_next_level; + int xp_current; + +} player_t; + +/* Add XP points to a player. Returns true if levels up */ +bool player_add_xp(player_t *p, int points); diff --git a/src/render.c b/src/render.c index b4da866..8b8685e 100644 --- a/src/render.c +++ b/src/render.c @@ -328,6 +328,14 @@ void render_game(game_t const *g, bool show_hitboxes) extern bopti_image_t img_hud; dimage(0, DHEIGHT - img_hud.height, &img_hud); + extern font_t font_hud; + dfont(&font_hud); + dprint_opt(349, DHEIGHT - 5, RGB24(0x15171a), C_NONE, DTEXT_CENTER, + DTEXT_TOP, "%d", g->player_data->xp_level); + dprint_opt(349, DHEIGHT - 6, RGB24(0xabb1ba), C_NONE, DTEXT_CENTER, + DTEXT_TOP, "%d", g->player_data->xp_level); + dfont(&font_rogue); + fighter_t *player_f = getcomp(g->player, fighter); mechanical_t *player_m = getcomp(g->player, mechanical); @@ -338,12 +346,20 @@ void render_game(game_t const *g, bool show_hitboxes) 0, img_hud_life.height - fill_height, img_hud_life.width, fill_height, DIMAGE_NONE); + /* Render XP bar */ + extern bopti_image_t img_hud_xp; + fill_height = (img_hud_xp.height * g->player_data->xp_current) / + max(g->player_data->xp_to_next_level, 1); + dsubimage(343, DHEIGHT - 10 - fill_height, &img_hud_xp, + 0, img_hud_xp.height - fill_height, img_hud_xp.width, fill_height, + DIMAGE_NONE); + /* Render skill icons */ extern bopti_image_t img_skillicons; static const int skill_size = 23; static const int skill_box_size = 27; - for(int i = 0; i < 6; i++) { + for(int i = 0; i < 5; i++) { /* Activity and cooldown */ fixed_t cooldown_total=0, cooldown_remaining=0; if(i == 0 && player_m->dash < 0) { diff --git a/src/util.h b/src/util.h index 45f03bf..fee4d6c 100644 --- a/src/util.h +++ b/src/util.h @@ -23,3 +23,8 @@ void font_damage_size(int value, int *w, int *h); /* Render text with digit-only image font. */ void font_damage_print(int x, int y, int color, int align_x, int align_y, int value); + +#define RGB24(hex) \ + (((hex & 0xf80000) >> 8) | \ + ((hex & 0x00fc00) >> 5) | \ + ((hex & 0x0000f8) >> 3))