RogueLife/src/aoe.c

364 lines
11 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.5);
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 };
distance = fix(0.375);
}
else if(type == AOE_PROJECTILE || type == AOE_PROJECTILE_FAST) {
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(23)/16, fix(24)/16, -fix(23)/16, fix(24)/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);
}
else if(type == AOE_FIRE_CHARGE
|| type == AOE_WATER_CHARGE
|| type == AOE_CHEMICAL_CHARGE) {
anim = NULL;
hitbox = (rect){ -fix(12)/16, fix(12)/16, -fix(13)/16, fix(2)/16 };
distance = fix(0);
lifetime = fix(1.1);
rotate = false;
}
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;
if(lifetime)
aoe->lifetime = lifetime;
else if(anim)
aoe->lifetime = anim_duration(anim);
else
aoe->lifetime = fix(1.0);
aoe->repeat_delay = 0;
aoe->origin = origin;
if(type == AOE_HIT
|| type == AOE_SLASH
|| type == AOE_IMPALE) {
aoe->data.generic.strength = f->ATK;
aoe->data.generic.dir = p->facing;
}
else if(type == AOE_JUDGEMENT) {
aoe->data.generic.strength = f->MAG;
aoe->data.generic.dir = p->facing;
}
else if(type == AOE_PROJECTILE || type == AOE_PROJECTILE_FAST) {
aoe->data.projectile.strength = f->ATK;
aoe->data.projectile.direction = dir;
if(type == AOE_PROJECTILE_FAST)
aoe->data.projectile.speed = fix(6.0);
else
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.2);
}
else if(type == AOE_BULLET) {
aoe->data.bullet.strength = f->MAG;
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;
}
else if(type == AOE_FIRE_CHARGE
|| type == AOE_WATER_CHARGE
|| type == AOE_CHEMICAL_CHARGE) {
aoe->data.charge.element = 0;
aoe->data.charge.power = f->MAG;
aoe->data.charge.origin = physical_pos(origin);
aoe->repeat_delay = fix(0.2);
}
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 = !aoe->origin ? NULL : 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;
if(target_f->invulnerability_delay > 0)
return false;
/* No friendly fire */
bool origin_is_monster = !aoe->origin ? true :
(origin_f && origin_f->enemy != NULL);
bool target_is_monster = (target_f->enemy != 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) 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 || aoe->type == AOE_PROJECTILE_FAST) {
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 /= 4;
fighter_effect_stun(target, fix(0.1));
}
else if(aoe->type == AOE_JUDGEMENT) {
r = fix(0);
damage = aoe->data.generic.strength * 5 / 2;
}
else if(aoe->type == AOE_BULLET) {
/* TODO: Sideways knockback */
damage = aoe->data.bullet.strength * 3 / 2;
}
else if(aoe->type == AOE_FIRE_CHARGE
|| aoe->type == AOE_WATER_CHARGE
|| aoe->type == AOE_CHEMICAL_CHARGE) {
dir.x = target_p->x - aoe->data.charge.origin.x;
dir.y = target_p->y - aoe->data.charge.origin.y;
dir = fnormalize(dir);
damage = aoe->data.charge.power * 3 / 2;
r /= 4;
fighter_effect_stun(target, fix(0.1));
}
/* 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 ? C_WHITE : C_RED);
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);
fighter_t *e_f = getcomp(e, fighter);
if(aoe->type == AOE_ITEM && e_f && e_f->player) {
bool picked = player_give_item(e, aoe->data.item.type);
if(picked) {
game_hud_anim_backpack_item(game);
aoe->lifetime = 0;
}
/* Don't bother recording the hit since the item will disappear */
was_hit = false;
}
/* 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_PROJECTILE_FAST
|| aoe->type == AOE_SLASH
|| aoe->type == AOE_IMPALE
|| aoe->type == AOE_SHOCK
|| aoe->type == AOE_BULLET
|| aoe->type == AOE_FIRE_CHARGE
|| aoe->type == AOE_WATER_CHARGE
|| aoe->type == AOE_CHEMICAL_CHARGE)
was_hit = attack_apply(game, aoe, e);
if(aoe->type == AOE_JUDGEMENT) {
fixed_t total_duration = anim_duration(&anims_skill_judgement);
fixed_t elapsed = total_duration - aoe->lifetime;
/* The AOE deals damage only during a part of the animation */
if(elapsed >= fix(0.360) && elapsed < fix(0.960))
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 || aoe->type == AOE_PROJECTILE_FAST) {
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;
}
}