#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; } }