#include "player.h" #include "conf.h" #include "draw.h" #include "input.h" #include "level.h" #include "tile.h" #include "util.h" static void reset_speed(struct Player *, int x, int y); static struct Vec update_rem(struct Player *); static int collide_margin(int x, int y, int tile, int margin); static int collide(int x, int y, int tile); static int collide_solid(int x, int y); void player_spawn(struct Player *p) { struct Vec pos = level_find(TILE_SPAWN); p->pos.x = pos.x * TILE_SIZE + (TILE_SIZE - PLAYER_WIDTH) / 2; p->pos.y = pos.y * TILE_SIZE + TILE_SIZE - PLAYER_HEIGHT; reset_speed(p, 1, 1); p->air_state = AS_NEUTRAL; p->jump_buffer = 0; p->jump_grace = 0; p->burn = 0; } void player_update(struct Player *p) { const int on_ground = collide_solid(p->pos.x, p->pos.y + 1); const int k_left = input_down(K_LEFT); const int k_right = input_down(K_RIGHT); // const int k_up = input_down(K_UP); // const int k_down = input_down(K_DOWN); const int k_jump = input_down(K_JUMP); const int kp_jump = input_pressed(K_JUMP); const int dir_x = k_right - k_left; /* horizontal friction & acceleration */ p->spd.x *= on_ground ? (1 - GROUND_FRICTION) : (1 - AIR_FRICTION); p->spd.x += dir_x * (on_ground ? GROUND_ACCELERATION : AIR_ACCELERATION); /* air resistance & gravity */ p->spd.y *= (1 - AIR_RESISTANCE); p->spd.y += (p->air_state == AS_BREAKING) ? (GRAVITY * JUMP_BREAK) : (GRAVITY); /* air state machine */ /* state is set to AS_RISING when jumping */ switch (p->air_state) { case AS_RISING: if (!k_jump) p->air_state = AS_BREAKING; /* fallthrough */ case AS_BREAKING: if (p->spd.y > 0) p->air_state = AS_NEUTRAL; break; case AS_NEUTRAL: default: break; } /* jump buffer */ if (kp_jump) p->jump_buffer = JUMP_BUFFER; else if (p->jump_buffer) p->jump_buffer--; /* jump grace */ if (on_ground) p->jump_grace = JUMP_GRACE; else if (p->jump_grace) p->jump_grace--; /* jump */ if (p->jump_grace && p->jump_buffer && k_jump) { p->spd.y = JUMP_SPEED; p->air_state = AS_RISING; p->jump_buffer = 0; p->jump_grace = 0; } /* burn and death */ if (collide(p->pos.x, p->pos.y, TILE_BURN)) { if (++p->burn >= BURN_DEATH) level_reload(); } else if (p->burn) { p->burn--; } /* next level */ if (collide(p->pos.x, p->pos.y, TILE_EXIT)) level_next(); player_move(p, update_rem(p)); } void player_draw(struct Player *p) { draw_rectangle(C_WHITE, p->pos.x, p->pos.y, PLAYER_WIDTH, PLAYER_HEIGHT); } void player_move(struct Player *p, struct Vec spd) { int sign_x = sign(spd.x); const int sign_y = sign(spd.y); if (!sign_x && !sign_y) sign_x = 1.0f; p->pos.x += spd.x; if (collide_solid(p->pos.x, p->pos.y)) reset_speed(p, 1, 0); while (collide_solid(p->pos.x, p->pos.y)) p->pos.x -= sign_x; p->pos.y += spd.y; if (collide_solid(p->pos.x, p->pos.y)) reset_speed(p, 0, 1); while (collide_solid(p->pos.x, p->pos.y)) p->pos.y -= sign_y; } static void reset_speed(struct Player *p, int x, int y) { if (x) { p->spd.x = 0.0f; p->rem.x = 0.0f; } if (y) { p->spd.y = 0.0f; p->rem.y = 0.0f; } } static struct Vec update_rem(struct Player *p) { struct VecF spd_n_rem = {p->spd.x + p->rem.x, p->spd.y + p->rem.y}; struct Vec spd_trunc = {spd_n_rem.x, spd_n_rem.y}; p->rem.x = spd_n_rem.x - (float)spd_trunc.x; p->rem.y = spd_n_rem.y - (float)spd_trunc.y; return spd_trunc; } static int collide_margin(int x, int y, int tile, int margin) { const int x1 = x + margin; const int x2 = x + PLAYER_WIDTH - 1 - margin; const int y1 = y + margin; const int y2 = y + PLAYER_HEIGHT - 1 - margin; return level_get_px(x1, y1) == tile || level_get_px(x2, y1) == tile || level_get_px(x1, y2) == tile || level_get_px(x2, y2) == tile; } static int collide(int x, int y, int tile) { return collide_margin(x, y, tile, 0); } static int collide_solid(int x, int y) { return collide(x, y, TILE_SOLID); }