#include "entities.h" #include "game.h" #include "enemies.h" #include #include //--- // 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; } 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 1.5x over-speed is maintained */ fixed_t cur_v2 = fmul(move->vx, move->vx) + fmul(move->vy, move->vy); fixed_t max_v2 = fmul(params->max_speed, params->max_speed); if(move->dash < 0 && 2 * cur_v2 > 3 * 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); } bool entity_moving(entity_t const *e) { return (e->movement.vx != 0) || (e->movement.vy != 0); } void entity_set_normal_anim(entity_t *e, anim_frame_t *frame, int priority) { if(priority < e->anim_priority) return; e->anim.frame = frame; e->anim.elapsed = 0; e->anim_priority = priority; } void entity_set_directional_anim(entity_t *e, anim_frame_t *frame_dirs[], int priority) { if(e->movement.facing >= 4) return; entity_set_normal_anim(e, frame_dirs[e->movement.facing], priority); } 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->identity > 0) { int index = (e->movement.facing == RIGHT); if(e->HP == 0) entity_set_anim(e, enemies[e->identity]->anim_death[index], 4); else entity_set_anim(e, enemies[e->identity]->anim_damage[index], 3); } else { entity_set_anim(e, anims_player_Hit[e->movement.facing], 3); } 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; } effect_area_t *effect_area_new_attack(uint16_t type, entity_t *e, int facing) { effect_area_t *area = effect_area_new(type); if(!area) return NULL; frect_t hitbox = { 0 }; fixed_t distance = fix(0.75); fpoint_t dir = fdir(facing); anim_frame_t *anim = NULL; if(type == EFFECT_ATTACK_HIT) { anim = anim_hit; hitbox = (frect_t){ -fix(4)/16, fix(3)/16, -fix(4)/16, fix(3)/16 }; } else if(type == EFFECT_ATTACK_SLASH) { anim = anim_swing[facing]; hitbox = (frect_t){ -fix(10)/16, fix(10)/16, -fix(4)/16, fix(4)/16 }; } else if(type == EFFECT_ATTACK_IMPALE) { anim = anim_impale[facing]; hitbox = (frect_t){ -fix(4)/16, fix(4)/16, -fix(5)/16, fix(5)/16 }; } else if(type == EFFECT_ATTACK_SHOCK) { anim = anims_skill_shock; hitbox = (frect_t){ -fix(18)/16, fix(18)/16, -fix(19)/16, fix(19)/16 }; distance = fix(0); } area->sprite = rect_rotate(hitbox, UP, facing); area->anchor = rect_center(entity_sprite(e)); area->anchor.x += fmul(distance, dir.x); area->anchor.y += fmul(distance, dir.y); area->lifetime = anim_duration(anim); area->repeat_delay = 0; area->origin = e; if(type == EFFECT_ATTACK_HIT || type == EFFECT_ATTACK_SLASH || type == EFFECT_ATTACK_IMPALE) { area->data.slash.strength = e->ATK; area->data.slash.dir = facing; } else if(type == EFFECT_ATTACK_SHOCK) { area->data.shock.strength = e->ATK; area->data.shock.origin = entity_pos(e); area->repeat_delay = fix(0.15); } effect_area_set_anim(area, anim); return area; } 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 attack_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; /* Knockback */ fixed_t r = (rand() & (fix(0.25)-1)) + fix(0.875); /* Half knockback against players */ if(e->identity == 0) r /= 2; fpoint_t dir = { 0, 0 }; int damage = 0; if(ea->type == EFFECT_ATTACK_HIT || ea->type == EFFECT_ATTACK_SLASH || ea->type == EFFECT_ATTACK_IMPALE) { dir = fdir(ea->data.slash.dir); damage = ea->data.slash.strength; } else if(ea->type == EFFECT_ATTACK_SHOCK) { dir.x = e->movement.x - ea->data.shock.origin.x; dir.y = e->movement.y - ea->data.shock.origin.y; dir = fnormalize(dir); damage = ea->data.shock.strength * 3 / 2; } /* Inflict damage */ damage = entity_damage(e, damage); /* Apply knockback */ 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(0.5) }; 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_ATTACK_HIT || ea->type == EFFECT_ATTACK_SLASH || ea->type == EFFECT_ATTACK_IMPALE || ea->type == EFFECT_ATTACK_SHOCK) was_hit = attack_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; } bool effect_area_is_background(effect_area_t const *ea) { return ea->type == EFFECT_ATTACK_SHOCK; }