RogueLife/src/aoe.c

298 lines
8.6 KiB
C

#include "comp/entity.h"
#include "comp/physical.h"
#include "comp/visible.h"
#include "comp/fighter.h"
#include "comp/particle.h"
#include "aoe.h"
#include "game.h"
#include "enemies.h"
entity_t *aoe_make(uint16_t type, vec2 position, fixed_t lifetime)
{
entity_t *e = entity_make(physical, visible, aoe);
physical_t *p = getcomp(e, physical);
p->x = position.x;
p->y = position.y;
aoe_t *aoe = getcomp(e, aoe);
aoe->type = type;
aoe->lifetime = lifetime;
aoe->hits = NULL;
aoe->hit_count = 0;
return e;
}
void aoe_destroy(entity_t *e)
{
aoe_t *aoe = getcomp(e, aoe);
free(aoe->hits);
}
entity_t *aoe_make_attack(uint16_t type, entity_t *origin, vec2 dir)
{
entity_t *e = aoe_make(type, physical_pos(origin), fix(0));
if(e == NULL)
return NULL;
fighter_t *f = getcomp(origin, fighter);
physical_t *p = getcomp(e, physical);
visible_t *v = getcomp(e, visible);
aoe_t *aoe = getcomp(e, aoe);
rect hitbox = { 0 };
fixed_t distance = fix(0.625);
anim_t const *anim = NULL;
bool rotate = true;
fixed_t lifetime = fix(0);
int plane = VERTICAL;
if(type == AOE_HIT) {
anim = &anims_skill_hit;
hitbox = (rect){ -fix(4)/16, fix(3)/16, -fix(4)/16, fix(3)/16 };
}
else if(type == AOE_PROJECTILE) {
anim = &anims_skill_projectile;
hitbox = (rect){ -fix(1)/16, fix(1)/16, -fix(1)/16, fix(1)/16 };
distance = fix(0.5);
lifetime = fix(999.0);
}
else if(type == AOE_SLASH) {
anim = &anims_skill_swing;
hitbox = (rect){ -fix(10)/16, fix(10)/16, -fix(8)/16, 0 };
}
else if(type == AOE_IMPALE) {
anim = &anims_skill_impale;
hitbox = (rect){ -fix(4)/16, fix(4)/16, -fix(10)/16, 0 };
}
else if(type == AOE_SHOCK) {
anim = &anims_skill_shock;
hitbox = (rect){ -fix(17)/16, fix(18)/16, -fix(17)/16, fix(18)/16 };
distance = fix(0);
rotate = false;
plane = HORIZONTAL;
}
else if(type == AOE_JUDGEMENT) {
anim = &anims_skill_judgement;
hitbox = (rect){ -fix(10)/16, fix(11)/16, -fix(6)/16, fix(6)/16 };
distance = fix(1.5);
rotate = false;
}
else if(type == AOE_BULLET) {
anim = &anims_skill_bullet;
hitbox = (rect){ -fix(8)/16, fix(8)/16, -fix(7)/16, fix(7)/16 };
distance = fix(0.375);
lifetime = fix(999.0);
}
p->x += fmul(distance, dir.x);
p->y += fmul(distance, dir.y);
p->facing = frdir(dir);
p->hitbox = rotate ? rect_rotate(hitbox, UP, p->facing) : hitbox;
v->sprite_plane = plane;
aoe->lifetime = lifetime ? lifetime : anim_duration(anim);
aoe->repeat_delay = 0;
aoe->origin = origin;
if(type == AOE_HIT
|| type == AOE_SLASH
|| type == AOE_IMPALE
|| type == AOE_JUDGEMENT) {
aoe->data.generic.strength = f->ATK;
aoe->data.generic.dir = p->facing;
}
else if(type == AOE_PROJECTILE) {
aoe->data.projectile.strength = f->ATK;
aoe->data.projectile.direction = dir;
aoe->data.projectile.speed = fix(3.0);
p->facing = (dir.x >= 0 ? RIGHT : LEFT);
v->z = fix(0.5);
v->shadow_size = 3;
}
else if(type == AOE_SHOCK) {
aoe->data.shock.strength = f->ATK;
aoe->data.shock.origin = physical_pos(origin);
aoe->repeat_delay = fix(0.1);
}
else if(type == AOE_BULLET) {
aoe->data.bullet.strength = f->ATK;
aoe->data.bullet.dir = p->facing;
aoe->data.bullet.v = fix(10.0);
aoe->data.bullet.final_v = fix(4.5);
aoe->repeat_delay = fix(0.05);
v->z = fix(0.5);
v->shadow_size = 4;
}
visible_set_anim(e, anim, 1);
return e;
}
entity_t *aoe_make_attack_4(uint16_t type, entity_t *origin, int facing)
{
return aoe_make_attack(type, origin, fdir(facing));
}
/* Existing record of the area hitting this entity */
static aoe_record_t *aoe_record(aoe_t *aoe, entity_t *target)
{
for(int i = 0; i < aoe->hit_count; i++) {
aoe_record_t *r = &aoe->hits[i];
if(r->entity == target) return r;
}
return NULL;
}
static bool attack_apply(game_t *game, aoe_t *aoe, entity_t *target)
{
fighter_t *origin_f = getcomp(aoe->origin, fighter);
fighter_t *target_f = getcomp(target, fighter);
mechanical_t *target_m = getcomp(target, mechanical);
physical_t *target_p = getcomp(target, physical);
if(!target_f)
return false;
/* No friendly fire */
bool origin_is_monster = (origin_f && origin_f->enemy_data != NULL);
bool target_is_monster = (target_f->enemy_data != NULL);
if(origin_is_monster == target_is_monster || target_f->HP == 0)
return false;
/* Dash invincibility */
if(target_m && mechanical_dashing(target))
return false;
/* Knockback */
fixed_t r = (rand() & (fix(0.125)-1)) + fix(0.375);
/* Half knockback against players */
if(target_f->enemy_data == NULL) r /= 2;
vec2 dir = { 0, 0 };
int damage = 0;
if(aoe->type == AOE_HIT
|| aoe->type == AOE_SLASH
|| aoe->type == AOE_IMPALE) {
dir = fdir(aoe->data.generic.dir);
damage = aoe->data.generic.strength;
}
else if(aoe->type == AOE_PROJECTILE) {
dir = aoe->data.projectile.direction;
damage = aoe->data.projectile.strength;
r /= 2;
/* Projectile disappears after hitting a single target */
aoe->lifetime = fix(0);
}
else if(aoe->type == AOE_SHOCK) {
dir.x = target_p->x - aoe->data.shock.origin.x;
dir.y = target_p->y - aoe->data.shock.origin.y;
dir = fnormalize(dir);
damage = aoe->data.shock.strength * 3 / 2;
r /= 2;
}
else if(aoe->type == AOE_JUDGEMENT) {
r = fix(0);
damage = aoe->data.generic.strength * 5;
}
else if(aoe->type == AOE_BULLET) {
/* TODO: Sideways knockback */
damage = aoe->data.bullet.strength * 2;
}
/* Inflict damage */
damage = fighter_damage(target, damage);
/* Apply knockback */
if(target_m) {
target_m->vdx += fmul(dir.x, fmul(r, KNOCKBACK_SPEED));
target_m->vdy += fmul(dir.y, fmul(r, KNOCKBACK_SPEED));
}
/* Spawn damage particle */
entity_t *particle = particle_make_damage(target, damage,
(target_f->enemy_data == NULL) ? C_RED : C_WHITE);
game_add_entity(game, particle);
/* Quick screenshake for entities hit by a bullet */
if(aoe->type == AOE_BULLET)
game_shake(game, 3, fix(0.1));
return true;
}
void aoe_apply(game_t *game, entity_t *entity, entity_t *e)
{
bool was_hit = false;
aoe_t *aoe = getcomp(entity, aoe);
aoe_record_t *rec = aoe_record(aoe, e);
/* Don't hit entities that have been recently hit */
if(rec && aoe->repeat_delay == 0) return;
if(rec && aoe->lifetime > rec->lifetime - aoe->repeat_delay) return;
if(aoe->type == AOE_HIT
|| aoe->type == AOE_PROJECTILE
|| aoe->type == AOE_SLASH
|| aoe->type == AOE_IMPALE
|| aoe->type == AOE_SHOCK
|| aoe->type == AOE_JUDGEMENT
|| aoe->type == AOE_BULLET)
was_hit = attack_apply(game, aoe, e);
if(!was_hit) return;
/* Update record */
if(!rec) {
size_t new_size = (aoe->hit_count + 1) * sizeof *aoe->hits;
aoe_record_t *new_hits = realloc(aoe->hits, new_size);
if(!new_hits) return;
aoe->hits = new_hits;
rec = &new_hits[aoe->hit_count];
rec->entity = e;
aoe->hit_count++;
}
rec->lifetime = aoe->lifetime;
}
void aoe_update(game_t *game, entity_t *entity, fixed_t dt)
{
physical_t *p = getcomp(entity, physical);
aoe_t *aoe = getcomp(entity, aoe);
if(aoe->type == AOE_PROJECTILE) {
fixed_t v = aoe->data.projectile.speed;
p->x += fmul(fmul(aoe->data.projectile.direction.x, v), dt);
p->y += fmul(fmul(aoe->data.projectile.direction.y, v), dt);
rect small_box = { 0, 0, 0, 0 };
small_box = rect_translate(small_box, physical_pos(entity));
if(map_collides(game->map, small_box))
aoe->lifetime = 0;
}
else if(aoe->type == AOE_BULLET) {
vec2 dir = fdir(aoe->data.bullet.dir);
p->x += fmul(fmul(aoe->data.bullet.v, dir.x), dt);
p->y += fmul(fmul(aoe->data.bullet.v, dir.y), dt);
/* Speed update - exponential decline from initial value to final_v */
aoe->data.bullet.v += fmul(
aoe->data.bullet.final_v - aoe->data.bullet.v, fix(0.001));
/* Collision of anchor with map destroys the bullet */
rect small_box = { 0, 0, 0, 0 };
small_box = rect_translate(small_box, physical_pos(entity));
if(map_collides(game->map, small_box))
aoe->lifetime = 0;
}
}