/* SPDX-License-Identifier: MIT * Copyright (c) 2021 KikooDX * This file is part of * [Painfull Success CG](https://git.sr.ht/~kikoodx/painfull-success-cg), * which is MIT licensed. The MIT license requires this copyright notice to be * included in all copies and substantial portions of the software. */ #include #include #include "lazyint.h" #include "player.h" #include "conf.h" #include "vec2.h" #include "level.h" #include "input.h" #include "tiles.h" void player_init(Player *player) { player->pos = (Vec2){0, 0}; player->spd_x = 0.0; player->spd_y = 0.0; player->facing = 1; player->stun = false; player->knocked = false; player->keys_left = 0; player->jump_buffer = 0; player->coyot = 0; } void player_update(Player *player, Level *level, Input input, u8 *level_id) { const Vec2 one_px_down = (Vec2){player->pos.x, player->pos.y + 1}; const bool on_ground = player_collide(*level, one_px_down, SOLID_TILE, 0) || player_collide(*level, one_px_down, SEMI_SOLID_TILE, 0); if (!player->stun) { /* Get input. */ const i8 move_x = sign( input_is_down(input, K_RIGHT) - input_is_down(input, K_LEFT)); const bool fastfall = input_is_down(input, K_DOWN); const bool jump_pressed = input_is_pressed(input, K_JUMP); const bool jump_held = input_is_down(input, K_JUMP); /* Facing direction. */ if (move_x != 0) player->facing = move_x; /* Friction. */ if (on_ground) player->spd_x *= 1 - GROUND_FRICTION; else player->spd_x *= 1 - AIR_FRICTION; /* Acceleration. */ if (on_ground) player->spd_x += move_x * GROUND_ACCELERATION; else player->spd_x += move_x * AIR_ACCELERATION; /* Gravity. */ player->spd_y += GRAVITY; /* Vertical friction. */ if (fastfall && player->spd_y > 0) player->spd_y *= 1 - FASTFALL_RESISTANCE; else player->spd_y *= 1 - FALL_RESISTANCE; /* Jump logic. */ /** Coyot jump. */ if (on_ground) player->coyot = COYOT; else if (player->coyot > 0) player->coyot -= 1; /** Input buffer. */ if (jump_pressed) player->jump_buffer = JUMP_BUFFER; else if (player->jump_buffer > 0) player->jump_buffer -= 1; /** Jump. */ if (jump_held && player->coyot > 0 && player->jump_buffer > 0) { player->spd_y = JUMP_SPD; player->jump_buffer = 0; player->coyot = 0; } } else { /* Gravity. */ player->spd_y += GRAVITY; /* Vertical friction. */ player->spd_y *= 1 - FALL_RESISTANCE; } /* Semi-solid platform stop fall. */ if (player->spd_y > 0 && player_collide(*level, player->pos, SEMI_SOLID_TILE, 0)) player->spd_y = 0; /* Apply movement. */ const bool hit_wall_x = player_move(player, *level, round(player->spd_x), 0); player_move(player, *level, 0, round(player->spd_y)); /* If player hit a wall in stun mode, turn back. */ if (hit_wall_x && player->stun) { player->facing *= -1; player->spd_x = KNOCKBACK_X * -player->facing; } /* Damageboost a.k.a. stun mode. */ if (player_collide(*level, player->pos, PAIN_TILE, 5)) { if (!player->knocked) { player->spd_y = -signf(player->spd_y) * KNOCKBACK_Y; player->spd_x = -signf(player->facing) * KNOCKBACK_X; player->knocked = true; } player->stun = true; } else { player->knocked = false; if (on_ground) player->stun = false; } /* Return to last checkpoint if out of bounds. */ if ((player->pos.x <= -PLAYER_WIDTH) || (player->pos.y <= -PLAYER_HEIGHT) || (player->pos.x >= LEVEL_WIDTH * TILE_SIZE) || (player->pos.y >= LEVEL_HEIGHT * TILE_SIZE)) { player_init(player); player->pos = level->start_pos; } /* Exit, victory! */ if (player->keys_left == 0 && player_collide(*level, player->pos, EXIT_TILE, 0)) { *level_id += 1; level_load(level, player, *level_id); } } void player_draw(Player player) { /* Draw colored rectangle depending on player state. */ u32 color = 0; if (player.stun) color = C_RGB((230/4), (41/4), (55/4)); /* Red. */ else color = C_RGB((0/4), (121/4), (241/4)); /* Blue. */ const vec2_int_t x = player.pos.x + DRAW_OFFSET_X; const vec2_int_t y = player.pos.y + DRAW_OFFSET_Y; drect(x, y, x + PLAYER_WIDTH - 1, y + PLAYER_HEIGHT - 1, color); } /* Helper functions */ bool player_collide(Level level, Vec2 pos, tile_t tile, u8 margin) { const vec2_int_t left = pos.x + margin; const vec2_int_t right = pos.x + PLAYER_WIDTH - 1 - margin; const vec2_int_t up = pos.y + margin; const vec2_int_t down = pos.y + PLAYER_HEIGHT - 1 - margin; return ((tile == level_get_tile_at_px(level, (Vec2){left, up})) || (tile == level_get_tile_at_px(level, (Vec2){right, up})) || (tile == level_get_tile_at_px(level, (Vec2){left, down})) || (tile == level_get_tile_at_px(level, (Vec2){right, down}))); } bool player_move(Player *player, Level level, i8 spd_x, i8 spd_y) { player->pos.x += spd_x; player->pos.y += spd_y; /* If player is in solid, move back until it ain't. */ if (player_collide(level, player->pos, SOLID_TILE, 0)) { const i8 sign_spd_x = sign(spd_x); const i8 sign_spd_y = sign(spd_y); if (sign_spd_x != 0) player->spd_x = 0; if (sign_spd_y != 0) player->spd_y = 0; while (player_collide(level, player->pos, SOLID_TILE, 0)) { player->pos.x -= sign_spd_x; player->pos.y -= sign_spd_y; } return true; } return false; } i8 sign(i8 value) { if (value > 0) return 1; else if (value < 0) return -1; else return 0; } f32 signf(f32 value) { if (value > 0.0) return 1.0; else if (value < 0.0) return -1.0; else return 0.0; } i8 round(f32 value) { if (value > 0) return (i8)(value + 0.5); else return (i8)(value - 0.5); }