297 lines
6.8 KiB
C
297 lines
6.8 KiB
C
#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();
|
|
}
|