stage4: port to coroutine system (WIP)

Merges #285

Mostly functional, but needs a design iteration.

Squashed commit of the following:

commit 792e817f77
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Jan 31 05:46:51 2023 +0100

    stage4: fix warning

commit eefa6a5181
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Jan 31 05:41:32 2023 +0100

    stage4: vlad's army "fixes"

commit 7ac97455ef
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Jan 31 05:40:48 2023 +0100

    shader/sprite_negative: modulate color

commit 9a7121e329
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Jan 31 04:44:13 2023 +0100

    stage4: fix kurumi's boss nons to soft-reset position

commit a26c85c5bd
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Jan 31 04:35:47 2023 +0100

    stage4: use super fairy for supercard

commit 6453352555
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Jan 31 04:33:20 2023 +0100

    stage4: fix scythe

commit 9e0743573d
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Jan 31 04:32:47 2023 +0100

    stage4: use huge fairies for splashers

commit e36f3ab154
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Jan 31 03:30:21 2023 +0100

    stage4: formatting fixups

commit 11b2071790
Author: laochailan <laochailan@web.de>
Date:   Sat Sep 25 11:55:00 2021 +0200

    fix Alice D.’s comments and some other loose ends

commit 0e2bc7af1f
Author: laochailan <laochailan@web.de>
Date:   Sat Sep 25 11:03:33 2021 +0200

    stage4: remove unused constant

commit 1343a55141
Author: laochailan <laochailan@web.de>
Date:   Sat Sep 25 11:02:54 2021 +0200

    stage4: fix explosive_swirl crash

commit c5e571707c
Author: laochailan <laochailan@web.de>
Date:   Sat Sep 25 10:54:12 2021 +0200

    update to latest task api

commit e7202f9492
Author: laochailan <laochailan@web.de>
Date:   Sun May 2 10:08:51 2021 +0200

    attempt to fix scythe timing

commit 9abe4b4274
Author: laochailan <laochailan@web.de>
Date:   Sat May 1 21:04:23 2021 +0200

    modernize vlads army except draw rules

commit daf2b18e3d
Author: laochailan <laochailan@web.de>
Date:   Sat May 1 08:53:28 2021 +0200

    modernize vampiric vapor

commit 7a5ea6afc7
Author: laochailan <laochailan@web.de>
Date:   Sat May 1 07:42:43 2021 +0200

    rename a function

commit e76888cafe
Author: laochailan <laochailan@web.de>
Date:   Fri Apr 30 21:10:20 2021 +0200

    modernize blow the walls

commit d3cf94f20a
Author: laochailan <laochailan@web.de>
Date:   Fri Apr 30 08:33:37 2021 +0200

    remove the enemy from aniwall

commit 5604af5450
Author: laochailan <laochailan@web.de>
Date:   Thu Apr 29 21:41:30 2021 +0200

    animate wall modernized

    todo: remove the enemy out of it

commit d89c1d12b4
Author: laochailan <laochailan@web.de>
Date:   Wed Apr 28 20:55:57 2021 +0200

    modernize red spike

commit d963c50d2e
Author: laochailan <laochailan@web.de>
Date:   Wed Apr 28 19:35:07 2021 +0200

    modernize gate_of_walachia

commit 98b592ab80
Author: laochailan <laochailan@web.de>
Date:   Tue Apr 27 18:47:41 2021 +0200

    modernize kurumi nonspells

commit 109198fbb2
Author: laochailan <laochailan@web.de>
Date:   Mon Apr 26 17:47:47 2021 +0200

    modernize scythe (as a fairy)

commit b4b2b3d0e3
Author: laochailan <laochailan@web.de>
Date:   Sun Apr 25 22:03:22 2021 +0200

    modernize bigcircle_fairy

commit 4d1fa425b3
Author: laochailan <laochailan@web.de>
Date:   Sun Apr 25 21:44:10 2021 +0200

    modernize supercard fairy

commit 3214c1ab21
Author: laochailan <laochailan@web.de>
Date:   Sun Apr 25 19:19:08 2021 +0200

    modernize explosive_swirl

commit 8674f5d259
Author: laochailan <laochailan@web.de>
Date:   Sun Apr 25 17:46:33 2021 +0200

    modernize backfire_swirl

commit 645d56ac7d
Author: laochailan <laochailan@web.de>
Date:   Sun Apr 25 14:32:38 2021 +0200

    modernize cardbuster fairy

commit 2e641f6205
Author: laochailan <laochailan@web.de>
Date:   Sun Apr 25 12:26:04 2021 +0200

    modernize partcircle fairy

commit 46dd0dedca
Author: laochailan <laochailan@web.de>
Date:   Sun Apr 25 11:45:49 2021 +0200

    modernize fodder_fairy

commit 706ca100bf
Author: Alice D <alice@starwitch.productions>
Date:   Thu Apr 15 20:10:42 2021 -0400

    rebase

commit f1cf5b2f6d
Author: Alice D <alice@starwitch.productions>
Date:   Sun Apr 11 17:17:32 2021 -0400

    begin porting stage 4 - splasher fairies ported

Co-authored-by: Andrei Alexeyev <akari@taisei-project.org>
Co-authored-by: Alice D <alice@starwitch.productions>
This commit is contained in:
Lukas Weber 2023-01-31 05:48:56 +01:00 committed by Andrei Alexeyev
parent e580657f5b
commit 93c68dbb0d
No known key found for this signature in database
GPG key ID: 72D26128040B9690
16 changed files with 1172 additions and 1244 deletions

View file

@ -4,5 +4,5 @@
void spriteMain(out vec4 fragColor) {
vec4 texel = texture(tex, texCoord);
fragColor = vec4((1.0 - texel.rgb / max(0.01, texel.a)) * texel.a, 0);
fragColor = color * vec4((1.0 - texel.rgb / max(0.01, texel.a)) * texel.a, 0);
}

View file

@ -12,7 +12,33 @@
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
DEFINE_EXTERN_TASK(stage4_boss_nonspell_burst) {
Boss *b = TASK_BIND(ARGS.boss);
int count = ARGS.count;
for(int i = 0; i < ARGS.duration; i += WAIT(100)) {
play_sfx("shot_special1");
aniplayer_queue(&b->ani, "muda", 4);
aniplayer_queue(&b->ani, "main", 0);
for(int j = 0; j < count; j++) {
PROJECTILE(
.proto = pp_bigball,
.pos = b->pos,
.color = RGBA(0.5, 0.0, 0.5, 0.0),
.move = move_asymptotic_simple(cdir(M_TAU / count * j), 3),
);
}
}
}
DEFINE_EXTERN_TASK(stage4_boss_nonspell_redirect) {
Projectile *p = TASK_BIND(ARGS.proj);
p->move = ARGS.new_move;
p->color.b *= -1;
play_sfx_ex("redirect", 10, false);
spawn_projectile_highlight_effect(p);
}
static void kurumi_global_rule(Boss *b, int time) {
// FIXME: avoid running this every frame!
@ -32,40 +58,19 @@ Boss *stage4_spawn_kurumi(cmplx pos) {
return b;
}
void kurumi_slave_visual(Enemy *e, int t, bool render) {
if(render) {
return;
}
if(!(t%2)) {
cmplx offset = (frand()-0.5)*30;
offset += (frand()-0.5)*20.0*I;
DEFINE_EXTERN_TASK(stage4_boss_slave_visual) {
for(;;) {
PARTICLE(
.sprite = "smoothdot",
.pos = offset,
.sprite = "stain",
.pos = *ARGS.pos,
.color = RGBA(0.3, 0.0, 0.0, 0.0),
.draw_rule = Shrink,
.rule = enemy_flare,
.timeout = 50,
.args = { (-50.0*I-offset)/50.0, add_ref(e) },
.draw_rule = pdraw_timeout_fade(1, 0),
.angle = rng_angle(),
.scale = 0.4,
.timeout = 30,
.flags = PFLAG_REQUIREDPARTICLE,
);
}
}
void kurumi_slave_static_visual(Enemy *e, int t, bool render) {
if(render) {
return;
}
if(e->args[1]) {
PARTICLE(
.sprite = "smoothdot",
.pos = e->pos,
.color = RGBA(1, 1, 1, 0),
.draw_rule = Fade,
.timeout = 30,
);
WAIT(ARGS.interval);
}
}
@ -73,27 +78,13 @@ void kurumi_spell_bg(Boss *b, int time) {
float f = 0.5+0.5*sin(time/80.0);
r_mat_mv_push();
r_mat_mv_translate(VIEWPORT_W/2, VIEWPORT_H/2,0);
r_mat_mv_translate(VIEWPORT_W / 2.0, VIEWPORT_H / 2.0, 0);
r_mat_mv_scale(0.6, 0.6, 1);
r_color3(f, 1 - f, 1 - f);
draw_sprite(0, 0, "stage4/kurumibg1");
r_mat_mv_pop();
r_color4(1, 1, 1, 0);
fill_viewport(time/300.0, time/300.0, 0.5, "stage4/kurumibg2");
fill_viewport(time / 300.0, time / 300.0, 0.5, "stage4/kurumibg2");
r_color4(1, 1, 1, 1);
}
int kurumi_splitcard(Projectile *p, int t) {
if(t < 0) {
return ACTION_ACK;
}
if(t == creal(p->args[2])) {
p->args[0] += p->args[3];
p->color.b *= -1;
play_sound_ex("redirect", 10, false);
spawn_projectile_highlight_effect(p);
}
return asymptotic(p, t);
}

View file

@ -11,8 +11,12 @@
#include "boss.h"
DECLARE_EXTERN_TASK(stage4_boss_nonspell_burst, { BoxedBoss boss; int duration; int count; });
DECLARE_EXTERN_TASK(stage4_boss_nonspell_redirect, { BoxedProjectile proj; MoveParams new_move; });
DECLARE_EXTERN_TASK(stage4_boss_slave_visual, { cmplx *pos; int interval; });
Boss *stage4_spawn_kurumi(cmplx pos);
void kurumi_slave_visual(Enemy *e, int t, bool render);
void kurumi_slave_static_visual(Enemy *e, int t, bool render);
int kurumi_splitcard(Projectile *p, int t);
void kurumi_spell_bg(Boss*, int);

View file

@ -13,50 +13,44 @@
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
DEFINE_EXTERN_TASK(stage4_boss_nonspell_1) {
STAGE_BOOKMARK(boss-non1);
Boss *b = INIT_BOSS_ATTACK(&ARGS);
b->move = move_towards(VIEWPORT_W/2 + 200*I, 0.01);
BEGIN_BOSS_ATTACK(&ARGS);
void kurumi_sbreaker(Boss *b, int time) {
if(time < 0)
return;
int duration = difficulty_value(300, 350, 400, 450);
int count = difficulty_value(12, 14, 16, 18);
int redirect_time = 40;
int dur = 300+50*global.diff;
int t = time % dur;
int i;
TIMER(&t);
for(;;) {
int c = 10+global.diff*2;
int kt = 40;
FROM_TO_SND("shot1_loop", 50, dur, 2+(global.diff < D_Hard)) {
cmplx p = b->pos + 150*sin(_i/8.0)+100.0*I*cos(_i/15.0);
cmplx n = cexp(2.0*I*M_PI/c*_i);
PROJECTILE(
.proto = pp_rice,
.pos = p,
.color = RGB(1.0, 0.0, 0.5),
.rule = kurumi_splitcard,
.args = {
2*n,
0,
kt,
1.5*cexp(I*carg(global.plr.pos - p - 2*kt*n))-1.7*n
}
int rice_step = difficulty_value(3, 3, 2, 2);
WAIT(50);
INVOKE_SUBTASK_DELAYED(10, stage4_boss_nonspell_burst,
.boss = ENT_BOX(b),
.duration = duration-10,
.count = 20
);
}
FROM_TO(60, dur, 100) {
play_sfx("shot_special1");
aniplayer_queue(&b->ani, "muda", 4);
aniplayer_queue(&b->ani, "main", 0);
for(int i = 0; i < duration/rice_step; i++, WAIT(rice_step)) {
play_sfx_loop("shot1_loop");
cmplx spawn_pos = b->pos + 150 * sin(i / 8.0) + I * 100.0 * cos(i / 15.0);
for(i = 0; i < 20; i++) {
PROJECTILE(
.proto = pp_bigball,
.pos = b->pos,
.color = RGBA(0.5, 0.0, 0.5, 0.0),
.rule = asymptotic,
.args = { cexp(2.0*I*M_PI/20.0*i), 3 },
cmplx dir = cdir(M_TAU / count * i);
cmplx redirect_vel = 1.5 * cnormalize(global.plr.pos - spawn_pos - 2 * redirect_time * dir) + 0.3 * dir;
Projectile *p = PROJECTILE(
.proto = pp_rice,
.pos = spawn_pos,
.color = RGB(1.0, 0.0, 0.5),
.move = move_linear(2 * dir),
);
INVOKE_TASK_DELAYED(redirect_time, stage4_boss_nonspell_redirect,
.proj = ENT_BOX(p),
.new_move = move_asymptotic_simple(redirect_vel, 3)
);
}
}

View file

@ -13,53 +13,53 @@
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
DEFINE_EXTERN_TASK(stage4_boss_nonspell_2) {
STAGE_BOOKMARK(boss-non2);
Boss *b = INIT_BOSS_ATTACK(&ARGS);
b->move = move_towards(VIEWPORT_W/2 + 200*I, 0.01);
BEGIN_BOSS_ATTACK(&ARGS);
void kurumi_breaker(Boss *b, int time) {
int t = time % 400;
int i;
int duration = 350;
int c = 10+global.diff*2;
int kt = 20;
int count = difficulty_value(10, 12, 14, 16);
int rice_step = difficulty_value(43, 36, 29, 22);
int redirect_time = 20;
if(time < 0)
return;
int time = 0;
for(;;) {
WAIT(50);
GO_TO(b, VIEWPORT_W/2 + VIEWPORT_W/3*sin(time/220) + I*cimag(b->pos), 0.02);
INVOKE_SUBTASK_DELAYED(10, stage4_boss_nonspell_burst,
.boss = ENT_BOX(b),
.duration = duration-10,
.count = 20
);
TIMER(&t);
for(int i = 0; i < duration/rice_step; i++, WAIT(rice_step)) {
cmplx boss_target = VIEWPORT_W / 2.0 + VIEWPORT_W / 3.0 * sin(time / 220.0) + I*cimag(b->pos);
FROM_TO_SND("shot1_loop", 50, 400, 50-7*global.diff) {
cmplx p = b->pos + 150*sin(_i) + 100.0*I*cos(_i);
b->move = move_towards(boss_target, 0.02);
for(i = 0; i < c; i++) {
cmplx n = cexp(2.0*I*M_PI/c*i);
PROJECTILE(
.proto = pp_rice,
.pos = p,
.color = RGB(1,0,0.5),
.rule = kurumi_splitcard,
.args = {
3*n,
0,
kt,
1.5*cexp(I*carg(global.plr.pos - p - 2*kt*n))-2.6*n
});
}
}
play_sfx("shot1");
cmplx spawn_pos = b->pos + 150 * sin(i) + 100.0 * I * cos(i);
FROM_TO(60, 400, 100) {
play_sfx("shot_special1");
aniplayer_queue(&b->ani,"muda",1);
aniplayer_queue(&b->ani,"main",0);
for(i = 0; i < 20; i++) {
PROJECTILE(
.proto = pp_bigball,
.pos = b->pos,
.color = RGBA(0.5, 0.0, 0.5, 0.0),
.rule = asymptotic,
.args = { cexp(2.0*I*M_PI/20.0*i), 3 },
);
for(int j = 0; j < count; j++) {
cmplx dir = cdir(M_TAU / count * j);
Projectile *p = PROJECTILE(
.proto = pp_rice,
.pos = spawn_pos,
.color = RGB(1,0,0.5),
.move = move_linear(3 * dir)
);
cmplx redirect_vel = 1.5 * cnormalize(global.plr.pos - spawn_pos - 2 * redirect_time * dir) + 0.4 * dir;
INVOKE_TASK_DELAYED(redirect_time, stage4_boss_nonspell_redirect,
.proj = ENT_BOX(p),
.new_move = move_asymptotic_simple(redirect_vel, 3)
);
}
time++;
}
}
}

View file

@ -11,5 +11,5 @@
#include "boss.h"
void kurumi_sbreaker(Boss *b, int time);
void kurumi_breaker(Boss *b, int time);
DECLARE_EXTERN_TASK_WITH_INTERFACE(stage4_boss_nonspell_1, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(stage4_boss_nonspell_2, BossAttack);

View file

@ -9,104 +9,112 @@
#include "taisei.h"
#include "spells.h"
#include "common_tasks.h"
#include "../kurumi.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
typedef struct {
cmplx pos;
MoveParams move;
} Mover; // make this global?
static int aniwall_bullet(Projectile *p, int t) {
if(t < 0) {
return ACTION_ACK;
TASK(mover_update, { Mover *m; }) {
for(;;) {
move_update(&ARGS.m->pos, &ARGS.m->move);
YIELD;
}
if(t > creal(p->args[1])) {
if(global.diff > D_Normal) {
tsrand_fill(2);
p->args[0] += 0.1*(0.1-0.2*afrand(0) + 0.1*I-0.2*I*afrand(1))*(global.diff-2);
p->args[0] += 0.002*cexp(I*carg(global.plr.pos - p->pos));
}
p->pos += p->args[0];
}
p->color.r = cimag(p->pos)/VIEWPORT_H;
return ACTION_NONE;
}
static int aniwall_slave(Enemy *e, int t) {
float re, im;
if(t < 0)
return 1;
TASK(kurumi_aniwall_bullet, { cmplx pos; MoveParams move; }) {
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_ball,
.pos = ARGS.pos,
.color = RGB(1,0,0),
.move = ARGS.move,
));
if(creal(e->pos) <= 0)
e->pos = I*cimag(e->pos);
if(creal(e->pos) >= VIEWPORT_W)
e->pos = VIEWPORT_W + I*cimag(e->pos);
if(cimag(e->pos) <= 0)
e->pos = creal(e->pos);
if(cimag(e->pos) >= VIEWPORT_H)
e->pos = creal(e->pos) + I*VIEWPORT_H;
for(;;) {
p->color.r = cimag(p->pos)/VIEWPORT_H;
YIELD;
}
}
re = creal(e->pos);
im = cimag(e->pos);
TASK(kurumi_aniwall_slave_move, { Mover *m; cmplx direction; }) {
if(cabs(e->args[1]) <= 0.1) {
if(re == 0 || re == VIEWPORT_W) {
cmplx corners[] = {
0,
VIEWPORT_W,
VIEWPORT_W + I * VIEWPORT_H,
I * VIEWPORT_H,
};
e->args[1] = 1;
e->args[2] = 10.0*I;
}
real speed = 10;
e->pos += e->args[0]*t;
int order;
int next;
if(creal(ARGS.direction) > 0) {
next = 2;
order = 1;
} else {
if((re <= 0) + (im <= 0) + (re >= VIEWPORT_W) + (im >= VIEWPORT_H) == 2) {
float sign = 1;
sign *= 1-2*(re > 0);
sign *= 1-2*(im > 0);
sign *= 1-2*(cimag(e->args[2]) == 0);
e->args[2] *= sign*I;
}
e->pos += e->args[2];
if(!(t % 7-global.diff-2*(global.diff > D_Normal))) {
cmplx v = e->args[2]/cabs(e->args[2])*I*sign(creal(e->args[0]));
if(cimag(v) > -0.1 || global.diff >= D_Normal) {
play_sound("shot1");
PROJECTILE(
.proto = pp_ball,
.pos = e->pos+I*v*20*nfrand(),
.color = RGB(1,0,0),
.rule = aniwall_bullet,
.args = { 1*v, 40 }
);
}
}
next = 3;
order = -1;
}
return 1;
}
for(;; next = (next + order + ARRAY_SIZE(corners)) % ARRAY_SIZE(corners)) {
cmplx d = corners[next] - ARGS.m->pos;
ARGS.m->move = move_linear(speed * cnormalize(d));
void kurumi_aniwall(Boss *b, int time) {
TIMER(&time);
AT(EVENT_DEATH) {
enemy_kill_all(&global.enemies);
}
GO_TO(b, VIEWPORT_W/2 + VIEWPORT_W/3*sin(time/200) + I*cimag(b->pos),0.03)
if(time < 0)
return;
AT(0) {
aniplayer_queue(&b->ani, "muda", 0);
play_sound("laser1");
create_lasercurve2c(b->pos, 50, 80, RGBA(1.0, 0.8, 0.8, 0.0), las_accel, 0, 0.2*cexp(0.4*I));
create_enemy1c(b->pos, ENEMY_IMMUNE, kurumi_slave_static_visual, aniwall_slave, 0.2*cexp(0.4*I));
create_lasercurve2c(b->pos, 50, 80, RGBA(1.0, 0.8, 0.8, 0.0), las_accel, 0, 0.2*cexp(I*M_PI - 0.4*I));
create_enemy1c(b->pos, ENEMY_IMMUNE, kurumi_slave_static_visual, aniwall_slave, 0.2*cexp(I*M_PI - 0.4*I));
int travel_time = cabs(d) / speed;
WAIT(travel_time);
}
}
TASK(kurumi_aniwall_slave, { cmplx pos; cmplx direction; }) {
Mover m = {
.pos = ARGS.pos,
.move = move_accelerated(0, 0.2*ARGS.direction)
};
INVOKE_SUBTASK(mover_update, &m);
INVOKE_SUBTASK(stage4_boss_slave_visual, &m.pos, .interval = 1);
create_lasercurve2c(ARGS.pos, 50, 80, RGBA(1.0, 0.4, 0.4, 0.0), las_accel, 0, m.move.acceleration);
real target_edge = creal(ARGS.direction) > 0 ? VIEWPORT_W : 0;
int impact_time = sqrt(2 * fabs(creal(ARGS.pos - target_edge) / creal(m.move.acceleration)));
WAIT(impact_time);
m.move = move_linear(10 * I * sign(cimag(ARGS.direction)));
INVOKE_SUBTASK(kurumi_aniwall_slave_move, &m, ARGS.direction);
int step = difficulty_value(6, 5, 2, 1);
for(;;WAIT(step)) {
cmplx vel = sign(creal(ARGS.direction)) * I * cnormalize(m.move.velocity);
if(cimag(vel) > -0.1 || global.diff > D_Easy) {
play_sfx("shot1");
INVOKE_TASK(kurumi_aniwall_bullet,
.pos = m.pos + I * vel * 20 * rng_sreal(),
.move = move_linear(vel)
);
}
}
}
DEFINE_EXTERN_TASK(kurumi_aniwall) {
Boss *b = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
b->move = move_towards((VIEWPORT_W + I * VIEWPORT_H) * 0.5, 0.0005);
b->move.retention = cdir(0.01);
aniplayer_queue(&b->ani, "muda", 0);
play_sfx("laser1");
INVOKE_SUBTASK(kurumi_aniwall_slave, .pos = b->pos, .direction = cdir(0.4));
INVOKE_SUBTASK(kurumi_aniwall_slave, .pos = b->pos, .direction = cdir(M_PI - 0.4));
STALL;
}

View file

@ -9,105 +9,84 @@
#include "taisei.h"
#include "spells.h"
#include "common_tasks.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(kurumi_blowwall_exploder, { cmplx pos; cmplx acceleration; }) {
static int blowwall_slave(Enemy *e, int t) {
float re, im;
cmplx pos = ARGS.pos;
for(int t = 0; pos == cwclamp(pos, 0, VIEWPORT_W + I * VIEWPORT_H); t++, YIELD) {
pos += ARGS.acceleration * t;
}
if(t < 0)
return 1;
float f;
ProjPrototype *type;
e->pos += e->args[0]*t;
int count = difficulty_value(60, 100, 140, 180);
if(creal(e->pos) <= 0)
e->pos = I*cimag(e->pos);
if(creal(e->pos) >= VIEWPORT_W)
e->pos = VIEWPORT_W + I*cimag(e->pos);
if(cimag(e->pos) <= 0)
e->pos = creal(e->pos);
if(cimag(e->pos) >= VIEWPORT_H)
e->pos = creal(e->pos) + I*VIEWPORT_H;
for(int i = 0; i < count; i++) {
f = rng_real();
re = creal(e->pos);
im = cimag(e->pos);
if(re <= 0 || im <= 0 || re >= VIEWPORT_W || im >= VIEWPORT_H) {
int i, c;
float f;
ProjPrototype *type;
c = 20 + global.diff*40;
for(i = 0; i < c; i++) {
f = frand();
if(f < 0.3) {
type = pp_soul;
} else if(f < 0.6) {
type = pp_bigball;
} else {
type = pp_plainball;
}
PROJECTILE(
.proto = type,
.pos = e->pos,
.color = RGBA(1.0, 0.1, 0.1, 0.0),
.rule = asymptotic,
.args = { (1+3*f)*cexp(2.0*I*M_PI*frand()), 4 },
);
if(f < 0.3) {
type = pp_soul;
} else if(f < 0.6) {
type = pp_bigball;
} else {
type = pp_plainball;
}
play_sound("shot_special1");
return ACTION_DESTROY;
PROJECTILE(
.proto = type,
.pos = pos,
.color = RGBA(1.0, 0.1, 0.1, 0.0),
.move = move_asymptotic_simple((1 + 3 * f) * rng_dir(), 4)
);
}
return 1;
play_sfx("shot_special1");
}
static void bwlaser(Boss *b, float arg, int slave) {
create_lasercurve2c(b->pos, 50, 100, RGBA(1.0, 0.5+0.3*slave, 0.5+0.3*slave, 0.0), las_accel, 0, (0.1+0.1*slave)*cexp(I*arg));
static void kurumi_blowwall_laser(Boss *b, cmplx direction, bool explode) {
cmplx acceleration = 0.1 * (1 + explode) * direction;
create_lasercurve2c(b->pos, 50, 100, RGBA(1.0, 0.3, 0.3, 0.0), las_accel, 0, acceleration);
if(slave) {
play_sound("laser1");
create_enemy1c(b->pos, ENEMY_IMMUNE, NULL, blowwall_slave, 0.2*cexp(I*arg));
if(explode) {
play_sfx("laser1");
INVOKE_SUBTASK(kurumi_blowwall_exploder, b->pos, acceleration);
} else {
// FIXME: needs a better sound
play_sound("shot2");
play_sound("shot_special1");
play_sfx("shot_special1");
play_sfx("redirect");
}
}
void kurumi_blowwall(Boss *b, int time) {
int t = time % 600;
TIMER(&t);
DEFINE_EXTERN_TASK(kurumi_blowwall) {
Boss *b = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
if(time == EVENT_DEATH)
enemy_kill_all(&global.enemies);
b->move = move_towards(BOSS_DEFAULT_GO_POS, 0.04);
if(time < 0) {
return;
}
GO_TO(b, BOSS_DEFAULT_GO_POS, 0.04)
AT(0) {
INVOKE_SUBTASK(common_charge, b->pos, RGBA(1, 0.3, 0.2, 0), 50, .sound = COMMON_CHARGE_SOUNDS);
for(;;) {
aniplayer_queue(&b->ani,"muda",0);
WAIT(50);
kurumi_blowwall_laser(b, cdir(0.4), true);
WAIT(50);
kurumi_blowwall_laser(b, cdir(M_PI-0.4), true);
WAIT(100);
for(int i = 0; i < 2; i++) {
kurumi_blowwall_laser(b, cdir(-M_PI * rng_real()), true);
WAIT(50);
}
play_sfx("laser1");
for(int i = 0; i < 20; i++) {
kurumi_blowwall_laser(b, cdir(M_PI / 10 * i), false);
WAIT(10);
}
INVOKE_SUBTASK(common_charge, b->pos, RGBA(1, 0.3, 0.2, 0), 100, .sound = COMMON_CHARGE_SOUNDS);
WAIT(50);
}
AT(50)
bwlaser(b, 0.4, 1);
AT(100)
bwlaser(b, M_PI-0.4, 1);
FROM_TO(200, 300, 50)
bwlaser(b, -M_PI*frand(), 1);
FROM_TO(300, 500, 10)
bwlaser(b, M_PI/10*_i, 0);
}

View file

@ -8,67 +8,69 @@
#include "taisei.h"
#include "common_tasks.h"
#include "spells.h"
#include "../kurumi.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(kurumi_walachia_slave_move_turn, { cmplx *vel; int duration; cmplx offset; }) {
for(int i = 0; i < ARGS.duration; i++, YIELD) {
*ARGS.vel -= 0.02 * ARGS.offset;
*ARGS.vel *= cdir(0.02);
}
}
static int kurumi_burstslave(Enemy *e, int t) {
TIMER(&t);
AT(EVENT_BIRTH)
e->args[1] = e->args[0];
AT(EVENT_DEATH) {
free_ref(e->args[2]);
return 1;
TASK(kurumi_walachia_slave_move, { cmplx *pos; cmplx *vel; cmplx direction; }) {
INVOKE_SUBTASK_DELAYED(40, kurumi_walachia_slave_move_turn, ARGS.vel, 60, ARGS.direction);
for(int i = 0;; i++, YIELD) {
*ARGS.pos += 2 * *ARGS.vel * (sin(i / 10.0) + 1.5);
}
}
if(t == 600 || REF(e->args[2]) == NULL)
return ACTION_DESTROY;
TASK(kurumi_walachia_slave, { cmplx pos; cmplx direction; int lifetime; }) {
cmplx pos = ARGS.pos;
cmplx vel = ARGS.direction;
e->pos += 2*e->args[1]*(sin(t/10.0)+1.5);
INVOKE_SUBTASK(stage4_boss_slave_visual, &pos, .interval = 1);
FROM_TO(0, 600, 18-2*global.diff) {
float r = cimag(e->pos)/VIEWPORT_H;
INVOKE_SUBTASK(kurumi_walachia_slave_move, &pos, &vel, ARGS.direction);
for(int i = 1; i >= -1; i -= 2) {
int step = difficulty_value(16, 14, 12, 10);
for(int i = 0; i < ARGS.lifetime; i += WAIT(step)) {
float r = cimag(pos)/VIEWPORT_H;
for(int j = 1; j >= -1; j -= 2) {
PROJECTILE(
.proto = pp_wave,
.pos = e->pos + i*10.0*I*e->args[0],
.pos = pos + j * 10.0 * I * ARGS.direction,
.color = RGB(r,0,0),
.rule = accelerated,
.args = { i*2.0*I*e->args[0], -0.01*e->args[1] }
.move = move_accelerated(j * 2.0 * I * ARGS.direction, -0.01*vel)
);
}
play_sfx("shot1");
}
FROM_TO(40, 100,1) {
e->args[1] -= e->args[0]*0.02;
e->args[1] *= cexp(0.02*I);
}
return 1;
}
void kurumi_slaveburst(Boss *b, int time) {
int t = time % 400;
TIMER(&t);
DEFINE_EXTERN_TASK(kurumi_walachia) {
Boss *b = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
if(time == EVENT_DEATH)
enemy_kill_all(&global.enemies);
int slave_count = difficulty_value(5, 7, 9, 11);
int duration = 400;
if(time < 0)
return;
AT(0) {
int i;
int n = 3+2*global.diff;
for(i = 0; i < n; i++) {
create_enemy3c(b->pos, ENEMY_IMMUNE, kurumi_slave_visual, kurumi_burstslave, cexp(I*2*M_PI/n*i+0.2*I*time/500), 0, add_ref(b));
for(int run = 0;; run++) {
INVOKE_SUBTASK(common_charge, .pos = b->pos, .time = 40, .color = RGBA(1.0, 0, 0, 0));
for(int i = 0; i < slave_count; i++) {
INVOKE_SUBTASK(kurumi_walachia_slave,
.pos = b->pos,
.direction = cdir(M_TAU / slave_count * i + 0.16 * run),
.lifetime = duration + 200);
}
WAIT(duration);
}
}

View file

@ -9,102 +9,116 @@
#include "taisei.h"
#include "spells.h"
#include "common_tasks.h"
#include "../kurumi.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(kurumi_redspike_slave, { cmplx pos; int direction; }) {
cmplx pos = ARGS.pos;
MoveParams move;
static int kurumi_spikeslave(Enemy *e, int t) {
TIMER(&t);
AT(EVENT_BIRTH)
e->args[1] = e->args[0];
AT(EVENT_DEATH) {
free_ref(e->args[2]);
return 1;
}
INVOKE_SUBTASK(common_move_ext, &pos, &move);
INVOKE_SUBTASK(stage4_boss_slave_visual, &pos, .interval = 1);
move.velocity = ARGS.direction;
move.retention = cdir(0.01 * ARGS.direction);
if(t == 300+50*global.diff || REF(e->args[2]) == NULL)
return ACTION_DESTROY;
int lifetime = difficulty_value(350, 400, 450, 500);
int step = difficulty_value(16, 14, 12, 10);
e->pos += e->args[1];
e->args[1] *= cexp(0.01*I*e->args[0]);
for(int i = 0; i < lifetime/step; i++, WAIT(step)) {
float r = cimag(pos)/VIEWPORT_H;
FROM_TO(0, 600, 18-2*global.diff) {
float r = cimag(e->pos)/VIEWPORT_H;
for(int i = 1; i >= -1; i -= 2) {
for(int d = 1; d >= -1; d -= 2) {
PROJECTILE(
.proto = pp_wave,
.pos = e->pos + 10.0*I*e->args[0],
.pos = pos + 10.0 * I * ARGS.direction,
.color = RGB(r,0,0),
.rule = linear,
.args = { i*1.5*I*e->args[1] }
.move = move_linear(d * 1.5 * I * move.velocity)
);
}
play_sound("shot1");
play_sfx("shot1");
}
return 1;
}
void kurumi_redspike(Boss *b, int time) {
int t = time % 500;
if(time == EVENT_DEATH)
enemy_kill_all(&global.enemies);
TASK(kurumi_redspike_spawn_slaves, { BoxedBoss boss; int interval; }) {
Boss *b = TASK_BIND(ARGS.boss);
if(time < 0)
return;
TIMER(&t);
FROM_TO(0, 500, 60) {
create_enemy3c(b->pos, ENEMY_IMMUNE, kurumi_slave_visual, kurumi_spikeslave, 1-2*(_i&1), 0, add_ref(b));
for(int i = 0;; i++) {
INVOKE_SUBTASK(kurumi_redspike_slave, b->pos, 1 - 2 * (i&1));
WAIT(ARGS.interval);
}
}
DEFINE_EXTERN_TASK(kurumi_dryfountain) {
Boss *b = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
INVOKE_SUBTASK(kurumi_redspike_spawn_slaves, .boss = ENT_BOX(b), .interval = 60);
int step = difficulty_value(100, 50, 50, 50);
int count = difficulty_value(8, 16, 16, 16);
real speed = 3 * difficulty_value(1.1, 1.2, 1.2, 1.2);
for(;;) {
for(int i = 0; i < count; i++) {
cmplx vel = speed * cdir(M_TAU / count * i) * cnormalize(global.plr.pos-b->pos);
PROJECTILE(
.proto = pp_bigball,
.pos = b->pos,
.color = RGBA(1.0, 0.0, 0.0, 0.0),
.move = move_asymptotic_simple(vel, 3)
);
}
play_sfx("shot_special1");
WAIT(step);
}
}
TASK(kurumi_redspike_animate, { BoxedBoss boss; }) {
Boss *b = TASK_BIND(ARGS.boss);
WAIT(80);
aniplayer_queue(&b->ani, "muda", 0);
WAIT(420);
aniplayer_queue(&b->ani, "main", 0);
}
DEFINE_EXTERN_TASK(kurumi_redspike) {
Boss *b = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
INVOKE_SUBTASK(kurumi_redspike_spawn_slaves, .boss = ENT_BOX(b), .interval = 60);
for(;;) {
INVOKE_SUBTASK(kurumi_redspike_animate, ENT_BOX(b));
WAIT(80);
for(int rep = 0; rep < 2; rep++) {
int step = difficulty_value(4, 4, 4, 2);
for(int i = 0; i < 200; i += WAIT(step)) {
RNG_ARRAY(rand, 2);
cmplx offset = 100 * vrng_real(rand[0]) * vrng_dir(rand[1]);
cmplx dir = cnormalize(global.plr.pos - b->pos - offset);
if(global.diff < D_Hard) {
FROM_TO(0, 500, 150-50*global.diff) {
int i;
int n = global.diff*8;
for(i = 0; i < n; i++) {
PROJECTILE(
.proto = pp_bigball,
.pos = b->pos,
.color = RGBA(1.0, 0.0, 0.0, 0.0),
.rule = asymptotic,
.args = {
(1+0.1*(global.diff == D_Normal))*3*cexp(2.0*I*M_PI/n*i+I*carg(global.plr.pos-b->pos)),
3
},
.proto = pp_rice,
.pos = b->pos+offset,
.color = RGBA(1, 0, 0, 0),
.move = move_accelerated(-dir, 0.05 * dir),
);
play_sfx_ex("warp",0,false);
}
play_sound("shot_special1");
}
} else {
AT(80) {
aniplayer_queue(&b->ani, "muda", 0);
}
AT(499) {
aniplayer_queue(&b->ani, "main", 0);
}
FROM_TO_INT(80, 500, 40,200,2+2*(global.diff == D_Hard)) {
tsrand_fill(2);
cmplx offset = 100*afrand(0)*cexp(2.0*I*M_PI*afrand(1));
cmplx n = cexp(I*carg(global.plr.pos-b->pos-offset));
PROJECTILE(
.proto = pp_rice,
.pos = b->pos+offset,
.color = RGBA(1, 0, 0, 0),
.rule = accelerated,
.args = { -1*n, 0.05*n },
);
play_sound_ex("warp",0,false);
WAIT(40);
}
}
}

View file

@ -11,9 +11,10 @@
#include "boss.h"
void kurumi_slaveburst(Boss*, int);
void kurumi_redspike(Boss*, int);
void kurumi_aniwall(Boss*, int);
void kurumi_blowwall(Boss*, int);
void kurumi_danmaku(Boss*, int);
void kurumi_extra(Boss*, int);
DECLARE_EXTERN_TASK_WITH_INTERFACE(kurumi_walachia, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(kurumi_dryfountain, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(kurumi_redspike, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(kurumi_aniwall, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(kurumi_blowwall, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(kurumi_vampvape, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(kurumi_vladsarmy, BossAttack);

View file

@ -9,139 +9,129 @@
#include "taisei.h"
#include "spells.h"
#include "common_tasks.h"
#include "../kurumi.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
static Projectile *vapor_particle(cmplx pos, const Color *clr) {
return PARTICLE(
.sprite = "stain",
.color = clr,
.timeout = 60,
.draw_rule = ScaleFade,
.args = { 0, 0, 0.2 + 2.0*I },
.draw_rule = pdraw_timeout_scalefade(0.2, 2.0, 0.6, 0),
.pos = pos,
.angle = M_PI*2*frand(),
.angle = rng_angle(),
);
}
static int kdanmaku_proj(Projectile *p, int t) {
if(t < 0) {
return ACTION_ACK;
// XXX replace me by common_interpolate once merged
TASK(interpolate, { float *clr; float target; float step; }) {
for(;;) {
fapproach_p(ARGS.clr, ARGS.target, ARGS.step);
YIELD;
}
int time = creal(p->args[0]);
if(t == time) {
p->color = *RGBA(0.6, 0.3, 1.0, 0.0);
projectile_set_prototype(p, pp_bullet);
p->args[1] = (global.plr.pos - p->pos) * 0.001;
if(frand() < 0.5) {
Projectile *v = vapor_particle(p->pos, color_mul_scalar(COLOR_COPY(&p->color), 0.5));
if(frand() < 0.5) {
v->flags |= PFLAG_REQUIREDPARTICLE;
}
}
PARTICLE(
.sprite = "flare",
.color = RGB(1, 1, 1),
.timeout = 30,
.draw_rule = ScaleFade,
.args = { 0, 0, 3.0 },
.pos = p->pos,
);
play_sound("shot3");
}
if(t > time && cabs(p->args[1]) < 2) {
p->args[1] *= 1.02;
fapproach_p(&p->color.a, 1, 0.025);
}
p->pos += p->args[1];
p->angle = carg(p->args[1]);
return ACTION_NONE;
}
static int kdanmaku_slave(Enemy *e, int t) {
float re;
TASK(kurumi_vampvape_proj, { int delay; cmplx pos; cmplx vel; }) {
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_thickrice,
.pos = ARGS.pos,
.color = RGBA(1.0, 0.5, 0.5, 0.0),
.move = move_linear(ARGS.vel),
.flags = PFLAG_NOSPAWNFLARE,
));
if(t < 0)
return 1;
WAIT(ARGS.delay);
if(!e->args[1])
e->pos += e->args[0]*t;
else
e->pos += 5.0*I;
p->color = *RGBA(0.3, 0.8, 0.8, 0.0);
projectile_set_prototype(p, pp_bullet);
p->move = move_linear((global.plr.pos - p->pos) * 0.001);
p->move.retention = 1.02;
if(creal(e->pos) <= 0)
e->pos = I*cimag(e->pos);
if(creal(e->pos) >= VIEWPORT_W)
e->pos = VIEWPORT_W + I*cimag(e->pos);
if(rng_chance(0.5)) {
Projectile *v = vapor_particle(p->pos, color_mul_scalar(COLOR_COPY(&p->color), 0.3));
re = creal(e->pos);
if(rng_chance(0.5)) {
v->flags |= PFLAG_REQUIREDPARTICLE;
}
}
if(re <= 0 || re >= VIEWPORT_W)
e->args[1] = 1;
PARTICLE(
.sprite = "flare",
.color = RGB(1, 1, 1),
.timeout = 30,
.draw_rule = pdraw_timeout_scalefade(3.0, 0, 0.6, 0),
.pos = p->pos,
);
if(cimag(e->pos) >= VIEWPORT_H)
return ACTION_DESTROY;
play_sfx("shot3");
if(e->args[2] && e->args[1]) {
int i, n = 3+imax(D_Normal,global.diff);
float speed = 1.5+0.1*global.diff;
INVOKE_SUBTASK(interpolate, &p->color.a, 1, 0.025);
while(cabs(p->move.velocity) < 2) {
YIELD;
}
p->move.retention = 1.0;
}
TASK(kurumi_vampvape_slave, { cmplx pos; cmplx target; int time_offset; }) {
cmplx direction = cnormalize(ARGS.target - ARGS.pos);
cmplx acceleration = 0.2 * direction;
create_lasercurve2c(ARGS.pos, 50, 100, RGBA(1.0, 0.3, 0.3, 0.0), las_accel, 0, acceleration);
int travel_time = sqrt(2 * cabs(ARGS.target - ARGS.pos) / cabs(acceleration));
WAIT(travel_time);
real step = 5;
int step_count = VIEWPORT_H / step;
for(int i = ARGS.time_offset; i < ARGS.time_offset + step_count; i++, YIELD) {
real y = step * i;
int count = difficulty_value(3, 4, 5, 5);
float speed = difficulty_value(0.5, 0.7, 0.9, 0.95);
for(int j = 0; j < count; j++) {
cmplx p = VIEWPORT_W / (real)count * (j + psin(i * i * j * j + i * i)) + I * y;
cmplx dir = cdir(M_TAU * sin(245 * i + j * j * 3501));
for(i = 0; i < n; i++) {
cmplx p = VIEWPORT_W/(float)n*(i+psin(t*t*i*i+t*t)) + I*cimag(e->pos);
if(cabs(p-global.plr.pos) > 60) {
PROJECTILE(
.proto = pp_thickrice,
.pos = p,
.color = RGBA(1.0, 0.5, 0.5, 0.0),
.rule = kdanmaku_proj,
.args = { 160, speed*0.5*cexp(2.0*I*M_PI*sin(245*t+i*i*3501)) },
.flags = PFLAG_NOSPAWNFLARE,
);
INVOKE_TASK(kurumi_vampvape_proj, 160, p, speed * dir);
if(frand() < 0.5) {
vapor_particle(p, RGBA(0.5, 0.125 * frand(), 0.125 * frand(), 0.1));
if(rng_chance(0.5)) {
RNG_ARRAY(rand, 2);
vapor_particle(p, RGBA(0.5, 0.125 * vrng_real(rand[0]), 0.125 * vrng_real(rand[1]), 0.1));
}
}
}
play_sound_ex("redirect", 3, false);
play_sfx_ex("redirect", 3, false);
}
return 1;
}
void kurumi_danmaku(Boss *b, int time) {
int t = time % 400;
TIMER(&t);
DEFINE_EXTERN_TASK(kurumi_vampvape) {
Boss *b = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
b->move = move_towards(BOSS_DEFAULT_GO_POS, 0.04);
if(time == EVENT_DEATH)
enemy_kill_all(&global.enemies);
if(time < 0)
return;
for(int t = 0;; t++) {
INVOKE_SUBTASK(common_charge, b->pos, RGBA(1, 0.3, 0.2, 0), 50, .sound = COMMON_CHARGE_SOUNDS);
WAIT(50);
play_sfx("laser1");
INVOKE_SUBTASK(kurumi_vampvape_slave,
.pos = b->pos,
.target = 0,
.time_offset = t
);
INVOKE_SUBTASK(kurumi_vampvape_slave,
.pos = b->pos,
.target = VIEWPORT_W,
.time_offset = 1.23*t
);
WAIT(210);
GO_TO(b, BOSS_DEFAULT_GO_POS, 0.04)
AT(260) {
aniplayer_queue(&b->ani,"muda",4);
aniplayer_queue(&b->ani,"main",0);
}
AT(50) {
play_sound("laser1");
create_lasercurve2c(b->pos, 50, 100, RGBA(1.0, 0.8, 0.8, 0.0), las_accel, 0, 0.2*cexp(I*carg(-b->pos)));
create_lasercurve2c(b->pos, 50, 100, RGBA(1.0, 0.8, 0.8, 0.0), las_accel, 0, 0.2*cexp(I*carg(VIEWPORT_W-b->pos)));
create_enemy3c(b->pos, ENEMY_IMMUNE, kurumi_slave_static_visual, kdanmaku_slave, 0.2*cexp(I*carg(-b->pos)), 0, 1);
create_enemy3c(b->pos, ENEMY_IMMUNE, kurumi_slave_static_visual, kdanmaku_slave, 0.2*cexp(I*carg(VIEWPORT_W-b->pos)), 0, 0);
WAIT(140);
}
}

View file

@ -10,143 +10,176 @@
#include "spells.h"
#include "../kurumi.h"
#include "enemy_classes.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
// TODO SUPER REDESIGN THIS, IT'S A MESS!
static void kurumi_extra_shield_pos(Enemy *e, int time) {
double dst = 75 + 100 * fmax((60 - time) / 60.0, 0);
double spd = cimag(e->args[0]) * fmin(time / 120.0, 1);
e->args[0] += spd;
e->pos = global.boss->pos + dst * cexp(I*creal(e->args[0]));
static void kurumi_extra_shield_visual(Enemy *e, int time, bool render) {
if(!render) {
return;
}
// TODO: something nicer here
float h = clampf(e->hp / e->spawn_hp, 0, 1);
h *= h;
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)
),
.sprite = "enemy/swirl",
.pos.as_cmplx = e->pos,
.rotation.angle = time * -10 * DEG2RAD,
.shader = "sprite_negative",
});
}
static bool kurumi_extra_shield_expire(Enemy *e, int time) {
if(time > creal(e->args[1])) {
e->hp = 0;
return true;
}
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);
return false;
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 int kurumi_extra_dead_shield_proj(Projectile *p, int time) {
if(time < 0) {
return ACTION_ACK;
static void kurumi_extra_fairy_visual(Enemy *e, int time, bool render) {
if(render) {
draw_negative_fairy(e, time, res_anim("enemy/fairy_blue"));
}
p->color = *color_lerp(
RGBA(2.0, 0.0, 0.0, 0.0),
RGBA(0.2, 0.1, 0.5, 0.0),
fmin(time / 60.0f, 1.0f));
return asymptotic(p, time);
}
static int kurumi_extra_dead_shield(Enemy *e, int time) {
if(time < 0) {
return 1;
static void kurumi_extra_bigfairy_visual(Enemy *e, int time, bool render) {
if(render) {
draw_negative_fairy(e, time, res_anim("enemy/superfairy"));
}
}
if(!(time % 6)) {
// complex dir = cexp(I*(M_PI * 0.5 * nfrand() + carg(global.plr.pos - e->pos)));
// complex dir = cexp(I*(carg(global.plr.pos - e->pos)));
cmplx dir = cexp(I*creal(e->args[0]));
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.rule = kurumi_extra_dead_shield_proj,
.args = { 2*dir, 10 }
);
play_sound("shot1");
TASK(kurumi_vladsarmy_shield_death_proj, { cmplx pos; MoveParams move; }) {
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_ball,
.pos = ARGS.pos,
.move = ARGS.move
));
int duration = 60;
for(int i = 0; i < duration; i++, YIELD) {
p->color = *color_lerp(
RGBA(2.0, 0.0, 0.0, 0.0),
RGBA(0.2, 0.1, 0.5, 0.0),
i / (float) duration);
}
}
time += cimag(e->args[1]);
TASK(kurumi_vladsarmy_shield_death, { BoxedEnemy enemy; }) {
Enemy *e = ENT_UNBOX(ARGS.enemy);
if(e == NULL || !(e->flags & EFLAG_IMPENETRABLE)) {
return;
}
cmplx pos = e->pos;
kurumi_extra_shield_pos(e, time);
play_sfx_ex("boon", 3, false);
int shots = 5;
int count = 10;
if(kurumi_extra_shield_expire(e, time)) {
int cnt = 10;
for(int i = 0; i < cnt; ++i) {
cmplx dir = cexp(I*M_PI*2*i/(double)cnt);
tsrand_fill(2);
PROJECTILE(
.proto = pp_ball,
.pos = e->pos,
.rule = kurumi_extra_dead_shield_proj,
.args = { 1.5 * (1 + afrand(0)) * dir, 4 + anfrand(1) },
for(int i = 0; i < shots; i++) {
for(int j = 0; j < count; ++j) {
cmplx dir = cdir(M_TAU/count * j);
real speed = 2.5 * (1 + rng_real());
INVOKE_TASK(kurumi_vladsarmy_shield_death_proj,
.pos = pos,
.move = move_asymptotic_simple(speed * dir, -0.7)
);
}
play_sound("boom");
WAIT(6);
}
return 1;
}
static int kurumi_extra_shield(Enemy *e, int time) {
if(time == EVENT_DEATH) {
if(global.boss && !(global.gameover > 0) && !boss_is_dying(global.boss) && e->args[2] == 0) {
create_enemy2c(e->pos, ENEMY_IMMUNE, kurumi_slave_visual, kurumi_extra_dead_shield, e->args[0], e->args[1]);
}
return 1;
TASK(kurumi_vladsarmy_shield_manage, { BoxedEnemy enemy; BoxedBoss boss; real angle; int timeout; }) {
Enemy *e = TASK_BIND(ARGS.enemy);
for(int i = 0; i < ARGS.timeout; i++, YIELD) {
e->pos = NOT_NULL(ENT_UNBOX(ARGS.boss))->pos + 140 * cdir(ARGS.angle + 0.02 * i);
}
if(time < 0) {
return 1;
}
e->args[1] = creal(e->args[1]) + time*I;
kurumi_extra_shield_pos(e, time);
kurumi_extra_shield_expire(e, time);
return 1;
e->flags &= ~EFLAG_IMPENETRABLE;
enemy_kill(e);
}
static int kurumi_extra_bigfairy1(Enemy *e, int time) {
if(time < 0) {
return 1;
}
TIMER(&time);
TASK(kurumi_vladsarmy_shield, { BoxedBoss boss; real angle; }) {
int hp = 1500;
Boss *b = NOT_NULL(ENT_UNBOX(ARGS.boss));
int escapetime = 400+4000*(global.diff == D_Lunatic);
if(time < escapetime) {
GO_TO(e, e->args[0], 0.02);
} else {
GO_TO(e, e->args[0]-I*VIEWPORT_H,0.01)
}
Enemy *e = create_enemy2c(b->pos, hp, kurumi_extra_shield_visual, NULL, 0, 0);
e->flags = EFLAG_IMPENETRABLE;
FROM_TO(50,escapetime,60) {
int timeout = 800;
INVOKE_SUBTASK_WHEN(&e->events.killed, kurumi_vladsarmy_shield_death, ENT_BOX(e));
INVOKE_SUBTASK(kurumi_vladsarmy_shield_manage,
.enemy=ENT_BOX(e),
.boss = ARGS.boss,
.angle = ARGS.angle,
.timeout = timeout
);
STALL;
}
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;
int escapetime = difficulty_value(400, 400, 400, 4000);
e->move = move_towards(ARGS.target, 0.02);
WAIT(50);
for(int k = 0; k < escapetime; k += WAIT(60)) {
int count = 5;
cmplx phase = cexp(I*2*M_PI*frand());
for(int i = 0; i < count; i++) {
cmplx arg = cexp(I*2*M_PI*i/count);
cmplx phase = rng_dir();
for(int j = 0; j < count; j++) {
cmplx dir = cdir(M_TAU * j / count);
if(global.diff == D_Lunatic)
arg *= phase;
create_lasercurve2c(e->pos, 20, 200, RGBA(1.0, 0.3, 0.7, 0.0), las_accel, arg, 0.1*arg);
dir *= phase;
create_lasercurve2c(e->pos, 20, 200, RGBA(1.0, 0.3, 0.7, 0.0), las_accel, dir, 0.1 * dir);
PROJECTILE(
.proto = pp_bullet,
.pos = e->pos,
.color = RGB(1.0, 0.3, 0.7),
.rule = accelerated,
.args = { arg, 0.1*arg },
.move = move_accelerated(dir, 0.1 * dir)
);
}
play_sound("laser1");
// XXX make this sound enjoyable
play_sfx_ex("laser1", 60, false);
}
return 1;
e->move = move_towards(ARGS.target - I * VIEWPORT_H, 0.01);
}
DEPRECATED_DRAW_RULE
typedef struct DrainerState {
Texture *texture;
float alpha;
cmplx target;
} DrainerState;
static void kurumi_extra_drainer_draw(Projectile *p, int time, ProjDrawRuleArgs args) {
// TODO: Make this use the sprite batcher?
DrainerState *drainer = args[0].as_ptr;
cmplx org = p->pos;
cmplx targ = p->args[1];
double a = 0.5 * creal(p->args[2]);
Texture *tex = r_texture_get("part/sinewave");
cmplx targ = drainer->target;
float a = 0.5f * drainer->alpha;
Texture *tex = drainer->texture;
r_shader_standard();
@ -160,131 +193,81 @@ static void kurumi_extra_drainer_draw(Projectile *p, int time, ProjDrawRuleArgs
loop_tex_line_p(org, targ, 24, time * 0.003, tex);
}
static int kurumi_extra_drainer(Projectile *p, int time) {
if(time == EVENT_DEATH) {
free_ref(p->args[0]);
return ACTION_ACK;
}
TASK(kurumi_vladsarmy_drainer, { BoxedBoss boss; BoxedEnemy enemy; }) {
DrainerState state = {
.texture = res_texture("part/sinewave"),
};
if(time < 0) {
return ACTION_ACK;
}
Enemy *e = REF(p->args[0]);
p->pos = global.boss->pos;
if(e) {
p->args[1] = e->pos;
p->args[2] = approach(p->args[2], 1, 0.5);
if(time > 40 && e->hp > 0) {
// TODO: maybe add a special sound for this?
float drain = fmin(4, e->hp);
ent_damage(&e->ent, &(DamageInfo) { .amount = drain });
global.boss->current->hp = fmin(global.boss->current->maxhp, global.boss->current->hp + drain * 2);
}
} else {
p->args[2] = approach(p->args[2], 0, 0.5);
if(!creal(p->args[2])) {
return ACTION_DESTROY;
}
}
return ACTION_NONE;
}
static void kurumi_extra_create_drainer(Enemy *e) {
PROJECTILE(
Projectile *p = TASK_BIND(PROJECTILE(
.size = 1+I,
.pos = e->pos,
.rule = kurumi_extra_drainer,
.draw_rule = kurumi_extra_drainer_draw,
.args = { add_ref(e) },
.pos = NOT_NULL(ENT_UNBOX(ARGS.boss))->pos,
.draw_rule = {
.func = kurumi_extra_drainer_draw,
.args[0].as_ptr = &state,
},
.shader = "sprite_default",
.flags = PFLAG_NOCLEAR | PFLAG_NOCOLLISION,
);
.layer = LAYER_BOSS - 1,
));
for(int i = 0;; i++, YIELD) {
Enemy *e = ENT_UNBOX(ARGS.enemy);
Boss *boss = ENT_UNBOX(ARGS.boss);
if(boss) {
p->pos = boss->pos;
}
if(e && boss) {
state.target = e->pos;
fapproach_asymptotic_p(&state.alpha, 1, 0.5, 1e-3);
if(i > 40 && e->hp > 0) {
// TODO: maybe add a special sound for this?
float drain = fmin(4, e->hp);
ent_damage(&e->ent, &(DamageInfo) { .amount = drain });
boss->current->hp = fmin(boss->current->maxhp, boss->current->hp + drain * 2);
}
} else {
fapproach_asymptotic_p(&state.alpha, 0, 0.5, 1e-3);
if(!state.alpha) {
kill_projectile(p);
}
}
}
}
static void kurumi_extra_swirl_visual(Enemy *e, int time, bool render) {
if(!render) {
// why the hell was this here?
// Swirl(e, time, render);
return;
}
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->flags |= EFLAG_NO_AUTOKILL;
// FIXME: blend
r_blend(BLEND_SUB);
Swirl(e, time, render);
r_blend(BLEND_PREMUL_ALPHA);
}
e->move = move_towards(ARGS.target_pos, 0.1);
static void kurumi_extra_fairy_visual(Enemy *e, int time, bool render) {
if(!render) {
Fairy(e, time, render);
return;
}
// r_blend(BLEND_ADD);
r_shader("sprite_negative");
Fairy(e, time, render);
r_shader("sprite_default");
// r_blend(BLEND_ALPHA);
}
static void kurumi_extra_bigfairy_visual(Enemy *e, int time, bool render) {
if(!render) {
BigFairy(e, time, render);
return;
}
// r_blend(BLEND_ADD);
r_shader("sprite_negative");
BigFairy(e, time, render);
r_shader("sprite_default");
// r_blend(BLEND_ALPHA);
}
static int kurumi_extra_fairy(Enemy *e, int t) {
TIMER(&t);
AT(EVENT_KILLED) {
spawn_items(e->pos, ITEM_POINTS, 1);
return 1;
}
if(e->flags & EFLAG_NO_AUTOKILL && t > 50)
WAIT(50);
e->flags &= ~EFLAG_NO_AUTOKILL;
if(creal(e->args[0]-e->pos) != 0)
e->moving = true;
e->dir = creal(e->args[0]-e->pos) < 0;
WAIT(ARGS.attack_time-70);
FROM_TO(0,90,1) {
GO_TO(e,e->args[0],0.1)
}
real speed = difficulty_value(2.0, 2.1, 2.2, 2.3);
/*FROM_TO(100, 200, 22-global.diff*3) {
PROJECTILE("ball", e->pos, RGB(1, 0.3, 0.5), asymptotic, 2*cexp(I*M_PI*2*frand()), 3);
}*/
int attacktime = creal(e->args[1]);
int flytime = cimag(e->args[1]);
FROM_TO_SND("shot1_loop", attacktime-20,attacktime+20,20) {
cmplx vel = cexp(I*frand()*2*M_PI)*(2+0.1*(global.diff-D_Easy));
if(e->args[2] == 0) { // attack type
for(int k = 0; k <= 2; k++, WAIT(20)) {
play_sfx_loop("shot1_loop");
cmplx vel = speed * rng_dir();
if(ARGS.attack_type == 0) {
int corners = 5;
double len = 50;
int count = 5;
real len = 50;
int count = 4;
for(int i = 0; i < corners; i++) {
for(int j = 0; j < count; j++) {
cmplx pos = len/2/tan(2*M_PI/corners)*I+(j/(double)count-0.5)*len;
pos *= cexp(I*2*M_PI/corners*i);
cmplx offset = 0.5 / tan(M_TAU / corners) * I + (j / (real)count - 0.5);
offset *= len*cdir(M_TAU / corners * i);
PROJECTILE(
.proto = pp_flea,
.pos = e->pos+pos,
.pos = e->pos + offset,
.color = RGB(1, 0.3, 0.5),
.rule = linear,
.args = { vel+0.1*I*pos/cabs(pos) }
.move = move_linear(vel + 0.1 * cnormalize(offset)),
);
}
}
@ -292,31 +275,26 @@ static int kurumi_extra_fairy(Enemy *e, int t) {
int count = 30;
double rad = 20;
for(int j = 0; j < count; j++) {
double x = (j/(double)count-0.5)*2*M_PI;
cmplx pos = 0.5*cos(x)+sin(2*x) + (0.5*sin(x)+cos(2*x))*I;
pos*=vel/cabs(vel);
real x = M_TAU * (j / (real)count-0.5);
cmplx pos = 0.5 * cos(x) + sin(2 * x) + (0.5 * sin(x) + cos( 2 * x))*I;
pos *= cnormalize(vel);
PROJECTILE(
.proto = pp_flea,
.pos = e->pos+rad*pos,
.color = RGB(0.5, 0.3, 1),
.rule = linear,
.args = { vel+0.1*pos },
.move = move_linear(vel + 0.1*pos),
);
}
}
}
AT(attacktime) {
e->args[0] = global.plr.pos-e->pos;
kurumi_extra_create_drainer(e);
play_sound("redirect");
}
FROM_TO(attacktime,attacktime+flytime,1) {
e->pos += e->args[0]/flytime;
}
e->move = move_linear((global.plr.pos - e->pos) / ARGS.chase_time);
INVOKE_TASK(kurumi_vladsarmy_drainer, ARGS.boss, ENT_BOX(e));
play_sfx("redirect");
FROM_TO(attacktime,attacktime+flytime,20-global.diff*3) {
int chase_drop_step = difficulty_value(17, 14, 11, 8);
for(int i = 0; i < ARGS.chase_time / chase_drop_step; i++, WAIT(chase_drop_step)) {
if(global.diff>D_Easy) {
Color *clr = RGBA_MUL_ALPHA(0.1 + 0.07 * _i, 0.3, 1 - 0.05 * _i, 0.8);
Color *clr = RGBA_MUL_ALPHA(0.1 + 0.07 * i, 0.3, 1 - 0.05 * i, 0.8);
clr->a = 0;
PROJECTILE(
@ -327,94 +305,95 @@ static int kurumi_extra_fairy(Enemy *e, int t) {
);
}
}
if(t > attacktime + flytime + 20 && global.boss) {
GO_TO(e,global.boss->pos,0.04)
}
return 1;
e->move = move_towards(global.boss->pos, 0.04);
}
void kurumi_extra(Boss *b, int time) {
int length = 400;
int t = time % length;
int direction = (time/length)%2;
DEFINE_EXTERN_TASK(kurumi_vladsarmy) {
Boss *b = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
int castlimit = b->current->maxhp * 0.05;
int shieldlimit = b->current->maxhp * 0.1;
cmplx startpos = VIEWPORT_W * 0.5 + VIEWPORT_H * 0.28 * I;
TIMER(&t);
int prey_count = 20;
int fairy_chase_time = difficulty_value(105, 100, 95, 90);
if(time == EVENT_DEATH) {
enemy_kill_all(&global.enemies);
return;
}
for(int run = 0;; run++) {
b->move = move_towards(startpos, 0.01);
int direction = run&1;
if(time < 120)
GO_TO(b, VIEWPORT_W * 0.5 + VIEWPORT_H * 0.28 * I, 0.01)
int castlimit = b->current->maxhp * 0.05;
int shieldlimit = b->current->maxhp * 0.1;
if(t == 0 && b->current->hp >= shieldlimit) {
int cnt = 12;
for(int i = 0; i < cnt; ++i) {
double a = M_PI * 2 * i / (double)cnt;
int hp = 500;
create_enemy2c(b->pos, hp, kurumi_extra_swirl_visual, kurumi_extra_shield, a + 0.05*I, 800);
create_enemy2c(b->pos, hp, kurumi_extra_swirl_visual, kurumi_extra_shield, a - 0.05*I, 800);
if(b->current->hp >= shieldlimit) {
int shield_count = 12;
for(int i = 0; i < shield_count; ++i) {
real angle = M_TAU/shield_count * i;
INVOKE_SUBTASK(kurumi_vladsarmy_shield, ENT_BOX(b), .angle = angle);
}
}
}
AT(90) {
int cnt = 20;
for(int i = 0; i < cnt; i++) {
WAIT(90);
for(int i = 0; i < prey_count; i++) {
b->current->hp -= 600;
if(b->current->hp < castlimit)
if(b->current->hp < castlimit) {
b->current->hp = castlimit;
tsrand_fill(2);
cmplx pos = VIEWPORT_W/2*afrand(0)+I*afrand(1)*VIEWPORT_H*2/3;
}
RNG_ARRAY(rand, 2);
cmplx pos = vrng_real(rand[0]) * VIEWPORT_W / 2 + I * vrng_real(rand[1]) * VIEWPORT_H * 0.67;
if(direction)
pos = VIEWPORT_W-creal(pos)+I*cimag(pos);
// immune so they dont get killed while they are still offscreen.
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;
INVOKE_SUBTASK(kurumi_vladsarmy_fairy,
.start_pos = b->pos - 300 * (1 - 2 * direction),
.target_pos = pos,
.attack_time = 100 + 20 * i,
.chase_time = fairy_chase_time,
.attack_type = direction,
.boss = ENT_BOX(b)
);
}
// XXX: maybe add a special sound for this?
play_sound("shot_special1");
}
play_sfx("shot_special1");
cmplx sidepos = VIEWPORT_W * (0.5+0.3*(1-2*direction)) + VIEWPORT_H * 0.28 * I;
FROM_TO(90,120,1) {
GO_TO(b, sidepos,0.1)
}
cmplx sidepos = VIEWPORT_W * (0.5 + 0.3 * (1 - 2 * direction)) + VIEWPORT_H * 0.28 * I;
b->move = move_towards(sidepos, 0.1);
WAIT(100);
b->move = move_towards(sidepos + 30 * I, 0.1);
FROM_TO(190,200,1) {
GO_TO(b, sidepos+30*I,0.1)
}
AT(190){
aniplayer_queue_frames(&b->ani, "muda", 110);
aniplayer_queue(&b->ani, "main", 0);
}
FROM_TO(300,400,1) {
GO_TO(b,VIEWPORT_W * 0.5 + VIEWPORT_H * 0.28 * I,0.1)
}
WAIT(110);
if(global.diff >= D_Hard) {
AT(300) {
double ofs = VIEWPORT_W * 0.5;
b->move = move_towards(startpos, 0.1);
if(global.diff >= D_Hard) {
double offset = VIEWPORT_W * 0.5;
cmplx pos = 0.5 * VIEWPORT_W + I * (VIEWPORT_H - 100);
cmplx targ = 0.5 *VIEWPORT_W + VIEWPORT_H * 0.3 * I;
create_enemy1c(pos + ofs, 3300, kurumi_extra_bigfairy_visual, kurumi_extra_bigfairy1, targ + 0.8*ofs);
create_enemy1c(pos - ofs, 3300, kurumi_extra_bigfairy_visual, kurumi_extra_bigfairy1, targ - 0.8*ofs);
cmplx target = 0.5 * VIEWPORT_W + VIEWPORT_H * 0.3 * I;
INVOKE_SUBTASK(kurumi_vladsarmy_bigfairy,
.pos = pos + offset,
.target = target + 0.8*offset
);
INVOKE_SUBTASK(kurumi_vladsarmy_bigfairy,
.pos = pos - offset,
.target = target - 0.8*offset
);
}
}
if((t == length-20 && global.diff == D_Easy)|| b->current->hp < shieldlimit) {
for(Enemy *e = global.enemies.first; e; e = e->next) {
if(e->logic_rule == kurumi_extra_shield) {
e->args[2] = 1; // discharge extra shield
e->hp = 0;
continue;
WAIT(100);
/*if((t == length-20 && global.diff == D_Easy)|| b->current->hp < shieldlimit) {
for(Enemy *e = global.enemies.first; e; e = e->next) {
if(e->logic_rule == kurumi_extra_shield) {
e->args[2] = 1; // discharge extra shield
e->hp = 0;
continue;
}
}
}
}*/
}
}

View file

@ -28,40 +28,40 @@ struct stage4_spells_s stage4_spells = {
.mid = {
.gate_of_walachia = {
{ 0, 1, 2, 3}, AT_Spellcard, "Bloodless “Gate of Walachia”", 50, 44000,
kurumi_slaveburst, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4
NULL, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4, TASK_INDIRECT_INIT(BossAttack, kurumi_walachia)
},
.dry_fountain = {
{ 4, 5, -1, -1}, AT_Spellcard, "Bloodless “Dry Fountain”", 50, 44000,
kurumi_redspike, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4
NULL, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4, TASK_INDIRECT_INIT(BossAttack, kurumi_dryfountain)
},
.red_spike = {
{-1, -1, 6, 7}, AT_Spellcard, "Bloodless “Red Spike”", 50, 46000,
kurumi_redspike, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4
NULL, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4, TASK_INDIRECT_INIT(BossAttack, kurumi_redspike)
},
},
.boss = {
.animate_wall = {
{ 8, 9, -1, -1}, AT_Spellcard, "Limit “Animate Wall”", 60, 50000,
kurumi_aniwall, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4
NULL, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4, TASK_INDIRECT_INIT(BossAttack, kurumi_aniwall),
},
.demon_wall = {
{-1, -1, 10, 11}, AT_Spellcard, "Summoning “Demon Wall”", 60, 55000,
kurumi_aniwall, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4
NULL, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4, TASK_INDIRECT_INIT(BossAttack, kurumi_aniwall)
},
.blow_the_walls = {
{12, 13, 14, 15}, AT_Spellcard, "Power Sign “Blow the Walls”", 60, 55000,
kurumi_blowwall, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4
NULL, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4, TASK_INDIRECT_INIT(BossAttack, kurumi_blowwall)
},
.bloody_danmaku = {
{18, 19, 16, 17}, AT_Spellcard, "Predation “Vampiric Vapor”", 80, 60000,
kurumi_danmaku, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4
NULL, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4, TASK_INDIRECT_INIT(BossAttack, kurumi_vampvape)
},
},
.extra.vlads_army = {
{ 0, 1, 2, 3}, AT_ExtraSpell, "Blood Magic “Vlads Army”", 60, 50000,
kurumi_extra, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4
NULL, kurumi_spell_bg, BOSS_DEFAULT_GO_POS, 4, TASK_INDIRECT_INIT(BossAttack, kurumi_vladsarmy)
},
};
@ -69,6 +69,8 @@ static void stage4_start(void) {
stage4_drawsys_init();
stage4_bg_init_fullstage();
stage_start_bgm("stage4");
stage_set_voltage_thresholds(170, 340, 660, 1040);
INVOKE_TASK(stage4_timeline);
}
static void stage4_spellpractice_start(void) {
@ -136,7 +138,6 @@ StageProcs stage4_procs = {
.preload = stage4_preload,
.end = stage4_end,
.draw = stage4_draw,
.event = stage4_events,
.shader_rules = stage4_bg_effects,
.spellpractice_procs = &stage4_spell_procs,
};

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,6 @@
#include "boss.h"
void stage4_events(void);
DECLARE_EXTERN_TASK(stage4_timeline);
#define STAGE4_MIDBOSS_TIME 3724
#define STAGE4_MIDBOSS_MUSIC_TIME 2818