RogueLife/src/enemies.c

262 lines
6.4 KiB
C

#include "comp/entity.h"
#include "comp/physical.h"
#include "comp/visible.h"
#include "comp/mechanical.h"
#include "comp/fighter.h"
#include "aoe.h"
#include "enemies.h"
#include <string.h>
/* Declare animations for an enemy */
#define ANIMS(name, I, W, A, H, D) \
.anim_idle = &anims_ ## name ## _ ## I, \
.anim_walking = &anims_ ## name ## _ ## W, \
.anim_attack = &anims_ ## name ## _ ## A, \
.anim_hit = &anims_ ## name ## _ ## H, \
.anim_death = &anims_ ## name ## _ ## D
static enemy_t const slime_1 = {
.name = "Slime",
.level = 1,
ANIMS(slime, Idle, Walking, Idle, Hit, Death),
.hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 },
.limits = {
.max_speed = fix(1),
.friction = fix(0.6),
},
.stats = {
.HP = fix(1.4),
.ATK = fix(1.3),
.MAG = fix(1.0),
.DEF = fix(1.4),
},
.shadow_size = 4,
.xp = 2,
.z = 0,
};
static enemy_t const bat_2 = {
.name = "Bat",
.level = 2,
ANIMS(bat, Idle, Idle, Idle, Hit, Death),
.hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 },
.limits = {
.max_speed = fix(1.8),
.friction = fix(0.8),
},
.stats = {
.HP = fix(1.5),
.ATK = fix(1.5),
.MAG = fix(1.3),
.DEF = fix(1.2),
},
.shadow_size = 4,
.xp = 6,
.z = fix(0.75),
};
static enemy_t const fire_slime_4 = {
.name = "Fire slime",
.level = 4,
ANIMS(fire_slime, Idle, Walking, Idle, Hit, Death),
.hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 },
.limits = {
.max_speed = fix(1),
.friction = fix(0.6),
},
.stats = {
.HP = fix(1.4),
.ATK = fix(1.2),
.MAG = fix(1.4),
.DEF = fix(1.4),
},
.shadow_size = 4,
.xp = 14,
.z = 0,
};
static enemy_t const gunslinger_8 = {
.name = "Gunslinger",
.level = 8,
ANIMS(gunslinger, Idle, Walking, Fire, Hit, Death),
.hitbox = (rect){ -fix(3)/16, fix(4)/16, -fix(2)/16, fix(3)/16 },
.limits = {
.max_speed = fix(1.8),
.friction = fix(0.8),
},
.stats = {
.HP = fix(1.3),
.ATK = fix(1.6),
.MAG = fix(1.4),
.DEF = fix(1.2),
},
.shadow_size = 4,
.xp = 30,
.z = fix(0.25),
};
static enemy_t const * const enemies[] = {
[ENEMY_SLIME_1] = &slime_1,
[ENEMY_BAT_2] = &bat_2,
[ENEMY_FIRE_SLIME_4] = &fire_slime_4,
[ENEMY_GUNSLINGER_8] = &gunslinger_8,
};
enemy_t const *enemy_data(int enemy_id)
{
return enemies[enemy_id];
}
entity_t *enemy_make(int enemy_id)
{
if(enemy_id < 0 || (size_t)enemy_id >= sizeof enemies / sizeof *enemies)
return NULL;
entity_t *e = entity_make(physical, visible, mechanical, fighter);
if(e == NULL)
return NULL;
enemy_t const *data = enemies[enemy_id];
/* These will probably be overridden by the caller */
physical_t *p = getcomp(e, physical);
p->x = fix(0);
p->y = fix(0);
p->hitbox = data->hitbox;
p->facing = LEFT;
visible_t *v = getcomp(e, visible);
v->z = data->z;
v->sprite_plane = VERTICAL;
v->shadow_size = data->shadow_size;
visible_set_anim(e, data->anim_idle, 1);
mechanical_t *m = getcomp(e, mechanical);
m->limits = &data->limits;
m->vx = fix(0);
m->vy = fix(0);
m->dash = fix(0);
m->dash_facing = LEFT;
/* Instantiate fighter statistics */
fighter_t *f = getcomp(e, fighter);
memset(f, 0, sizeof *f);
f->enemy_data = data;
fighter_set_stats(f, &data->stats, data->level, fix(1.0));
f->HP = f->HP_max;
f->combo_length = 1;
return e;
}
/* Enemy AIs */
static void move_towards_player(game_t *g, entity_t *e, fixed_t dt)
{
physical_t *p = getcomp(e, physical);
vec2 pos = physical_pos(e);
vec2 direction;
if(dist2(pos, physical_pos(g->player)) <= fix(0.25)) /* 0.5^2 */
return;
pfg_path_t path = pfg_bfs_inwards(&g->paths_to_player, vec_f2i(pos));
if(path.points) {
direction = pfc_shortcut_one(&path, pos, physical_pos(g->player),
p->hitbox);
pfg_path_free(&path);
}
if(direction.x && direction.y) {
direction.x -= pos.x;
direction.y -= pos.y;
}
mechanical_move(e, direction, dt, g->map);
if(direction.x > 0) p->facing = RIGHT;
else if(direction.x < 0) p->facing = LEFT;
else if(p->x < getcomp(g->player, physical)->x) p->facing = RIGHT;
else p->facing = LEFT;
}
static bool move_within_range_of_player(game_t *g, entity_t *e, fixed_t range,
fixed_t dt)
{
if(dist2(physical_pos(e), physical_pos(g->player)) <= fmul(range, range))
return true;
move_towards_player(g, e, dt);
return false;
}
static bool contact_attack(game_t *g, entity_t *e)
{
fighter_t *f = getcomp(e, fighter);
if(f->current_attack)
return false;
physical_t *p = getcomp(e, physical);
physical_t *player_p = getcomp(g->player, physical);
vec2 dir = { player_p->x - p->x, player_p->y - p->y };
dir = fnormalize(dir);
entity_t *aoe = aoe_make_attack(AOE_HIT, e, dir);
game_add_entity(g, aoe);
f->current_attack = aoe;
f->attack_follows_movement = true;
visible_set_anim(e, f->enemy_data->anim_attack, 2);
return true;
}
static bool range_attack(game_t *g, entity_t *e)
{
fighter_t *f = getcomp(e, fighter);
if(f->current_attack)
return false;
physical_t *p = getcomp(e, physical);
physical_t *player_p = getcomp(g->player, physical);
vec2 dir = { player_p->x - p->x, player_p->y - p->y };
dir = fnormalize(dir);
entity_t *aoe = aoe_make_attack(AOE_PROJECTILE, e, dir);
game_add_entity(g, aoe);
f->current_attack = aoe;
f->attack_follows_movement = false;
visible_set_anim(e, f->enemy_data->anim_attack, 2);
return true;
}
void enemy_ai(game_t *g, entity_t *e, fixed_t dt)
{
fighter_t *f = getcomp(e, fighter);
if(f->enemy_data == &slime_1 || f->enemy_data == &bat_2) {
if(move_within_range_of_player(g, e, fix(1.0), dt)) {
contact_attack(g, e);
}
}
else if(f->enemy_data == &fire_slime_4) {
}
else if(f->enemy_data == &gunslinger_8) {
rect hitbox = { fix(-1/16), fix(1/16), fix(-1/16), fix(1/16) };
bool clear = raycast_clear_hitbox(g->map, physical_pos(e),
physical_pos(g->player), hitbox);
if(clear)
range_attack(g, e);
else
move_towards_player(g, e, dt);
}
}