#include #include #include "player.h" #include "player_vars.h" #include "player_modifiers.h" #include "vec.h" #include "conf.h" #include "camera.h" #include "input.h" #include "collide.h" #include "tiles.h" /* TODO: Determine FRICTION and ACCELERATION from UPS. */ #define MAX_SPD (128 * PXS) #define FRICTION 0.01 #define ACCELERATION (int)(MAX_SPD * FRICTION) #define GRAVITY_SCALAR 16 #define GRAVITY (PXS / GRAVITY_SCALAR) #define FAST_FALL_FACTOR 3 #define JUMP_SPD (-128 * PXS) #define GRACE_UNITS (int)(UPS / 10) #define EARLY_UNITS (int)(UPS / 5) #define H_CLIP_MARGIN (TILE_SIZE / 4) #define V_CLIP_MARGIN (TILE_SIZE / 3) #define SGN(x) (((x) > 0) ? (1) : (((x) < 0) ? (-1) : (0))) #define PLAYER_COLLIDE_SOLID(pos) (player_collide_or(player, pos, level) & F_SOLID) void player_init(Player *player, const Level *level) { vec_cpy(&player->pos, level->start_pos); player->spd.x = 0; player->spd.y = 0; player->hbox.x = TILE_SIZE - 1; player->hbox.y = TILE_SIZE - 1; player->vbox.x = 7; player->vbox.y = 7; player->origin.x = 0; player->origin.y = 0; player->grace = 0; player->jump_held = false; player->dead = false; } void player_set_vars(Player *player, const Level *level) { player->vars.friction = FRICTION; player->vars.acceleration = ACCELERATION; player->vars.jump_spd = JUMP_SPD; player->vars.gravity = GRAVITY; player->vars.gravity_scalar = GRAVITY_SCALAR; /* collide on all sides */ Vec pos = { player->pos.x - 1, player->pos.y }; Tile_flags flags_left; Tile_flags flags_right; Tile_flags flags_floor; Tile_flags flags_ceil; flags_left = player_collide_or(player, pos, level); pos.x += 2; flags_right = player_collide_or(player, pos, level); pos.x -= 1; pos.y += 1; flags_floor = player_collide_or(player, pos, level); pos.y -= 2; flags_ceil = player_collide_or(player, pos, level); uint8_t side_flags[sizeof(Tile_flags) * 8]; /* make sum for each flag */ for (uint i = 0; i < sizeof(Tile_flags) * 8; i++) { Tile_flags cur_flag = 1 << i; side_flags[i] = 0; if (flags_left & cur_flag) side_flags[i] |= D_LEFT; if (flags_right & cur_flag) side_flags[i] |= D_RIGHT; if (flags_floor & cur_flag) side_flags[i] |= D_FLOOR; if (flags_ceil & cur_flag) side_flags[i] |= D_CEIL; } /* apply modifiers */ player_mod_water(player, side_flags[I_WATER]); player_mod_ice(player, side_flags[I_ICE]); player_mod_glue(player, side_flags[I_GLUE]); /* fix wrong values */ if (player->vars.friction > 1) player->vars.friction = 1; } void player_move(Player *player, const Level *level) { /* TODO: Take into account player's hitbox */ const int sgn_spd_x = SGN(player->spd.x); const int sgn_spd_y = SGN(player->spd.y); Vec destination; vec_cpy(&destination, player->pos); /* snap the player to the grid if they hit a wall */ destination.x += player->spd.x; if (PLAYER_COLLIDE_SOLID(destination)) { /* Used for clipping and positionning. */ int offset = destination.y % TILE_SIZE; /* If player is rising or offset is too high then don't clip. */ if (player->spd.y < 0 || offset > H_CLIP_MARGIN) { offset = 0; } destination.y -= offset; if (PLAYER_COLLIDE_SOLID(destination)) { destination.y += offset; destination.x = player->pos.x - player->pos.x % TILE_SIZE; /* Move the player tile per tile until it enters an * occupied tile. */ while (!PLAYER_COLLIDE_SOLID(destination)) { destination.x += TILE_SIZE * sgn_spd_x; } /* then, move it back one tile */ destination.x -= TILE_SIZE * sgn_spd_x; player->spd.x = 0; } } /* do the same for y */ destination.y += player->spd.y; if (PLAYER_COLLIDE_SOLID(destination)) { destination.y = player->pos.y - player->pos.y % TILE_SIZE; while (!PLAYER_COLLIDE_SOLID(destination)) { destination.y += TILE_SIZE * sgn_spd_y; } destination.y -= TILE_SIZE * sgn_spd_y; if (sgn_spd_y > 0) { /* the player was falling */ player->grace = GRACE_UNITS; } player->spd.y = 0; } /* move the player to their new position */ vec_cpy(&player->pos, destination); } void player_step(Player *player, Input *input, const Level *level, uint step) { player_set_vars(player, level); /* Get directionnal input and assign it to move.x/move.y: * i.e., if the player hold left and down move will have * move.x = -1 and move.y = 1. */ Vec move = { (INPUT_DOWN(K_RIGHT) - INPUT_DOWN(K_LEFT)), (INPUT_DOWN(K_DOWN) - INPUT_DOWN(K_UP)) }; /* other keys */ bool k_jump = INPUT_DOWN(K_JUMP); int xacc = move.x * player->vars.acceleration; /* calculate horizontal acceleration */ player->spd.x *= 1 - player->vars.friction; /* apply horizontal friction */ player->spd.x += xacc; /* apply horizontal acceleration */ /* apply gravity */ if (player->spd.y < 0 && !player->jump_held) { /* The player is rising and let go the jump key, * accelerate gravity until they reach 0. */ player->spd.y += player->vars.gravity * FAST_FALL_FACTOR * player->vars.gravity_scalar; } else { player->spd.y += player->vars.gravity * player->vars.gravity_scalar; } /* Grace frames allow the player to jump a short * time after leaving a platform. */ if (player->grace) { player->grace -= 1; if (k_jump && (INPUT_LAST_PRESS(K_JUMP) <= EARLY_UNITS)) { /* If the player try to jump and can, prevent * them to do it again and assign them the * corresponding y speed. */ player->grace = 0; player->spd.y = player->vars.jump_spd; player->jump_held = true; input->last_press[K_JUMP] = 0; } } /* See if the player is still holding their jump button, this is * usefull for jump height and gravity manipulation. */ if (player->jump_held && !k_jump) { player->jump_held = false; } player_move(player, level); /* move the player according to their speed */ /* player death */ if (player_collide_or(player, player->pos, level) & F_SPIKY) { player->dead = true; } } void player_draw(Player *player, Camera *camera) { Vec tl; /* top left */ Vec br; /* bottom right */ /* The rest of this function calculates the player on screen * position and draw it. */ vec_cpy(&tl, player->pos); vec_add(&tl, player->origin); vec_cpy(&br, tl); vec_div(&tl, VEC_PRECISION); vec_div(&br, VEC_PRECISION); vec_add(&br, player->vbox); vec_sub(&tl, camera->offset); vec_sub(&br, camera->offset); vec_mul(&tl, SCALE); vec_mul(&br, SCALE); vec_add(&br, (Vec){ SCALE - 1, SCALE - 1 }); /* draw code here */ vec_drect(tl, br, C_BLACK); } void player_draw_debug(Player *player, uint step, const Level *level) { /* This debug function displays more or less usefull * informations for debugging player movement. */ dprint(0, 0, C_BLACK, "x: %d", player->pos.x); dprint(0, 10, C_BLACK, "y: %d", player->pos.y); dprint(0, 20, C_BLACK, "vp: %d", VEC_PRECISION); dprint(0, 30, C_BLACK, "st: %u", step); dprint(0, 40, C_BLACK, "cx: %d", player->pos.x / TILE_SIZE); dprint(0, 50, C_BLACK, "cy: %d", player->pos.y / TILE_SIZE); dprint(0, 60, C_BLACK, "cl: %d", collide_point(player->pos, level)); }