add XP and player leveling

XP only lasts for one level/arena.
This commit is contained in:
Lephenixnoir 2022-01-17 18:29:05 +01:00
parent 1b2f0fbc15
commit fdde4809d3
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
18 changed files with 197 additions and 58 deletions

View File

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

View File

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

BIN
assets-cg/font_hud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets-cg/hud_xp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

View File

@ -3,4 +3,4 @@ player_*.aseprite:
name_regex: (.*)\.aseprite frames_\1
center: 11, 19
next: Idle=Idle, Walking=Walking
profile: p8
profile: p4

View File

@ -1,3 +1,4 @@
*.tsx:
custom-type: tiled-tileset
name_regex: (.*)\.tsx tileset_\1
profile: p4

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@
#include "level.h"
#include "map.h"
#include "pathfinding.h"
#include "player.h"
#include "render.h"
#include <gint/display.h>
@ -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,

31
src/player.c Normal file
View File

@ -0,0 +1,31 @@
#include "player.h"
#include <gint/defs/util.h>
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;
}

27
src/player.h Normal file
View File

@ -0,0 +1,27 @@
//---
// player: Dynamic player information
//---
#pragma once
#include "comp/entity.h"
#include "comp/mechanical.h"
#include "comp/fighter.h"
#include <stdbool.h>
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);

View File

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

View File

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