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