jtmm2/src/player.c

153 lines
3.5 KiB
C

#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_init(struct Player *p)
{
p->pos.x = TILE_SIZE;
p->pos.y = TILE_SIZE;
reset_speed(p, 1, 1);
p->air_state = AS_NEUTRAL;
p->jump_buffer = 0;
p->jump_grace = 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;
}
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)
{
float sign_x = sign(spd.x);
const float 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);
}