RogueLife/src/entities.c

343 lines
9.4 KiB
C

#include "entities.h"
#include "game.h"
#include "enemies.h"
#include <stdlib.h>
#include <gint/defs/util.h>
//---
// Entities
//---
fpoint_t entity_pos(entity_t const *e)
{
return (fpoint_t){ e->movement.x, e->movement.y };
}
frect_t entity_hitbox(entity_t const *e)
{
return rect_translate(e->hitbox, entity_pos(e));
}
frect_t entity_sprite(entity_t const *e)
{
return rect_translate(e->sprite, entity_pos(e));
}
static void update_pos(entity_movement_t *m, fixed_t dt)
{
m->x += fmul(m->vx, dt);
m->y += fmul(m->vy, dt);
}
void entity_dash(entity_t *e, int direction)
{
entity_movement_params_t const *params = e->movement_params;
entity_movement_t *move = &e->movement;
if(move->dash != 0) return;
move->dash = params->dash_duration;
move->dash_facing = direction;
}
bool entity_dashing(entity_t const *e)
{
entity_movement_t const *move = &e->movement;
entity_movement_params_t const *params = e->movement_params;
/* True only if the direction of the dash movement is preserved */
if(move->facing != move->dash_facing)
return false;
/* True during initial propulsion */
if(move->dash > 0)
return true;
/* Also true as long as 1.5x over-speed is maintained */
fixed_t cur_v2 = fmul(move->vx, move->vx) + fmul(move->vy, move->vy);
fixed_t max_v2 = fmul(params->max_speed, params->max_speed);
if(move->dash < 0 && 2 * cur_v2 > 3 * max_v2)
return true;
return false;
}
entity_movement_t entity_move(entity_t *e, fpoint_t direction, fixed_t dt)
{
entity_movement_params_t const *params = e->movement_params;
entity_movement_t next = e->movement;
direction = fnormalize(direction);
/* Determine facing */
int facing = -1;
bool horz = abs(direction.x) >= abs(direction.y);
if(direction.x == 0 && direction.y == 0) facing = -1;
else if( horz && direction.x >= 0) facing = RIGHT;
else if( horz && direction.x <= 0) facing = LEFT;
else if(!horz && direction.y <= 0) facing = UP;
else if(!horz && direction.y >= 0) facing = DOWN;
/* Targeted speed */
fpoint_t target = { 0, 0 };
/* The dash speed is set to one direction and cannot changed */
if(next.dash > 0) {
fpoint_t dir = fdir(next.dash_facing);
target.x += fmul(params->dash_speed, dir.x);
target.y += fmul(params->dash_speed, dir.y);
}
/* Walking speed can be directed anywhere, anytime */
if(facing >= 0) {
next.facing = facing;
target.x += fmul(params->max_speed, direction.x);
target.y += fmul(params->max_speed, direction.y);
}
/* Get there exponentially fast */
next.vx += fmul(target.x - next.vx, fmul(params->propulsion, dt));
next.vy += fmul(target.y - next.vy, fmul(params->propulsion, dt));
/* Round very small speeds (smaller than 1/256 tiles/s) to 0 */
if(next.vx >= -256 && next.vx <= 255) next.vx = 0;
if(next.vy >= -256 && next.vy <= 255) next.vy = 0;
/* Decrement dash duration or dash cooldown */
if(next.dash > 0) {
next.dash -= dt;
if(next.dash <= 0) next.dash = -params->dash_cooldown;
}
else if(next.dash < 0) {
next.dash += dt;
if(next.dash >= 0) next.dash = 0;
}
update_pos(&next, dt);
return next;
}
entity_movement_t entity_move4(entity_t *e, int direction, fixed_t dt)
{
return entity_move(e, fdir(direction), dt);
}
bool entity_moving(entity_t const *e)
{
return (e->movement.vx != 0) || (e->movement.vy != 0);
}
void entity_set_normal_anim(entity_t *e, anim_frame_t *frame, int priority)
{
if(priority < e->anim_priority) return;
e->anim.frame = frame;
e->anim.elapsed = 0;
e->anim_priority = priority;
}
void entity_set_directional_anim(entity_t *e, anim_frame_t *frame_dirs[],
int priority)
{
if(e->movement.facing >= 4) return;
entity_set_normal_anim(e, frame_dirs[e->movement.facing], priority);
}
int entity_damage(entity_t *e, int base_damage)
{
if(e->HP == 0) return 0;
base_damage -= e->DEF;
if(base_damage <= 0) return 0;
int variation = (base_damage >= 4) ? (rand() % (base_damage / 4)) : 0;
int damage = (base_damage * 7) / 8 + variation;
if(e->HP < damage) e->HP = 0;
else e->HP -= damage;
if(e->identity > 0) {
int index = (e->movement.facing == RIGHT);
if(e->HP == 0)
entity_set_anim(e, enemies[e->identity]->anim_death[index], 4);
else
entity_set_anim(e, enemies[e->identity]->anim_damage[index], 3);
}
else {
entity_set_anim(e, anims_player_Hit[e->movement.facing], 3);
}
return damage;
}
//---
// Effect areas
//---
effect_area_t *effect_area_new(uint16_t type)
{
effect_area_t *ea = malloc(sizeof *ea);
if(!ea) return NULL;
ea->type = type;
ea->hits = NULL;
ea->hit_count = 0;
return ea;
}
effect_area_t *effect_area_new_attack(uint16_t type, entity_t *e, int facing)
{
effect_area_t *area = effect_area_new(type);
if(!area) return NULL;
frect_t hitbox = { 0 };
fixed_t distance = fix(0.75);
fpoint_t dir = fdir(facing);
anim_frame_t *anim = NULL;
if(type == EFFECT_ATTACK_HIT) {
anim = anim_hit;
hitbox = (frect_t){ -fix(4)/16, fix(3)/16, -fix(4)/16, fix(3)/16 };
}
else if(type == EFFECT_ATTACK_SLASH) {
anim = anim_swing[facing];
hitbox = (frect_t){ -fix(10)/16, fix(10)/16, -fix(4)/16, fix(4)/16 };
}
else if(type == EFFECT_ATTACK_IMPALE) {
anim = anim_impale[facing];
hitbox = (frect_t){ -fix(4)/16, fix(4)/16, -fix(5)/16, fix(5)/16 };
}
else if(type == EFFECT_ATTACK_SHOCK) {
anim = anims_skill_shock;
hitbox = (frect_t){ -fix(18)/16, fix(18)/16, -fix(19)/16, fix(19)/16 };
distance = fix(0);
}
area->sprite = rect_rotate(hitbox, UP, facing);
area->anchor = rect_center(entity_sprite(e));
area->anchor.x += fmul(distance, dir.x);
area->anchor.y += fmul(distance, dir.y);
area->lifetime = anim_duration(anim);
area->repeat_delay = 0;
area->origin = e;
if(type == EFFECT_ATTACK_HIT
|| type == EFFECT_ATTACK_SLASH
|| type == EFFECT_ATTACK_IMPALE) {
area->data.slash.strength = e->ATK;
area->data.slash.dir = facing;
}
else if(type == EFFECT_ATTACK_SHOCK) {
area->data.shock.strength = e->ATK;
area->data.shock.origin = entity_pos(e);
area->repeat_delay = fix(0.15);
}
effect_area_set_anim(area, anim);
return area;
}
void effect_area_set_anim(effect_area_t *ea, anim_frame_t *frame)
{
ea->anim.frame = frame;
ea->anim.elapsed = 0;
}
/* Existing record of the area hitting this entity */
static effect_area_record_t *effect_record(effect_area_t *ea, entity_t *e)
{
for(int i = 0; i < ea->hit_count; i++) {
effect_area_record_t *rec = &ea->hits[i];
if(rec->entity == e) return rec;
}
return NULL;
}
static bool attack_apply(game_t *game, effect_area_t *ea, entity_t *e)
{
/* No friendly fire */
bool origin_is_monster = (ea->origin->identity != 0);
bool target_is_monster = (e->identity != 0);
if(origin_is_monster == target_is_monster || e->HP == 0) return false;
/* Dash invincibility */
if(entity_dashing(e)) return false;
/* Knockback */
fixed_t r = (rand() & (fix(0.25)-1)) + fix(0.875);
/* Half knockback against players */
if(e->identity == 0) r /= 2;
fpoint_t dir = { 0, 0 };
int damage = 0;
if(ea->type == EFFECT_ATTACK_HIT
|| ea->type == EFFECT_ATTACK_SLASH
|| ea->type == EFFECT_ATTACK_IMPALE) {
dir = fdir(ea->data.slash.dir);
damage = ea->data.slash.strength;
}
else if(ea->type == EFFECT_ATTACK_SHOCK) {
dir.x = e->movement.x - ea->data.shock.origin.x;
dir.y = e->movement.y - ea->data.shock.origin.y;
dir = fnormalize(dir);
damage = ea->data.shock.strength * 3 / 2;
}
/* Inflict damage */
damage = entity_damage(e, damage);
/* Apply knockback */
e->movement.vx += fmul(dir.x, fmul(r, KNOCKBACK_SPEED));
e->movement.vy += fmul(dir.y, fmul(r, KNOCKBACK_SPEED));
/* Spawn damage particle */
particle_damage_t *p = malloc(sizeof *p);
p->particle.type = PARTICLE_DAMAGE;
p->particle.age = 0;
p->particle.pos = (fpoint_t){ e->movement.x, e->movement.y - fix(0.5) };
p->damage = damage;
p->color = (e->identity == 0) ? C_RED : C_WHITE;
game_add_particle(game, &p->particle);
return true;
}
void effect_area_apply(game_t *game, effect_area_t *ea, entity_t *e)
{
bool was_hit = false;
effect_area_record_t *rec = effect_record(ea, e);
/* Don't hit entities that have been recently hit */
if(rec && ea->repeat_delay == 0) return;
if(rec && ea->lifetime > rec->lifetime - ea->repeat_delay) return;
if(ea->type == EFFECT_ATTACK_HIT
|| ea->type == EFFECT_ATTACK_SLASH
|| ea->type == EFFECT_ATTACK_IMPALE
|| ea->type == EFFECT_ATTACK_SHOCK)
was_hit = attack_apply(game, ea, e);
if(!was_hit) return;
/* Update record */
if(!rec) {
size_t new_size = (ea->hit_count + 1) * sizeof *ea->hits;
effect_area_record_t *new_hits = realloc(ea->hits, new_size);
if(!new_hits) return;
ea->hits = new_hits;
rec = &new_hits[ea->hit_count];
rec->entity = e;
ea->hit_count++;
}
rec->lifetime = ea->lifetime;
}
bool effect_area_is_background(effect_area_t const *ea)
{
return ea->type == EFFECT_ATTACK_SHOCK;
}