#include "player.h" #include "conf.h" #include "draw.h" #include "input.h" #include "level.h" #include "missile.h" #include "polarity.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_opt(int x, int y, int tile, int margin, int collect); static int collide_point(int x, int y, int tile, int collect); 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); static int collide_burn(int x, int y); static int collide_collect(int x, int y, int tile, int margin); static void death(void); 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; p->gravity.x = 0; p->gravity.y = 1; reset_speed(p, 1, 1); p->air_state = AS_NEUTRAL; p->jump_buffer = 0; p->jump_grace = 0; p->burn = 0; p->was_in_water = 0; } void player_update(struct Player *p) { const int on_ground = collide_solid(p->pos.x + p->gravity.x, p->pos.y + p->gravity.y); const int in_water = collide(p->pos.x, p->pos.y, TILE_WATER); 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 kp_polarity = input_pressed(K_POLARITY); const int dir_x = (p->gravity.y) ? (k_right - k_left) : (k_down - k_up); const struct Vec rem_gravity = p->gravity; float spd_x, spd_y; /* speed transformation */ spd_x = p->spd.x; spd_y = p->spd.y * p->gravity.y; if (p->gravity.x) { spd_x = p->spd.y; spd_y = p->spd.x * p->gravity.x; } /* polarity swap */ if (kp_polarity) polarity_invert(); /* horizontal friction & acceleration */ spd_x *= on_ground ? (1 - GROUND_FRICTION) : (1 - AIR_FRICTION); spd_x += dir_x * (on_ground ? GROUND_ACCELERATION : AIR_ACCELERATION); /* air resistance & gravity */ spd_y *= 1 - (in_water ? WATER_RESISTANCE : AIR_RESISTANCE); 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 (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) { spd_y = JUMP_SPEED; p->air_state = AS_RISING; p->jump_buffer = 0; p->jump_grace = 0; } /* swim */ if (k_jump && (p->was_in_water || in_water)) { spd_y = minf(spd_y, (in_water) ? (SWIM_SPEED) : (SWIM_OUT_SPEED)); p->air_state = AS_RISING; } /* bounce */ if (collide(p->pos.x, p->pos.y, TILE_BOUNCER)) { if (!p->bouncing) { spd_y = -absf(spd_y) + BOUNCE_SPEED; p->bouncing = 1; } } else { p->bouncing = 0; } /* burn and death */ if (collide_burn(p->pos.x, p->pos.y)) { if (++p->burn >= BURN_DEATH) { death(); return; } } else if (p->burn) { p->burn--; } if (missile_collide_player(p)) { death(); return; } /* next level */ if (collide(p->pos.x, p->pos.y, TILE_EXIT)) { level_next(); return; } /* speed transformation */ if (rem_gravity.y) { p->spd.x = spd_x; p->spd.y = spd_y * p->gravity.y; } if (rem_gravity.x) { p->spd.y = spd_x; p->spd.x = spd_y * p->gravity.x; } player_move(p, update_rem(p)); /* gravity modifiers */ if (collide_margin(p->pos.x, p->pos.y, TILE_GRAV_D, GRAVS_MARGIN)) { p->gravity.x = 0; p->gravity.y = 1; } if (collide_margin(p->pos.x, p->pos.y, TILE_GRAV_U, GRAVS_MARGIN)) { p->gravity.x = 0; p->gravity.y = -1; } if (collide_margin(p->pos.x, p->pos.y, TILE_GRAV_R, GRAVS_MARGIN)) { p->gravity.x = 1; p->gravity.y = 0; } if (collide_margin(p->pos.x, p->pos.y, TILE_GRAV_L, GRAVS_MARGIN)) { p->gravity.x = -1; p->gravity.y = 0; } p->was_in_water = in_water; } void player_draw(const struct Player *p) { draw_rectangle(C_WHITE, p->pos.x + DRAW_OFF_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; if (collide_solid(p->pos.x, p->pos.y)) { reset_speed(p, 1, 1); return; } 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; } struct Vec player_middle(const struct Player *p) { return (struct Vec){p->pos.x + PLAYER_WIDTH / 2, p->pos.y + PLAYER_HEIGHT / 2}; } 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_opt(int x, int y, int tile, int margin, int collect) { 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 collide_point(x1, y1, tile, collect) + collide_point(x2, y1, tile, collect) + collide_point(x1, y2, tile, collect) + collide_point(x2, y2, tile, collect); } static int collide_point(int x, int y, int tile, int collect) { const int collide = level_get_px(x, y) == tile; if (collide && collect) level_set_px(x, y, TILE_AIR); return collide; } static int collide_margin(int x, int y, int tile, int margin) { return collide_opt(x, y, tile, margin, 0); } 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) || (!polarity() && collide(x, y, TILE_RED)) || (polarity() && collide(x, y, TILE_BLUE)); } static int collide_burn(int x, int y) { return collide(x, y, TILE_BURN) || (!polarity() && collide(x, y, TILE_BURN_RED)) || (polarity() && collide(x, y, TILE_BURN_BLUE)); } static int collide_collect(int x, int y, int tile, int margin) { return collide_opt(x, y, tile, margin, 1); } static void death(void) { level_reload(); }