momento/src/player/update.c

182 lines
4.5 KiB
C

/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright (C) 2021 KikooDX */
#include "conf.h"
#include "input.h"
#include "level.h"
#include "particles.h"
#include "player.h"
#include "tiles.h"
#include <gint/keyboard.h>
static void player_move(struct Player *player, int x, int y);
extern struct Level level;
/* return -1 on pause, return 1 if exit reached, 2 on death/reset and 0
* otherwise */
int
player_update(struct Player *player, struct Input input)
{
Tile collisions[COLLIDE_POINTS];
const int on_ground = player_collide_solid(player->x, player->y + 1);
int jumped = 0;
const int previous_flip_h = player->anim.flip_h;
/* process input */
const int k_left = input.keystates[K_LEFT] != KS_UP;
const int k_right = input.keystates[K_RIGHT] != KS_UP;
const int dir_x = k_right - k_left;
const int k_jump = input.keystates[K_A] != KS_UP;
const int kp_jump = input.keystates[K_A] == KS_PRESS;
/* pause the game? */
if (input.keystates[K_START] == KS_PRESS)
return -1;
/* horizontal friction/acceleration */
player->spd_x *= (on_ground) ? (1 - FRC_GND) : (1 - FRC_AIR);
player->spd_x += dir_x * ((on_ground) ? (ACC_GND) : (ACC_AIR));
/* air state */
/* enter AirRising state when player jump */
switch (player->air_state) {
case AirRising:
if (!k_jump)
player->air_state = AirBreaking;
if (player->spd_y > 0)
player->air_state = AirNeutral;
break;
case AirBreaking:
if (player->spd_y > 0)
player->air_state = AirNeutral;
break;
case AirNeutral:
default:
break;
}
/* vertical air resistance and gravity */
player->spd_y *= 1 - AIR_RES;
if (!on_ground) {
/* smooth jump apex */
const float abs_spd_y =
(player->spd_y > 0.0) ? (player->spd_y) : (-player->spd_y);
float factor = 1.0;
if (abs_spd_y < 1.0)
factor = 0.5 + 0.5 * (player->spd_y * player->spd_y);
if (player->air_state == AirBreaking)
factor *= AIR_BREAKING_FACTOR;
player->spd_y += GRAVITY * factor;
}
/* input buffer */
if (kp_jump)
player->jump_buffer = JUMP_BUFFER;
else if (player->jump_buffer)
player->jump_buffer -= 1;
/* grace frames and jumps refill */
if (on_ground) {
player->jump_grace = JUMP_GRACE;
player->jumps_left = AIR_JUMPS;
} else if (player->jump_grace)
player->jump_grace -= 1;
/* jump (youhou) */
if (k_jump && player->jump_buffer &&
(player->jump_grace || player->jumps_left)) {
jumped = 1;
/* ground jump */
if (player->jump_grace) {
player->jump_grace = 0;
player->spd_y = JUMP_SPD;
}
/* air jump (burst) */
else {
player->jumps_left -= 1;
player->spd_y = AIR_JMP_SPD;
/* burst boost */
player->spd_x += (float)dir_x * BURST_BOOST;
}
player->air_state = AirRising;
player->jump_buffer = 0;
}
/* speed reminder */
const float spd_n_rem_x = player->spd_x + player->rem_x;
const int spd_x = (int)spd_n_rem_x;
player->rem_x = spd_n_rem_x - spd_x;
const float spd_n_rem_y = player->spd_y + player->rem_y;
const int spd_y = (int)spd_n_rem_y;
player->rem_y = spd_n_rem_y - spd_y;
/* move the player */
player_move(player, 0, spd_y);
player_move(player, spd_x, 0);
/* get gold ($$$) */
level.gold -= player_collide_sub(player->x, player->y, TILE_GOLD,
TILE_VOID, MARGIN_GOLD);
/* unlock exit */
if (level.exit_locked &&
player_collide_sub(player->x, player->y, TILE_SWITCH, TILE_VOID,
MARGIN_SWITCH))
level.exit_locked = 0;
/* check for death and exit */
if (!level.exit_locked &&
player_collide_tile(collisions, player->x, player->y, TILE_EXIT, 0,
1)) {
return 1;
}
if (player_collide_tile(collisions, player->x, player->y, TILE_LETAL,
MARGIN_LETAL, 1)) {
return 2;
}
/* update animation */
if (jumped)
player->anim = player->jump_anim;
particle_update(&player->anim);
if (player->anim.life == 0) {
if (dir_x != 0)
player->anim = player->walk_anim;
else
player->anim = player->idle_anim;
}
/* horizontal flip */
player->anim.flip_h = previous_flip_h;
if (dir_x)
player->anim.flip_h = dir_x == -1;
return 0;
}
static void
player_move(struct Player *player, int x, int y)
{
const int sign_x = 1 * (x > 0) - 1 * (x < 0);
const int sign_y = 1 * (y > 0) - 1 * (y < 0);
player->x += x;
player->y += y;
if (player_collide_solid(player->x, player->y)) {
if (sign_x) {
player->spd_x = 0.0;
player->rem_x = 0.0;
}
if (sign_y) {
player->spd_y = 0.0;
player->rem_y = 0.0;
}
do {
player->x -= sign_x;
player->y -= sign_y;
} while (player_collide_solid(player->x, player->y));
}
}