RogueLife/src/entities.c

254 lines
6.7 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;
move->dash_particle_cooldown = fix(0);
}
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 over-speed is maintained */
fixed_t cur_v2 = fmul(move->x, move->x) + fmul(move->y, move->y);
fixed_t max_v2 = fmul(params->max_speed, params->max_speed);
if(move->dash < 0 && cur_v2 > 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);
}
void entity_set_normal_anim(entity_t *e, anim_frame_t *frame)
{
e->anim.frame = frame;
e->anim.elapsed = 0;
}
void entity_set_directional_anim(entity_t *e, anim_frame_t *frame_dirs[])
{
if(e->movement.facing >= 4) return;
entity_set_normal_anim(e, frame_dirs[e->movement.facing]);
}
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->HP == 0 && e->identity > 0) {
entity_set_anim(e, enemies[e->identity]->anim_death);
}
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;
}
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 slash_hit_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;
/* Inflict damage */
int damage = entity_damage(e, ea->data.slash.strength);
/* Knockback */
fixed_t r = (rand() & (fix(1)/4-1)) + fix(7)/8;
/* Half knockback against players */
if(e->identity == 0) r /= 2;
fpoint_t dir = fdir(ea->data.slash.dir);
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(1)/2 };
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_HIT)
was_hit = slash_hit_apply(game, ea, e);
else if(ea->type == EFFECT_SLASH)
was_hit = slash_hit_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;
}