jtmm2/src/player.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();
}