ranged attackers + more versatile AI setup
This commit is contained in:
parent
b04333ddda
commit
69baa8d319
|
@ -73,6 +73,8 @@ set(ASSETS
|
|||
assets-cg/skills/judgement.aseprite
|
||||
assets-cg/skills/teleport.aseprite
|
||||
assets-cg/skills/shock.aseprite
|
||||
assets-cg/skills/projectile_left.aseprite
|
||||
assets-cg/skills/projectile_right.aseprite
|
||||
# Enemies: Slime
|
||||
assets-cg/enemies/slime_left.aseprite
|
||||
assets-cg/enemies/slime_right.aseprite
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
A fun game about smashing monsters in a dungeon.i
|
||||
A fun game about smashing monsters in a dungeon.
|
||||
|
||||
By Lephe' & Masséna
|
||||
|
||||
|
|
|
@ -43,3 +43,10 @@ swing_left.aseprite:
|
|||
center: 8, 9
|
||||
swing_down.aseprite:
|
||||
center: 9, 0
|
||||
|
||||
projectile_left.aseprite:
|
||||
center: 3, 2
|
||||
projectile_right.aseprite:
|
||||
center: 3, 2
|
||||
projectile_*.aseprite:
|
||||
next: Projectile=Projectile
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -35,6 +35,7 @@
|
|||
|
||||
/* Basic skills */
|
||||
ANIM1(skill_hit);
|
||||
ANIM2(skill_projectile);
|
||||
ANIM1(skill_teleport);
|
||||
ANIM1(skill_shock);
|
||||
ANIM1(skill_judgement);
|
||||
|
|
|
@ -64,6 +64,7 @@ void anim_state_update(anim_state_t *state, fixed_t dt);
|
|||
|
||||
/* Basic skills */
|
||||
extern anim_t anims_skill_hit;
|
||||
extern anim_t anims_skill_projectile;
|
||||
extern anim_t anims_skill_teleport;
|
||||
extern anim_t anims_skill_shock;
|
||||
extern anim_t anims_skill_judgement;
|
||||
|
|
54
src/aoe.c
54
src/aoe.c
|
@ -30,7 +30,7 @@ void aoe_destroy(entity_t *e)
|
|||
free(aoe->hits);
|
||||
}
|
||||
|
||||
entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing)
|
||||
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)
|
||||
|
@ -44,7 +44,6 @@ entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing)
|
|||
|
||||
rect hitbox = { 0 };
|
||||
fixed_t distance = fix(0.625);
|
||||
vec2 dir = fdir(facing);
|
||||
anim_t const *anim = NULL;
|
||||
bool rotate = true;
|
||||
fixed_t lifetime = fix(0);
|
||||
|
@ -54,6 +53,12 @@ entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing)
|
|||
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 };
|
||||
|
@ -84,8 +89,8 @@ entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing)
|
|||
|
||||
p->x += fmul(distance, dir.x);
|
||||
p->y += fmul(distance, dir.y);
|
||||
p->facing = facing;
|
||||
p->hitbox = rotate ? rect_rotate(hitbox, UP, facing) : hitbox;
|
||||
p->facing = frdir(dir);
|
||||
p->hitbox = rotate ? rect_rotate(hitbox, UP, p->facing) : hitbox;
|
||||
|
||||
v->sprite_plane = plane;
|
||||
|
||||
|
@ -98,7 +103,15 @@ entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing)
|
|||
|| type == AOE_IMPALE
|
||||
|| type == AOE_JUDGEMENT) {
|
||||
aoe->data.generic.strength = f->ATK;
|
||||
aoe->data.generic.dir = facing;
|
||||
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;
|
||||
|
@ -107,16 +120,24 @@ entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing)
|
|||
}
|
||||
else if(type == AOE_BULLET) {
|
||||
aoe->data.bullet.strength = f->ATK;
|
||||
aoe->data.bullet.dir = facing;
|
||||
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)
|
||||
{
|
||||
|
@ -161,6 +182,13 @@ static bool attack_apply(game_t *game, aoe_t *aoe, entity_t *target)
|
|||
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;
|
||||
|
@ -209,6 +237,7 @@ void aoe_apply(game_t *game, entity_t *entity, entity_t *e)
|
|||
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
|
||||
|
@ -238,7 +267,18 @@ 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_BULLET) {
|
||||
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);
|
||||
|
|
11
src/aoe.h
11
src/aoe.h
|
@ -11,6 +11,8 @@
|
|||
enum {
|
||||
/* Bare-hand fist/close-contact attack */
|
||||
AOE_HIT,
|
||||
/* Simple ranged attack */
|
||||
AOE_PROJECTILE,
|
||||
/* Normal attack by a slashing weapon */
|
||||
AOE_SLASH,
|
||||
/* Impaling attack using a slashing weapon */
|
||||
|
@ -51,9 +53,11 @@ typedef struct
|
|||
union {
|
||||
/* Generic attacks: source and ATK strength */
|
||||
struct { int strength; int dir; } generic;
|
||||
/* EFFECT_ATTACK_SHOCK: origin and ATK strength */
|
||||
/* AOE_PROJECTILE: speed and direction of movement */
|
||||
struct { int strength; vec2 direction; fixed_t speed; } projectile;
|
||||
/* AOE_SHOCK: origin and ATK strength */
|
||||
struct { int strength; vec2 origin; } shock;
|
||||
/* EFFECT_ATTACK_BULLET: Magic strengh, speed parameters */
|
||||
/* AOE_BULLET: magic strengh, speed parameters */
|
||||
struct { int strength; int dir; fixed_t v; fixed_t final_v; } bullet;
|
||||
} data;
|
||||
|
||||
|
@ -67,7 +71,8 @@ entity_t *aoe_make(uint16_t type, vec2 position, fixed_t lifetime);
|
|||
void aoe_destroy(entity_t *aoe);
|
||||
|
||||
/* Create an area for a particular attack; all attributes are initialized. */
|
||||
entity_t *aoe_make_attack(uint16_t type, entity_t *origin, int facing);
|
||||
entity_t *aoe_make_attack(uint16_t type, entity_t *origin, vec2 dir);
|
||||
entity_t *aoe_make_attack_4(uint16_t type, entity_t *origin, int facing);
|
||||
|
||||
/* Apply effect of area on entity */
|
||||
struct game;
|
||||
|
|
112
src/enemies.c
112
src/enemies.c
|
@ -3,6 +3,7 @@
|
|||
#include "comp/visible.h"
|
||||
#include "comp/mechanical.h"
|
||||
#include "comp/fighter.h"
|
||||
#include "aoe.h"
|
||||
#include "enemies.h"
|
||||
#include <string.h>
|
||||
|
||||
|
@ -91,7 +92,7 @@ static enemy_t const gunslinger_8 = {
|
|||
},
|
||||
.shadow_size = 4,
|
||||
.xp = 30,
|
||||
.z = fix(0.375),
|
||||
.z = fix(0.25),
|
||||
};
|
||||
|
||||
static enemy_t const * const enemies[] = {
|
||||
|
@ -149,3 +150,112 @@ entity_t *enemy_make(int enemy_id)
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "comp/entity.h"
|
||||
#include "comp/mechanical.h"
|
||||
#include "comp/fighter.h"
|
||||
#include "game.h"
|
||||
#include "geometry.h"
|
||||
#include "anim.h"
|
||||
|
||||
|
@ -48,3 +49,6 @@ enemy_t const *enemy_data(int enemy_id);
|
|||
|
||||
/* Create a new enemy of the given type. */
|
||||
entity_t *enemy_make(int enemy_id);
|
||||
|
||||
/* Run enemy AI for an entity */
|
||||
void enemy_ai(game_t *g, entity_t *e, fixed_t dt);
|
||||
|
|
54
src/main.c
54
src/main.c
|
@ -486,55 +486,15 @@ int main(void)
|
|||
entity_t *e = game.entities[i];
|
||||
fighter_t *f = getcomp(e, fighter);
|
||||
mechanical_t *m = getcomp(e, mechanical);
|
||||
physical_t *p = getcomp(e, physical);
|
||||
if(!f || !m || f->enemy_data == NULL || f->HP == 0)
|
||||
continue;
|
||||
|
||||
/* Go within 1 block of the player */
|
||||
vec2 direction = { 0, 0 };
|
||||
vec2 pos = physical_pos(e);
|
||||
|
||||
bool in_range = dist2(pos, physical_pos(player)) <= fix(1);
|
||||
|
||||
if(!in_range) {
|
||||
pfg_path_t path = pfg_bfs_inwards(&game.paths_to_player,
|
||||
vec_f2i(pos));
|
||||
if(path.points) {
|
||||
direction = pfc_shortcut_one(&path, pos,
|
||||
physical_pos(player), getcomp(e, physical)->hitbox);
|
||||
pfg_path_free(&path);
|
||||
}
|
||||
|
||||
if(direction.x && direction.y) {
|
||||
direction.x -= pos.x;
|
||||
direction.y -= pos.y;
|
||||
}
|
||||
}
|
||||
|
||||
mechanical_move(e, direction, dt, game.map);
|
||||
if(direction.x > 0) p->facing = RIGHT;
|
||||
else if(direction.x < 0) p->facing = LEFT;
|
||||
else if(p->x < player_p->x) p->facing = RIGHT;
|
||||
else p->facing = LEFT;
|
||||
enemy_ai(&game, e, dt);
|
||||
|
||||
if(mechanical_moving(e))
|
||||
visible_set_anim(e, f->enemy_data->anim_walking, 1);
|
||||
else
|
||||
visible_set_anim(e, f->enemy_data->anim_idle, 1);
|
||||
|
||||
if(in_range && !f->current_attack) {
|
||||
/* Enemy attack */
|
||||
int facing = frdir((vec2){
|
||||
player_p->x - p->x,
|
||||
player_p->y - p->y });
|
||||
|
||||
entity_t *aoe = aoe_make_attack(AOE_HIT, e, facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
f->current_attack = aoe;
|
||||
f->attack_follows_movement = true;
|
||||
visible_set_anim(e, f->enemy_data->anim_attack, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Player attack */
|
||||
|
@ -548,7 +508,7 @@ int main(void)
|
|||
|
||||
if(hit_number == 0) effect = AOE_SLASH;
|
||||
if(hit_number == 1) effect = AOE_IMPALE;
|
||||
entity_t *aoe = aoe_make_attack(effect, player, player_p->facing);
|
||||
entity_t *aoe = aoe_make_attack_4(effect, player,player_p->facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
visible_set_anim(player, &anims_player_Attack, 2);
|
||||
|
@ -562,7 +522,7 @@ int main(void)
|
|||
!keydown(KEY_VARS);
|
||||
|
||||
if(can_attack && keydown(KEY_F2)) {
|
||||
entity_t *aoe = aoe_make_attack(AOE_SHOCK, player,
|
||||
entity_t *aoe = aoe_make_attack_4(AOE_SHOCK, player,
|
||||
player_p->facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
|
@ -570,10 +530,10 @@ int main(void)
|
|||
player_f->current_attack = aoe;
|
||||
player_f->attack_follows_movement = true;
|
||||
|
||||
game_shake(&game, 9, fix(0.7));
|
||||
game_shake(&game, 5, fix(0.7));
|
||||
}
|
||||
if(can_attack && keydown(KEY_F3)) {
|
||||
entity_t *aoe = aoe_make_attack( AOE_JUDGEMENT, player,
|
||||
entity_t *aoe = aoe_make_attack_4( AOE_JUDGEMENT, player,
|
||||
player_p->facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
|
@ -581,10 +541,10 @@ int main(void)
|
|||
player_f->current_attack = aoe;
|
||||
player_f->attack_follows_movement = false;
|
||||
|
||||
game_shake(&game, 5, fix(1.3));
|
||||
game_shake(&game, 3, fix(1.3));
|
||||
}
|
||||
if(can_attack && keydown(KEY_F4)) {
|
||||
entity_t *aoe = aoe_make_attack(AOE_BULLET, player,
|
||||
entity_t *aoe = aoe_make_attack_4(AOE_BULLET, player,
|
||||
player_p->facing);
|
||||
game_add_entity(&game, aoe);
|
||||
|
||||
|
|
Loading…
Reference in New Issue