enemy,enemy_classes: redesign draw callbacks

Allows to pass arbitrary data to draw callbacks.

Groundwork for alternative fairy spawn animations.
This commit is contained in:
Andrei Alexeyev 2023-05-07 08:17:29 +02:00
parent 907eb8ff4e
commit 57f6d3dfd8
No known key found for this signature in database
GPG key ID: 72D26128040B9690
7 changed files with 193 additions and 187 deletions

View file

@ -83,7 +83,7 @@ static inline void enemy_update(Enemy *e, int t) {
e->dir = creal(v) < 0;
}
Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule visual_rule) {
Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisual visual) {
if(IN_DRAW_CODE) {
log_fatal("Tried to spawn an enemy while in drawing code");
}
@ -97,9 +97,8 @@ Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule v
e->pos0_visual = pos;
e->spawn_hp = hp;
e->hp = hp;
e->alpha = 1.0;
e->flags = 0;
e->visual_rule = visual_rule;
e->visual = visual;
e->hurt_radius = 7;
e->hit_radius = 30;
@ -185,7 +184,7 @@ cmplx enemy_visual_pos(Enemy *enemy) {
return enemy->pos;
}
double t = (global.frames - enemy->birthtime) / 30.0;
real t = (global.frames - enemy->birthtime) / 30.0;
if(t >= 1) {
return enemy->pos;
@ -197,17 +196,17 @@ cmplx enemy_visual_pos(Enemy *enemy) {
return p;
}
static void call_visual_rule(Enemy *e, bool render) {
cmplx tmp = e->pos;
e->pos = enemy_visual_pos(e);
e->visual_rule(e, global.frames - e->birthtime, render);
e->pos = tmp;
static void draw_enemy(Enemy *e) {
e->visual.draw(e, (EnemyDrawParams) {
.time = global.frames - e->birthtime,
.pos = enemy_visual_pos(e),
});
}
static void ent_draw_enemy(EntityInterface *ent) {
Enemy *e = ENT_CAST(ent, Enemy);
if(!e->visual_rule) {
if(!e->visual.draw) {
return;
}
@ -216,7 +215,7 @@ static void ent_draw_enemy(EntityInterface *ent) {
memcpy(&prev_state, e, sizeof(Enemy));
#endif
call_visual_rule(e, true);
draw_enemy(e);
#ifdef ENEMY_DEBUG
if(memcmp(&prev_state, e, sizeof(Enemy))) {
@ -300,11 +299,7 @@ static DamageResult ent_damage_enemy(EntityInterface *ienemy, const DamageInfo *
}
float enemy_get_hurt_radius(Enemy *enemy) {
if(enemy->flags & EFLAG_NO_HURT || enemy->alpha < 1.0f) {
return 0;
} else {
return enemy->hurt_radius;
}
return (enemy->flags & EFLAG_NO_HURT) ? 0 : enemy->hurt_radius;
}
static bool should_auto_kill(Enemy *enemy) {
@ -331,16 +326,10 @@ void process_enemies(EnemyList *enemies) {
ent_damage(&global.plr.ent, &(DamageInfo) { .type = DMG_ENEMY_COLLISION });
}
enemy->alpha = approach(enemy->alpha, 1.0, 1.0/60.0);
if(should_auto_kill(enemy)) {
delete_enemy(enemies, enemy);
continue;
}
if(enemy->visual_rule) {
call_visual_rule(enemy, false);
}
}
}

View file

@ -28,7 +28,6 @@
#endif
typedef LIST_ANCHOR(Enemy) EnemyList;
typedef void (*EnemyVisualRule)(Enemy*, int t, bool render);
typedef enum EnemyFlag {
EFLAG_KILLED = (1 << 0), // is dead, pending removal (internal)
@ -50,12 +49,26 @@ typedef enum EnemyFlag {
0,
} EnemyFlag;
typedef struct EnemyDrawParams {
cmplx pos; // NOTE: subject to slide-in correction at screen edges
int time;
} EnemyDrawParams;
typedef void (*EnemyDrawFunc)(Enemy*, EnemyDrawParams);
typedef struct EnemyVisual {
EnemyDrawFunc draw;
void *drawdata;
} EnemyVisual;
#define ENEMY_NOVISUAL ((EnemyVisual) {})
DEFINE_ENTITY_TYPE(Enemy, {
cmplx pos;
cmplx pos0;
cmplx pos0_visual;
MoveParams move;
EnemyVisualRule visual_rule;
EnemyVisual visual;
COEVENTS_ARRAY(
predamage,
@ -95,8 +108,6 @@ DEFINE_ENTITY_TYPE(Enemy, {
float spawn_hp;
float hp;
float alpha;
float hit_radius;
float hurt_radius;
@ -109,15 +120,15 @@ DEFINE_ENTITY_TYPE(Enemy, {
)
});
Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisualRule draw_rule);
Enemy *create_enemy_p(EnemyList *enemies, cmplx pos, float hp, EnemyVisual visual);
#ifdef ENEMY_DEBUG
Enemy *_enemy_attach_dbginfo(Enemy *p, DebugInfo *dbg);
#define create_enemy_p(...) _enemy_attach_dbginfo(create_enemy_p(__VA_ARGS__), _DEBUG_INFO_PTR_)
#endif
#define create_enemy(pos, hp, draw_rule) \
create_enemy_p(&global.enemies, pos, hp, draw_rule)
#define create_enemy(pos, hp, visual) \
create_enemy_p(&global.enemies, pos, hp, visual)
void delete_enemy(EnemyList *enemies, Enemy* enemy);
void delete_enemies(EnemyList *enemies);

View file

@ -19,16 +19,52 @@
#define ECLASS_HP_HUGE_FAIRY 8000
#define ECLASS_HP_SUPER_FAIRY 28000
typedef struct BaseEnemyVisualParams {
union {
Sprite *spr;
Animation *ani;
};
} BaseEnemyVisualParams;
typedef struct FairyVisualParams {
BaseEnemyVisualParams base;
} FairyVisualParams;
SpriteParams ecls_anyfairy_sprite_params(
Enemy *fairy,
EnemyDrawParams draw_params,
SpriteParamsBuffer *out_spbuf
) {
FairyVisualParams *vp = fairy->visual.drawdata;
auto ani = vp->base.ani;
const char *seqname = !fairy->moving ? "main" : (fairy->dir ? "left" : "right");
Sprite *spr = animation_get_frame(ani, get_ani_sequence(ani, seqname), draw_params.time);
out_spbuf->color = *RGB(1, 1, 1);
return (SpriteParams) {
.color = &out_spbuf->color,
.sprite_ptr = spr,
.pos.as_cmplx = draw_params.pos,
};
}
static void anyfairy_draw(Enemy *e, EnemyDrawParams p) {
SpriteParamsBuffer spbuf;
SpriteParams sp = ecls_anyfairy_sprite_params(e, p, &spbuf);
r_draw_sprite(&sp);
}
TASK(fairy_circle, {
BoxedEnemy e;
Sprite *sprite;
Color color;
float spin_rate;
float scale_base;
float scale_osc_ampl;
float scale_osc_freq;
Color color;
}) {
Projectile *p = TASK_BIND(PARTICLE(
Projectile *circle = TASK_BIND(PARTICLE(
.sprite_ptr = ARGS.sprite,
.color = &ARGS.color,
.flags = PFLAG_NOMOVE | PFLAG_REQUIREDPARTICLE | PFLAG_MANUALANGLE | PFLAG_NOAUTOREMOVE,
@ -38,14 +74,14 @@ TASK(fairy_circle, {
float scale_osc_phase = 0.0f;
for(Enemy *e; (e = ENT_UNBOX(ARGS.e)); YIELD) {
p->pos = enemy_visual_pos(e);
p->opacity = e->alpha;
p->angle += ARGS.spin_rate;
p->scale = (ARGS.scale_base + ARGS.scale_osc_ampl * sinf(scale_osc_phase)) * (1.0f + I);
circle->pos = enemy_visual_pos(e);
circle->angle += ARGS.spin_rate;
float s = ARGS.scale_base + ARGS.scale_osc_ampl * sinf(scale_osc_phase);
circle->scale = CMPLXF(s, s);
scale_osc_phase += ARGS.scale_osc_freq;
}
kill_projectile(p);
kill_projectile(circle);
}
TASK(fairy_flame_emitter, {
@ -114,7 +150,6 @@ TASK(fairy_stardust_emitter, {
old_pos = e->pos;
if(!(t % period)) {
RNG_ARRAY(rng, 4);
cmplx pos = e->pos + vrng_sreal(rng[0]) * 12 + vrng_sreal(rng[1]) * 10 * I;
@ -134,58 +169,6 @@ TASK(fairy_stardust_emitter, {
}
}
static void draw_fairy(Enemy *e, int t, Animation *ani) {
const char *seqname = !e->moving ? "main" : (e->dir ? "left" : "right");
Sprite *spr = animation_get_frame(ani, get_ani_sequence(ani, seqname), t);
r_draw_sprite(&(SpriteParams) {
.color = RGBA_MUL_ALPHA(1, 1, 1, e->alpha),
.sprite_ptr = spr,
.pos.as_cmplx = e->pos,
});
}
static void visual_swirl(Enemy *e, int t, bool render) {
if(render) {
r_draw_sprite(&(SpriteParams) {
.color = RGBA_MUL_ALPHA(1, 1, 1, e->alpha),
.sprite = "enemy/swirl",
.pos.as_cmplx = e->pos,
.rotation.angle = t * 10 * DEG2RAD,
});
}
}
static void visual_fairy_blue(Enemy *e, int t, bool render) {
if(render) {
draw_fairy(e, t, res_anim("enemy/fairy_blue"));
}
}
static void visual_fairy_red(Enemy *e, int t, bool render) {
if(render) {
draw_fairy(e, t, res_anim("enemy/fairy_red"));
}
}
static void visual_big_fairy(Enemy *e, int t, bool render) {
if(render) {
draw_fairy(e, t, res_anim("enemy/bigfairy"));
}
}
static void visual_huge_fairy(Enemy *e, int t, bool render) {
if(render) {
draw_fairy(e, t, res_anim("enemy/hugefairy"));
}
}
static void visual_super_fairy(Enemy *e, int t, bool render) {
if(render) {
draw_fairy(e, t, res_anim("enemy/superfairy"));
}
}
TASK(enemy_drop_items, { BoxedEnemy e; ItemCounts items; }) {
// NOTE: Don't bind here! We need this task to outlive the enemy.
Enemy *e = NOT_NULL(ENT_UNBOX(ARGS.e));
@ -193,68 +176,116 @@ TASK(enemy_drop_items, { BoxedEnemy e; ItemCounts items; }) {
if(e->damage_info && DAMAGETYPE_IS_PLAYER(e->damage_info->type)) {
common_drop_items(e->pos, &ARGS.items);
}
// NOTE: Needed to keep drawdata alive for this frame (see _spawn below)
YIELD;
}
static Enemy *spawn(cmplx pos, const ItemCounts *item_drops, real hp, EnemyVisualRule visual) {
Enemy *e = create_enemy(pos, hp, visual);
static Enemy *_spawn(
cmplx pos, const ItemCounts *item_drops, real hp, EnemyDrawFunc draw, size_t drawdata_alloc
) {
assert(drawdata_alloc > 0);
Enemy *e = create_enemy(pos, hp, (EnemyVisual) { draw });
if(item_drops) {
INVOKE_TASK_WHEN(&e->events.killed, enemy_drop_items, ENT_BOX(e), *item_drops);
}
// NOTE: almost all enemies drop items, so we can abuse the itemdrop task's stack to hold onto
// the draw data buffer for us.
auto t = INVOKE_TASK_WHEN(&e->events.killed, enemy_drop_items, {
.e = ENT_BOX(e),
.items = LIKELY(item_drops) ? *item_drops : (ItemCounts) { }
});
e->visual.drawdata = cotask_malloc(t, drawdata_alloc);
return e;
}
// Spawn enemy; allocate and initialize its drawdata buffer
#define spawn(_pos, _items, _hp, _draw, ...) ({ \
auto _e = _spawn(_pos, _items, _hp, _draw, sizeof(__VA_ARGS__)); \
*(typeof(__VA_ARGS__)*)(_e->visual.drawdata) = __VA_ARGS__; \
_e; \
});
static void swirl_draw(Enemy *e, EnemyDrawParams p) {
BaseEnemyVisualParams *vp = e->visual.drawdata;
r_draw_sprite(&(SpriteParams) {
.color = RGB(1, 1 ,1),
.sprite_ptr = vp->spr,
.pos.as_cmplx = p.pos,
.rotation.angle = p.time * 10 * DEG2RAD,
});
}
Enemy *(espawn_swirl)(cmplx pos, const ItemCounts *item_drops) {
Enemy *e = spawn(pos, item_drops, ECLASS_HP_SWIRL, visual_swirl);
return spawn(pos, item_drops, ECLASS_HP_SWIRL, swirl_draw, (BaseEnemyVisualParams) {
.spr = res_sprite("enemy/swirl"),
});
}
static Enemy *spawn_fairy(cmplx pos, const ItemCounts *item_drops, real hp, Animation *ani) {
return spawn(pos, item_drops, hp, anyfairy_draw, (FairyVisualParams) {
.base.ani = ani,
});
}
static Enemy *espawn_fairy_weak(
cmplx pos, const ItemCounts *item_drops, Animation *fairy_ani, Sprite *circle_spr
) {
auto e = spawn_fairy(pos, item_drops, ECLASS_HP_FAIRY, fairy_ani);
INVOKE_TASK(fairy_circle, ENT_BOX(e),
.sprite = circle_spr,
.color = *RGB(1, 1, 1),
.spin_rate = 10 * DEG2RAD,
.scale_base = 0.8f,
.scale_osc_ampl = 1.0f / 6.0f,
.scale_osc_freq = 0.1f,
);
return e;
}
Enemy *(espawn_fairy_blue)(cmplx pos, const ItemCounts *item_drops) {
Enemy *e = spawn(pos, item_drops, ECLASS_HP_FAIRY, visual_fairy_blue);
INVOKE_TASK(fairy_circle, ENT_BOX(e),
.sprite = res_sprite("fairy_circle"),
.spin_rate = 10 * DEG2RAD,
.scale_base = 0.8f,
.scale_osc_ampl = 1.0f / 6.0f,
.scale_osc_freq = 0.1f,
.color = *RGB(1, 1, 1)
return espawn_fairy_weak(
pos, item_drops,
res_anim("enemy/fairy_blue"),
res_sprite("fairy_circle")
);
return e;
}
Enemy *(espawn_fairy_red)(cmplx pos, const ItemCounts *item_drops) {
Enemy *e = spawn(pos, item_drops, ECLASS_HP_FAIRY, visual_fairy_red);
return espawn_fairy_weak(
pos, item_drops,
res_anim("enemy/fairy_red"),
res_sprite("fairy_circle_red")
);
}
Enemy *(espawn_big_fairy)(cmplx pos, const ItemCounts *item_drops) {
auto e = spawn_fairy(pos, item_drops, ECLASS_HP_BIG_FAIRY, res_anim("enemy/bigfairy"));
INVOKE_TASK(fairy_circle, ENT_BOX(e),
.sprite = res_sprite("fairy_circle_red"),
.sprite = res_sprite("fairy_circle_big"),
.color = *RGB(1, 1, 1),
.spin_rate = 10 * DEG2RAD,
.scale_base = 0.8f,
.scale_osc_ampl = 1.0f / 6.0f,
.scale_osc_freq = 0.1f,
.color = *RGB(1, 1, 1)
);
return e;
}
Enemy *(espawn_big_fairy)(cmplx pos, const ItemCounts *item_drops) {
Enemy *e = spawn(pos, item_drops, ECLASS_HP_BIG_FAIRY, visual_big_fairy);
INVOKE_TASK(fairy_flame_emitter, ENT_BOX(e),
.period = 5,
.color = *RGBA(0.0, 0.2, 0.3, 0.0)
);
INVOKE_TASK(fairy_circle, ENT_BOX(e),
.sprite = res_sprite("fairy_circle_big"),
.spin_rate = 5 * DEG2RAD,
.scale_base = 0.75f,
.scale_osc_ampl = 0.15f,
.scale_osc_freq = 1.0f / 15.0f,
.color = *RGBA(1, 1, 1, 0.8)
);
return e;
}
Enemy *(espawn_huge_fairy)(cmplx pos, const ItemCounts *item_drops) {
Enemy *e = spawn(pos, item_drops, ECLASS_HP_HUGE_FAIRY, visual_huge_fairy);
auto e = spawn_fairy(pos, item_drops, ECLASS_HP_HUGE_FAIRY, res_anim("enemy/hugefairy"));
INVOKE_TASK(fairy_circle, ENT_BOX(e),
.sprite = res_sprite("fairy_circle_big"),
.color = *RGBA(1, 1, 1, 0.95),
.spin_rate = 5 * DEG2RAD,
.scale_base = 0.85f,
.scale_osc_ampl = 0.1f,
.scale_osc_freq = 1.0f / 15.0f,
);
INVOKE_TASK(fairy_flame_emitter, ENT_BOX(e),
.period = 6,
.color = *RGBA(0.0, 0.2, 0.3, 0.0)
@ -263,19 +294,19 @@ Enemy *(espawn_huge_fairy)(cmplx pos, const ItemCounts *item_drops) {
.period = 6,
.color = *RGBA(0.3, 0.0, 0.2, 0.0)
);
INVOKE_TASK(fairy_circle, ENT_BOX(e),
.sprite = res_sprite("fairy_circle_big"),
.spin_rate = 5 * DEG2RAD,
.scale_base = 0.85f,
.scale_osc_ampl = 0.1f,
.scale_osc_freq = 1.0f / 15.0f,
.color = *RGBA(1, 1, 1, 0.95)
);
return e;
}
Enemy *(espawn_super_fairy)(cmplx pos, const ItemCounts *item_drops) {
Enemy *e = spawn(pos, item_drops, ECLASS_HP_SUPER_FAIRY, visual_super_fairy);
auto e = spawn_fairy(pos, item_drops, ECLASS_HP_SUPER_FAIRY, res_anim("enemy/superfairy"));
INVOKE_TASK(fairy_circle, ENT_BOX(e),
.sprite = res_sprite("fairy_circle_big_and_mean"),
.color = *RGBA(1, 1, 1, 0.6),
.spin_rate = 5 * DEG2RAD,
.scale_base = 0.9f,
.scale_osc_ampl = 0.1f,
.scale_osc_freq = 1.0f / 15.0f,
);
INVOKE_TASK(fairy_flame_emitter, ENT_BOX(e),
.period = 5,
.color = *RGBA(0.2, 0.0, 0.3, 0.0)
@ -284,13 +315,5 @@ Enemy *(espawn_super_fairy)(cmplx pos, const ItemCounts *item_drops) {
.period = 15,
.color = *RGBA(0.0, 0.0, 0.0, 0.8)
);
INVOKE_TASK(fairy_circle, ENT_BOX(e),
.sprite = res_sprite("fairy_circle_big_and_mean"),
.spin_rate = 5 * DEG2RAD,
.scale_base = 0.9f,
.scale_osc_ampl = 0.1f,
.scale_osc_freq = 1.0f / 15.0f,
.color = *RGBA(1, 1, 1, 0.6)
);
return e;
}

View file

@ -39,3 +39,9 @@ Enemy *espawn_super_fairy(cmplx pos, const ItemCounts *item_drops);
#define espawn_big_fairy_box(...) ENT_BOX(espawn_big_fairy(__VA_ARGS__))
#define espawn_huge_fairy_box(...) ENT_BOX(espawn_huge_fairy(__VA_ARGS__))
#define espawn_super_fairy_box(...) ENT_BOX(espawn_super_fairy(__VA_ARGS__))
SpriteParams ecls_anyfairy_sprite_params(
Enemy *fairy,
EnemyDrawParams draw_params,
SpriteParamsBuffer *out_spbuf
);

View file

@ -20,21 +20,19 @@ TASK(spinner_bullet_redirect, { BoxedProjectile p; MoveParams move; }) {
p->move.velocity += ov;
}
static void amulet_visual(Enemy *e, int t, bool render) {
if(render) {
r_draw_sprite(&(SpriteParams) {
.color = RGBA(2, 1, 1, 0),
.sprite = "fairy_circle_big",
.pos.as_cmplx = e->pos,
.rotation.angle = t * 5 * DEG2RAD,
});
static void amulet_draw(Enemy *e, EnemyDrawParams p) {
r_draw_sprite(&(SpriteParams) {
.color = RGBA(2, 1, 1, 0),
.sprite = "fairy_circle_big",
.pos.as_cmplx = p.pos,
.rotation.angle = p.time * 5 * DEG2RAD,
});
r_draw_sprite(&(SpriteParams) {
.sprite = "enemy/swirl",
.pos.as_cmplx = e->pos,
.rotation.angle = t * -10 * DEG2RAD,
});
}
r_draw_sprite(&(SpriteParams) {
.sprite = "enemy/swirl",
.pos.as_cmplx = p.pos,
.rotation.angle = p.time * -10 * DEG2RAD,
});
}
TASK(amulet_fire_spinners, { BoxedEnemy core; BoxedProjectileArray *spinners; }) {
@ -77,7 +75,7 @@ TASK(amulet, {
MoveParams move;
CoEvent *death_event;
}) {
Enemy *core = create_enemy(ARGS.pos, 2000, amulet_visual);
Enemy *core = create_enemy(ARGS.pos, 2000, (EnemyVisual) { amulet_draw });
core->hurt_radius = 18;
core->hit_radius = 36;
core->flags |= EFLAG_NO_VISUAL_CORRECTION;

View file

@ -16,11 +16,7 @@
// TODO SUPER REDESIGN THIS, IT'S A MESS!
static void kurumi_extra_shield_visual(Enemy *e, int time, bool render) {
if(!render) {
return;
}
static void kurumi_extra_shield_draw(Enemy *e, EnemyDrawParams p) {
// TODO: something nicer here
float h = clampf(e->hp / e->spawn_hp, 0, 1);
@ -29,37 +25,20 @@ static void kurumi_extra_shield_visual(Enemy *e, int time, bool render) {
r_draw_sprite(&(SpriteParams) {
.color = RGBA_MUL_ALPHA(
1 + (1 - h), 0.3 + 0.7 * h, 0.2 + 0.8 * h,
e->alpha * (1 + 1 - h)
1 + 1 - h
),
.sprite = "enemy/swirl",
.pos.as_cmplx = e->pos,
.rotation.angle = time * -10 * DEG2RAD,
.pos.as_cmplx = p.pos,
.rotation.angle = p.time * -10 * DEG2RAD,
.shader = "sprite_negative",
});
}
static void draw_negative_fairy(Enemy *e, int t, Animation *ani) {
const char *seqname = !e->moving ? "main" : (e->dir ? "left" : "right");
Sprite *spr = animation_get_frame(ani, get_ani_sequence(ani, seqname), t);
r_draw_sprite(&(SpriteParams) {
.shader = "sprite_negative",
.color = RGBA_MUL_ALPHA(1, 1, 1, e->alpha),
.sprite_ptr = spr,
.pos.as_cmplx = e->pos,
});
}
static void kurumi_extra_fairy_visual(Enemy *e, int time, bool render) {
if(render) {
draw_negative_fairy(e, time, res_anim("enemy/fairy_blue"));
}
}
static void kurumi_extra_bigfairy_visual(Enemy *e, int time, bool render) {
if(render) {
draw_negative_fairy(e, time, res_anim("enemy/superfairy"));
}
static void draw_negative_fairy(Enemy *e, EnemyDrawParams p) {
SpriteParamsBuffer spbuf;
SpriteParams sp = ecls_anyfairy_sprite_params(e, p, &spbuf);
sp.shader_ptr = res_shader("sprite_negative");
r_draw_sprite(&sp);
}
TASK(kurumi_vladsarmy_shield_death_proj, { cmplx pos; MoveParams move; }) {
@ -119,7 +98,7 @@ TASK(kurumi_vladsarmy_shield, { BoxedBoss boss; real angle; }) {
int hp = 1500;
Boss *b = NOT_NULL(ENT_UNBOX(ARGS.boss));
Enemy *e = create_enemy(b->pos, hp, kurumi_extra_shield_visual);
Enemy *e = create_enemy(b->pos, hp, (EnemyVisual) { kurumi_extra_shield_draw });
e->flags = EFLAG_IMPENETRABLE;
int timeout = 800;
@ -135,7 +114,7 @@ TASK(kurumi_vladsarmy_shield, { BoxedBoss boss; real angle; }) {
TASK(kurumi_vladsarmy_bigfairy, { cmplx pos; cmplx target; }) {
Enemy *e = TASK_BIND(espawn_big_fairy(ARGS.pos, ITEMS(.points = 2, .power = 1)));
e->visual_rule = kurumi_extra_bigfairy_visual;
e->visual.draw = draw_negative_fairy;
int escapetime = difficulty_value(400, 400, 400, 4000);
@ -240,7 +219,7 @@ TASK(kurumi_vladsarmy_drainer, { BoxedBoss boss; BoxedEnemy enemy; }) {
TASK(kurumi_vladsarmy_fairy, { cmplx start_pos; cmplx target_pos; int attack_time; int chase_time; int attack_type; BoxedBoss boss; }){
Enemy *e = TASK_BIND(espawn_fairy_blue(ARGS.start_pos, ITEMS(.points = 1)));
e->visual_rule = kurumi_extra_fairy_visual;
e->visual.draw = draw_negative_fairy;
e->flags |= EFLAG_NO_AUTOKILL;
e->move = move_from_towards(e->pos, ARGS.target_pos, 0.1);

View file

@ -78,7 +78,7 @@ TASK(lightning_slave_move, { BoxedEnemy e; cmplx velocity; }) {
}
TASK(lightning_slave, { cmplx pos; cmplx move_arg; }) {
Enemy *e = TASK_BIND(create_enemy(ARGS.pos, 1, NULL));
Enemy *e = TASK_BIND(create_enemy(ARGS.pos, 1, ENEMY_NOVISUAL));
e->flags = EFLAGS_GHOST;
INVOKE_TASK(lightning_slave_move, ENT_BOX(e), ARGS.move_arg);