mirror of https://git.sr.ht/~kikoodx/crystal-tower
290 lines
6.8 KiB
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;
|
|
}
|