crystal-tower/src/player.c

290 lines
6.8 KiB
C

#include "player.h"
#include "camera.h"
#include "conf.h"
#include "input.h"
#include "level.h"
#include "raygint/display.h"
#include "shatter.h"
#include "tile.h"
#include "util.h"
#include "vec.h"
static struct Player self;
static int jump(int on_ground);
static void walljump(void);
static void death(void);
static int oob(int x, int y);
static int collide_tile(int x, int y, enum Tile);
static int collide_solid(int x, int y);
static int collide_spike(int x, int y);
static int collide_spike_right(int x, int y);
static int collide_spike_left(int x, int y);
static int collide_spike_down(int x, int y);
static int collide_spike_up(int x, int y);
void
player_init(struct Vec pos)
{
self.pos = pos;
self.pos.x += (TILE_SIZE - PLAYER_WIDTH) / 2;
self.pos.y += TILE_SIZE - PLAYER_HEIGHT;
self.spd = VECFZ;
self.rem = VECFZ;
self.jump_grace = 0;
self.jump_buffer = 0;
self.lock_direction = 0;
self.last_direction = 1;
level_set_px(pos.x, pos.y, TILE_VOID);
}
void
player_update(void)
{
const int on_ground = collide_solid(self.pos.x, self.pos.y + 1);
struct Vec dir = VEC(input_down(K_RIGHT) - input_down(K_LEFT),
input_down(K_DOWN) - input_down(K_UP));
if (self.lock_direction) {
self.lock_direction--;
if (self.spd.x == 0.0f)
self.lock_direction = 0;
else
dir.x = self.last_direction;
} else if (dir.x)
self.last_direction = dir.x;
/* acceleration & friction */
if (on_ground && !dir.x) {
self.spd.x *= 1.0f * FRICTION_BREAK;
} else {
self.spd.x *=
1.0f - (on_ground ? FRICTION_GROUND : FRICTION_AIR);
self.spd.x +=
(on_ground ? ACCELERATION_GROUND : ACCELERATION_AIR) *
dir.x;
}
/* resistance & gravity */
self.spd.y *= 1.0f - AIR_RESISTANCE;
if (!on_ground)
self.spd.y += GRAVITY;
/* jump buffer */
if (input_pressed(K_JUMP))
self.jump_buffer = JUMP_BUFFER;
else if (self.jump_buffer)
self.jump_buffer--;
/* jump or walljump */
if (!jump(on_ground))
walljump();
/* unlock direction */
if (self.lock_direction && absf(self.spd.x) < MAX_WALK_SPEED / 2)
self.lock_direction = 0;
/* death */
if (oob(self.pos.x, self.pos.y) ||
collide_spike(self.pos.x, self.pos.y)) {
death();
return;
}
/* next level */
if (collide_tile(self.pos.x, self.pos.y, TILE_NEXT)) {
level_next();
return;
}
player_move(player_update_rem());
}
static int
jump(int on_ground)
{
/* grace */
if (on_ground)
self.jump_grace = JUMP_GRACE;
else if (self.jump_grace)
self.jump_grace--;
/* jump */
if (self.jump_grace && self.jump_buffer && input_down(K_JUMP)) {
struct Vec shatter_pos = VEC(self.pos.x, self.pos.y);
self.jump_grace = 0;
self.jump_buffer = 0;
self.spd.y = JUMP_SPD;
shatter_pos.y += PLAYER_HEIGHT;
shatter(shatter_pos.x, shatter_pos.y);
shatter_pos.x += PLAYER_WIDTH - 1;
shatter(shatter_pos.x, shatter_pos.y);
return 1;
}
return 0;
}
static void
walljump(void)
{
const int wall_adjacent = collide_solid(self.pos.x - 2, self.pos.y) -
collide_solid(self.pos.x + 2, self.pos.y);
if (self.jump_buffer && wall_adjacent && input_down(K_JUMP)) {
struct Vec shatter_pos = VEC(self.pos.x, self.pos.y);
self.jump_buffer = 0;
self.spd.y = JUMP_SPD;
self.spd.x = wall_adjacent * MAX_WALK_SPEED;
self.lock_direction = WJUMP_LOCK;
self.last_direction = wall_adjacent;
shatter_pos.x +=
(wall_adjacent == 1) ? (-2) : (PLAYER_WIDTH + 1);
shatter(shatter_pos.x, shatter_pos.y);
shatter_pos.y += PLAYER_HEIGHT - 1;
shatter(shatter_pos.x, shatter_pos.y);
}
}
static void
death(void)
{
extern int deaths;
deaths++;
level_reload();
}
void
player_draw(void)
{
const struct Vec off = camera_offset();
const int x = self.pos.x + off.x;
const int y = self.pos.y + off.y;
#ifdef GINT
extern bopti_image_t bimg_player;
dimage(x, y, &bimg_player);
#endif
#ifdef RAYLIB
const int x2 = x + PLAYER_WIDTH - 1;
const int y2 = y + PLAYER_HEIGHT - 1;
drect(x, y, x2, y2, C_RED);
#endif
}
struct Vec
player_update_rem(void)
{
struct VecF spd_n_rem = {self.spd.x + self.rem.x,
self.spd.y + self.rem.y};
struct Vec spd_trunc = {spd_n_rem.x, spd_n_rem.y};
self.rem.x = spd_n_rem.x - (float)spd_trunc.x;
self.rem.y = spd_n_rem.y - (float)spd_trunc.y;
return spd_trunc;
}
void
player_move(struct Vec spd)
{
int vertical_shatter = 0;
float sign_x = signf(self.spd.x);
const float sign_y = signf(self.spd.y);
if (!sign_x && !sign_y)
sign_x = 1.0f;
self.pos.x += spd.x;
if (collide_solid(self.pos.x, self.pos.y)) {
self.spd.x = 0.0f;
self.rem.x = 0.0f;
}
while (collide_solid(self.pos.x, self.pos.y)) {
self.pos.x -= sign_x;
}
self.pos.y += spd.y;
if (collide_solid(self.pos.x, self.pos.y)) {
vertical_shatter = (absf(self.spd.y) > MAX_WALK_SPEED)
? (sign(self.spd.y))
: (0);
self.spd.y = 0.0f;
self.rem.y = 0.0f;
}
while (collide_solid(self.pos.x, self.pos.y)) {
self.pos.y -= sign_y;
}
if (vertical_shatter == -1) {
struct Vec shatter_pos = VEC(self.pos.x, self.pos.y);
shatter_pos.y +=
(vertical_shatter == 1) ? (PLAYER_HEIGHT) : (-1);
shatter(shatter_pos.x, shatter_pos.y);
shatter_pos.x += PLAYER_WIDTH - 1;
shatter(shatter_pos.x, shatter_pos.y);
}
}
static int
collide_tile(int x, int y, enum Tile t)
{
const int x2 = x + PLAYER_WIDTH - 1;
const int y2 = y + PLAYER_HEIGHT - 1;
return level_get_px(x, y) == t || level_get_px(x, y2) == t ||
level_get_px(x2, y) == t || level_get_px(x2, y2) == t;
}
static int
collide_solid(int x, int y)
{
return collide_tile(x, y, TILE_SOLID);
}
static int
collide_spike(int x, int y)
{
return (self.spd.x <= 0.0f && collide_spike_right(x, y)) ||
(self.spd.x >= 0.0f && collide_spike_left(x, y)) ||
(self.spd.y <= 0.0f && collide_spike_down(x, y)) ||
(self.spd.y >= 0.0f && collide_spike_up(x, y));
}
static int
collide_spike_right(int x, int y)
{
const int y2 = y + PLAYER_HEIGHT - 1;
return x % TILE_SIZE == 0 && (level_get_px(x, y) == TILE_SPIKE_R ||
level_get_px(x, y2) == TILE_SPIKE_R);
}
static int
collide_spike_left(int x, int y)
{
const int y2 = y + PLAYER_HEIGHT - 1;
return x % TILE_SIZE == TILE_SIZE - PLAYER_WIDTH &&
(level_get_px(x, y) == TILE_SPIKE_L ||
level_get_px(x, y2) == TILE_SPIKE_L);
}
static int
collide_spike_down(int x, int y)
{
const int x2 = x + PLAYER_WIDTH - 1;
return y % TILE_SIZE == 0 && (level_get_px(x, y) == TILE_SPIKE_D ||
level_get_px(x2, y) == TILE_SPIKE_D);
}
static int
collide_spike_up(int x, int y)
{
const int x2 = x + PLAYER_WIDTH - 1;
return y % TILE_SIZE == TILE_SIZE - PLAYER_HEIGHT &&
(level_get_px(x, y) == TILE_SPIKE_U ||
level_get_px(x2, y) == TILE_SPIKE_U);
}
static int
oob(int x, int y)
{
const int x2 = x + PLAYER_WIDTH - 1;
const int y2 = y + PLAYER_HEIGHT - 1;
return level_oob(x, y) && level_oob(x2, y2);
}
struct Vec *
player_pos(void)
{
return &self.pos;
}