Extended enemy properties
ENEMY_IMMUNE is now deprecated. Spawning an enemy with ENEMY_IMMUNE hp sets up the new flags field to match old behavior. The hp value itself has (almost) no special meaning anymore.
This commit is contained in:
parent
b3d3e76d6a
commit
f329a9f016
8 changed files with 101 additions and 36 deletions
60
src/enemy.c
60
src/enemy.c
|
@ -34,14 +34,14 @@ static void ent_draw_enemy(EntityInterface *ent);
|
|||
static DamageResult ent_damage_enemy(EntityInterface *ienemy, const DamageInfo *dmg);
|
||||
|
||||
static void fix_pos0_visual(Enemy *e) {
|
||||
if(e->flags & EFLAG_NO_VISUAL_CORRECTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
double x = creal(e->pos0_visual);
|
||||
double y = cimag(e->pos0_visual);
|
||||
double ofs = 21;
|
||||
|
||||
if(e->hp == ENEMY_IMMUNE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(x <= 0 && x > -ofs) {
|
||||
x = -ofs;
|
||||
} else if(x >= VIEWPORT_W && x < VIEWPORT_W + ofs) {
|
||||
|
@ -95,6 +95,12 @@ Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule v
|
|||
e->hp = hp;
|
||||
e->alpha = 1.0;
|
||||
|
||||
e->flags = 0;
|
||||
|
||||
if(e->hp == _internal_ENEMY_IMMUNE) {
|
||||
e->flags |= EFLAGS_GHOST;
|
||||
}
|
||||
|
||||
e->logic_rule = logic_rule;
|
||||
e->visual_rule = visual_rule;
|
||||
|
||||
|
@ -103,6 +109,9 @@ Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule v
|
|||
e->args[2] = a3;
|
||||
e->args[3] = a4;
|
||||
|
||||
e->hurt_radius = 7;
|
||||
e->hit_radius = 30;
|
||||
|
||||
e->ent.draw_layer = LAYER_ENEMY;
|
||||
e->ent.draw_func = ent_draw_enemy;
|
||||
e->ent.damage_func = ent_damage_enemy;
|
||||
|
@ -156,7 +165,7 @@ static void enemy_death_effect(cmplx pos) {
|
|||
static void* _delete_enemy(ListAnchor *enemies, List* enemy, void *arg) {
|
||||
Enemy *e = (Enemy*)enemy;
|
||||
|
||||
if(e->hp <= 0 && e->hp != ENEMY_IMMUNE) {
|
||||
if(e->hp <= 0 && !(e->flags & EFLAG_NO_DEATH_EXPLOSION)) {
|
||||
play_sfx("enemydeath");
|
||||
enemy_death_effect(e->pos);
|
||||
|
||||
|
@ -184,9 +193,13 @@ void delete_enemies(EnemyList *enemies) {
|
|||
}
|
||||
|
||||
static cmplx enemy_visual_pos(Enemy *enemy) {
|
||||
if(enemy->flags & EFLAG_NO_VISUAL_CORRECTION) {
|
||||
return enemy->pos;
|
||||
}
|
||||
|
||||
double t = (global.frames - enemy->birthtime) / 30.0;
|
||||
|
||||
if(t >= 1 || enemy->hp == ENEMY_IMMUNE) {
|
||||
if(t >= 1) {
|
||||
return enemy->pos;
|
||||
}
|
||||
|
||||
|
@ -338,11 +351,11 @@ void Swirl(Enemy *e, int t, bool render) {
|
|||
}
|
||||
|
||||
bool enemy_is_vulnerable(Enemy *enemy) {
|
||||
if(enemy->hp <= ENEMY_IMMUNE) {
|
||||
return false;
|
||||
}
|
||||
return !(enemy->flags & EFLAG_INVULNERABLE);
|
||||
}
|
||||
|
||||
return true;
|
||||
bool enemy_is_targetable(Enemy *enemy) {
|
||||
return !(enemy->flags & EFLAG_NO_HIT);
|
||||
}
|
||||
|
||||
bool enemy_in_viewport(Enemy *enemy) {
|
||||
|
@ -356,7 +369,8 @@ bool enemy_in_viewport(Enemy *enemy) {
|
|||
}
|
||||
|
||||
void enemy_kill(Enemy *enemy) {
|
||||
enemy->hp = ENEMY_KILLED;
|
||||
enemy->flags |= EFLAG_KILLED | EFLAG_NO_HIT | EFLAG_NO_HURT | EFLAG_INVULNERABLE;
|
||||
enemy->hp = 0;
|
||||
}
|
||||
|
||||
void enemy_kill_all(EnemyList *enemies) {
|
||||
|
@ -375,7 +389,7 @@ static DamageResult ent_damage_enemy(EntityInterface *ienemy, const DamageInfo *
|
|||
enemy->hp -= dmg->amount;
|
||||
|
||||
if(enemy->hp <= 0) {
|
||||
enemy->hp = ENEMY_KILLED;
|
||||
enemy_kill(enemy);
|
||||
|
||||
if(dmg->type == DMG_PLAYER_DISCHARGE) {
|
||||
spawn_and_collect_items(enemy->pos, 1, ITEM_VOLTAGE, (int)imax(1, enemy->spawn_hp / 100));
|
||||
|
@ -391,11 +405,25 @@ static DamageResult ent_damage_enemy(EntityInterface *ienemy, const DamageInfo *
|
|||
return DMG_RESULT_OK;
|
||||
}
|
||||
|
||||
float enemy_get_hurt_radius(Enemy *enemy) {
|
||||
if(enemy->flags & EFLAG_NO_HURT || enemy->alpha < 1.0f) {
|
||||
return 0;
|
||||
} else {
|
||||
return enemy->hurt_radius;
|
||||
}
|
||||
}
|
||||
|
||||
static bool should_auto_kill(Enemy *enemy) {
|
||||
return
|
||||
(enemy->hp <= 0 && enemy->hp != _internal_ENEMY_IMMUNE) ||
|
||||
(!(enemy->flags & EFLAG_NO_AUTOKILL) && !enemy_in_viewport(enemy));
|
||||
}
|
||||
|
||||
void process_enemies(EnemyList *enemies) {
|
||||
for(Enemy *enemy = enemies->first, *next; enemy; enemy = next) {
|
||||
next = enemy->next;
|
||||
|
||||
if(enemy->hp == ENEMY_KILLED) {
|
||||
if(enemy->flags & EFLAG_KILLED) {
|
||||
enemy_call_logic_rule(enemy, EVENT_KILLED);
|
||||
delete_enemy(enemies, enemy);
|
||||
continue;
|
||||
|
@ -403,13 +431,15 @@ void process_enemies(EnemyList *enemies) {
|
|||
|
||||
int action = enemy_call_logic_rule(enemy, global.frames - enemy->birthtime);
|
||||
|
||||
if(enemy->hp > ENEMY_IMMUNE && enemy->alpha >= 1.0 && cabs(enemy->pos - global.plr.pos) < ENEMY_HURT_RADIUS) {
|
||||
float hurt_radius = enemy_get_hurt_radius(enemy);
|
||||
|
||||
if(hurt_radius > 0 && cabs(enemy->pos - global.plr.pos) < hurt_radius) {
|
||||
ent_damage(&global.plr.ent, &(DamageInfo) { .type = DMG_ENEMY_COLLISION });
|
||||
}
|
||||
|
||||
enemy->alpha = approach(enemy->alpha, 1.0, 1.0/60.0);
|
||||
|
||||
if((enemy->hp > ENEMY_IMMUNE && (!enemy_in_viewport(enemy) || enemy->hp <= 0)) || action == ACTION_DESTROY) {
|
||||
if(action == ACTION_DESTROY || should_auto_kill(enemy)) {
|
||||
delete_enemy(enemies, enemy);
|
||||
continue;
|
||||
}
|
||||
|
|
35
src/enemy.h
35
src/enemy.h
|
@ -28,15 +28,34 @@
|
|||
#define IF_ENEMY_DEBUG(...)
|
||||
#endif
|
||||
|
||||
#define ENEMY_HURT_RADIUS 7
|
||||
|
||||
typedef LIST_ANCHOR(Enemy) EnemyList;
|
||||
typedef int (*EnemyLogicRule)(Enemy*, int t);
|
||||
typedef void (*EnemyVisualRule)(Enemy*, int t, bool render);
|
||||
|
||||
typedef enum EnemyFlag {
|
||||
EFLAG_KILLED = (1 << 0), // is dead, pending removal (internal)
|
||||
EFLAG_NO_HIT = (1 << 1), // can't be hit by player
|
||||
EFLAG_NO_HURT = (1 << 2), // can't hurt player
|
||||
EFLAG_NO_AUTOKILL = (1 << 3), // no autokill when out of bounds
|
||||
EFLAG_NO_VISUAL_CORRECTION = (1 << 4), // disable the slide-in hack for enemies spawning at screen edges
|
||||
EFLAG_NO_DEATH_EXPLOSION = (1 << 5), // don't explode on death; currently also disables bonus voltage on kill
|
||||
EFLAG_INVULNERABLE = (1 << 6), // can't be damaged by player (but can be hit)
|
||||
EFLAG_IMPENETRABLE = (1 << 7), // penetrating shots can't pass through (e.g. Marisa's laser)
|
||||
|
||||
EFLAGS_GHOST =
|
||||
EFLAG_NO_HIT |
|
||||
EFLAG_NO_HURT |
|
||||
EFLAG_NO_AUTOKILL |
|
||||
EFLAG_NO_VISUAL_CORRECTION |
|
||||
EFLAG_NO_DEATH_EXPLOSION |
|
||||
EFLAG_INVULNERABLE |
|
||||
0,
|
||||
} EnemyFlag;
|
||||
|
||||
enum {
|
||||
ENEMY_IMMUNE = -9000,
|
||||
ENEMY_KILLED = -9002,
|
||||
_internal_ENEMY_IMMUNE = -9000,
|
||||
ENEMY_IMMUNE attr_deprecated("Set enemy flags explicitly") = _internal_ENEMY_IMMUNE,
|
||||
// ENEMY_KILLED = -9002,
|
||||
};
|
||||
|
||||
DEFINE_ENTITY_TYPE(Enemy, {
|
||||
|
@ -56,6 +75,8 @@ DEFINE_ENTITY_TYPE(Enemy, {
|
|||
CoEvent killed;
|
||||
} events;
|
||||
|
||||
EnemyFlag flags;
|
||||
|
||||
int birthtime;
|
||||
int dir;
|
||||
|
||||
|
@ -64,6 +85,9 @@ DEFINE_ENTITY_TYPE(Enemy, {
|
|||
|
||||
float alpha;
|
||||
|
||||
float hit_radius;
|
||||
float hurt_radius;
|
||||
|
||||
bool moving;
|
||||
|
||||
IF_ENEMY_DEBUG(
|
||||
|
@ -92,7 +116,10 @@ void delete_enemies(EnemyList *enemies);
|
|||
void process_enemies(EnemyList *enemies);
|
||||
|
||||
bool enemy_is_vulnerable(Enemy *enemy);
|
||||
bool enemy_is_targetable(Enemy *enemy);
|
||||
bool enemy_in_viewport(Enemy *enemy);
|
||||
float enemy_get_hurt_radius(Enemy *enemy);
|
||||
|
||||
void enemy_kill(Enemy *enemy);
|
||||
void enemy_kill_all(EnemyList *enemies);
|
||||
|
||||
|
|
|
@ -1704,7 +1704,7 @@ cmplx plrutil_homing_target(cmplx org, cmplx fallback) {
|
|||
}
|
||||
|
||||
for(Enemy *e = global.enemies.first; e; e = e->next) {
|
||||
if(e->hp == ENEMY_IMMUNE) {
|
||||
if(!enemy_is_targetable(e)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ static void trace_laser(MarisaALaser *laser, cmplx vel, real damage) {
|
|||
|
||||
struct enemy_col {
|
||||
Enemy *enemy;
|
||||
int original_hp;
|
||||
EnemyFlag original_flags;
|
||||
} enemy_collisions[64] = { 0 }; // 64 collisions ought to be enough for everyone
|
||||
|
||||
int num_enemy_collissions = 0;
|
||||
|
@ -105,31 +105,35 @@ static void trace_laser(MarisaALaser *laser, cmplx vel, real damage) {
|
|||
.layer = LAYER_PARTICLE_HIGH,
|
||||
);
|
||||
|
||||
col.fatal = false;
|
||||
|
||||
if(col.type == PCOL_ENTITY && col.entity->type == ENT_TYPE_ID(Enemy)) {
|
||||
assert(num_enemy_collissions < ARRAY_SIZE(enemy_collisions));
|
||||
if(num_enemy_collissions < ARRAY_SIZE(enemy_collisions)) {
|
||||
Enemy *e = ENT_CAST(col.entity, Enemy);
|
||||
c = enemy_collisions + num_enemy_collissions++;
|
||||
c->enemy = ENT_CAST(col.entity, Enemy);
|
||||
c->enemy = e;
|
||||
if(e->flags & EFLAG_IMPENETRABLE) {
|
||||
col.fatal = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
col_types &= ~col.type;
|
||||
}
|
||||
|
||||
col.fatal = false;
|
||||
}
|
||||
|
||||
apply_projectile_collision(&lproj, lproj.first, &col);
|
||||
|
||||
if(c) {
|
||||
c->original_hp = (ENT_CAST(col.entity, Enemy))->hp;
|
||||
(ENT_CAST(col.entity, Enemy))->hp = ENEMY_IMMUNE;
|
||||
c->original_flags = (ENT_CAST(col.entity, Enemy))->flags;
|
||||
(ENT_CAST(col.entity, Enemy))->flags |= EFLAG_NO_HIT;
|
||||
}
|
||||
}
|
||||
|
||||
assume(num_enemy_collissions < ARRAY_SIZE(enemy_collisions));
|
||||
|
||||
for(int i = 0; i < num_enemy_collissions; ++i) {
|
||||
enemy_collisions[i].enemy->hp = enemy_collisions[i].original_hp;
|
||||
enemy_collisions[i].enemy->flags = enemy_collisions[i].original_flags;
|
||||
}
|
||||
|
||||
laser->trace_hit.last = col.location;
|
||||
|
|
|
@ -398,7 +398,7 @@ void calc_projectile_collision(Projectile *p, ProjCollisionResult *out_col) {
|
|||
}
|
||||
} else if(p->type == PROJ_PLAYER) {
|
||||
for(Enemy *e = global.enemies.first; e; e = e->next) {
|
||||
if(e->hp != ENEMY_IMMUNE && cabs(e->pos - p->pos) < 30) {
|
||||
if(!(e->flags & EFLAG_NO_HIT) && cabs(e->pos - p->pos) < e->hit_radius) {
|
||||
out_col->type = PCOL_ENTITY;
|
||||
out_col->entity = &e->ent;
|
||||
out_col->fatal = !(p->flags & PFLAG_INDESTRUCTIBLE);
|
||||
|
|
|
@ -419,11 +419,13 @@ static void stage_draw_collision_areas(void) {
|
|||
}
|
||||
|
||||
for(Enemy *e = global.enemies.first; e; e = e->next) {
|
||||
if(e->hp > ENEMY_IMMUNE && e->alpha >= 1.0) {
|
||||
float hurt_radius = enemy_get_hurt_radius(e);
|
||||
|
||||
if(hurt_radius > 0) {
|
||||
r_draw_sprite(&(SpriteParams) {
|
||||
.sprite_ptr = &stagedraw.dummy,
|
||||
.pos = { creal(e->pos), cimag(e->pos) },
|
||||
.scale = { .x = ENEMY_HURT_RADIUS * 2, .y = ENEMY_HURT_RADIUS * 2 },
|
||||
.scale = { .x = hurt_radius * 2, .y = hurt_radius * 2 },
|
||||
.blend = BLEND_ALPHA,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ static int stage3_enterswirl(Enemy *e, int t) {
|
|||
}
|
||||
|
||||
AT(60) {
|
||||
e->hp = ENEMY_KILLED;
|
||||
enemy_kill(e);
|
||||
}
|
||||
|
||||
e->pos += e->args[0];
|
||||
|
@ -404,8 +404,9 @@ static int stage3_bigfairy(Enemy *e, int t) {
|
|||
create_enemy3c(e->pos, 900, Fairy, slave, e->pos - 70 - 50 * I, e->args[0], -1);
|
||||
}
|
||||
|
||||
AT(creal(e->args[1]))
|
||||
e->hp = ENEMY_KILLED;
|
||||
AT(creal(e->args[1])) {
|
||||
enemy_kill(e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1337,8 +1337,8 @@ static int kurumi_extra_fairy(Enemy *e, int t) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if(e->hp == ENEMY_IMMUNE && t > 50)
|
||||
e->hp = 500;
|
||||
if(e->flags & EFLAG_NO_AUTOKILL && t > 50)
|
||||
e->flags &= ~EFLAG_NO_AUTOKILL;
|
||||
|
||||
if(creal(e->args[0]-e->pos) != 0)
|
||||
e->moving = true;
|
||||
|
@ -1458,7 +1458,8 @@ void kurumi_extra(Boss *b, int time) {
|
|||
if(direction)
|
||||
pos = VIEWPORT_W-creal(pos)+I*cimag(pos);
|
||||
// immune so they don’t get killed while they are still offscreen.
|
||||
create_enemy3c(pos-300*(1-2*direction),ENEMY_IMMUNE,kurumi_extra_fairy_visual,kurumi_extra_fairy,pos,100+20*i+100*(1.1-0.05*global.diff)*I,direction);
|
||||
Enemy *fairy = create_enemy3c(pos-300*(1-2*direction),500,kurumi_extra_fairy_visual,kurumi_extra_fairy,pos,100+20*i+100*(1.1-0.05*global.diff)*I,direction);
|
||||
fairy->flags |= EFLAG_NO_AUTOKILL;
|
||||
}
|
||||
|
||||
// XXX: maybe add a special sound for this?
|
||||
|
|
Loading…
Reference in a new issue