diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ac93d2..1b520a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/README.md b/README.md index 805dfdd..b4dcda2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/assets-cg/skills/fxconv-metadata.txt b/assets-cg/skills/fxconv-metadata.txt index c5bee0a..42bb604 100644 --- a/assets-cg/skills/fxconv-metadata.txt +++ b/assets-cg/skills/fxconv-metadata.txt @@ -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 diff --git a/assets-cg/skills/projectile_left.aseprite b/assets-cg/skills/projectile_left.aseprite new file mode 100644 index 0000000..7be3126 Binary files /dev/null and b/assets-cg/skills/projectile_left.aseprite differ diff --git a/assets-cg/skills/projectile_right.aseprite b/assets-cg/skills/projectile_right.aseprite new file mode 100644 index 0000000..b6e4be6 Binary files /dev/null and b/assets-cg/skills/projectile_right.aseprite differ diff --git a/src/anim.c b/src/anim.c index f26a08f..49d9003 100644 --- a/src/anim.c +++ b/src/anim.c @@ -35,6 +35,7 @@ /* Basic skills */ ANIM1(skill_hit); +ANIM2(skill_projectile); ANIM1(skill_teleport); ANIM1(skill_shock); ANIM1(skill_judgement); diff --git a/src/anim.h b/src/anim.h index 6937a54..6fc6ef9 100644 --- a/src/anim.h +++ b/src/anim.h @@ -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; diff --git a/src/aoe.c b/src/aoe.c index 4ccfe0f..426aefd 100644 --- a/src/aoe.c +++ b/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); diff --git a/src/aoe.h b/src/aoe.h index 13c8c8c..6c81ca2 100644 --- a/src/aoe.h +++ b/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; diff --git a/src/enemies.c b/src/enemies.c index 459cf84..b8ce9ed 100644 --- a/src/enemies.c +++ b/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 @@ -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); + } +} diff --git a/src/enemies.h b/src/enemies.h index 8e3988f..8c1baa2 100644 --- a/src/enemies.h +++ b/src/enemies.h @@ -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); diff --git a/src/main.c b/src/main.c index a9f5d68..48b4c2c 100644 --- a/src/main.c +++ b/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);