ranged attackers + more versatile AI setup

This commit is contained in:
Lephenixnoir 2022-02-03 17:39:57 +01:00
parent b04333ddda
commit 69baa8d319
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
12 changed files with 189 additions and 59 deletions

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -35,6 +35,7 @@
/* Basic skills */
ANIM1(skill_hit);
ANIM2(skill_projectile);
ANIM1(skill_teleport);
ANIM1(skill_shock);
ANIM1(skill_judgement);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);