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:
Andrei Alexeyev 2020-07-09 15:07:40 +03:00
parent b3d3e76d6a
commit f329a9f016
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
8 changed files with 101 additions and 36 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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