taisei/src/stages/stage3/timeline.c
2024-05-17 14:11:48 +02:00

839 lines
20 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "timeline.h" // IWYU pragma: keep
#include "draw.h"
#include "nonspells/nonspells.h"
#include "scuttle.h"
#include "spells/spells.h"
#include "stage3.h"
#include "wriggle.h"
#include "stages/common_imports.h"
static void stage3_dialog_post_boss(void) {
PlayerMode *pm = global.plr.mode;
INVOKE_TASK_INDIRECT(Stage3PostBossDialog, pm->dialog->Stage3PostBoss);
}
TASK(swarm_trail_proj_cleanup, { BoxedProjectile phead; }) {
Projectile *p = TASK_BIND(ARGS.phead);
clear_projectile(p, CLEAR_HAZARDS_FORCE | CLEAR_HAZARDS_NOW);
}
static cmplx trail_pos(
int t, cmplx origin, cmplx vstart, cmplx vend, real x, real width, real turn_length
) {
cmplx z = t/width + I * x/turn_length;
return origin + width * z * (vstart + (vend - vstart) / (cexp(-z) + 1));
}
TASK(swarm_trail_proj, { cmplx pos; cmplx vstart; cmplx vend; real x; real width; }) {
int t0 = -70;
real turn_length = 1; // |x/turn_length| should be smaller than pi
cmplx prevpos = trail_pos(
t0 - 1, ARGS.pos, ARGS.vstart, ARGS.vend, ARGS.x, ARGS.width, turn_length);
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_rice,
.pos = prevpos,
.color = RGB(0.4, 0, 0.8),
.flags = PFLAG_NOAUTOREMOVE | PFLAG_NOMOVE | PFLAG_MANUALANGLE,
.max_viewport_dist = ARGS.width,
));
BoxedProjectile phead = ENT_BOX(PROJECTILE(
.proto = pp_flea,
.pos = prevpos,
.color = RGB(0.8, 0.1, 0.4),
.flags = PFLAG_NOAUTOREMOVE | PFLAG_NOMOVE | PFLAG_MANUALANGLE,
.max_viewport_dist = ARGS.width,
));
INVOKE_TASK_AFTER(&TASK_EVENTS(THIS_TASK)->finished, swarm_trail_proj_cleanup, phead);
for(int t = t0;; t++) {
Projectile *head = ENT_UNBOX(phead);
if(t == 0) {
p->flags &= ~PFLAG_NOAUTOREMOVE;
if(head) {
head->flags &= ~PFLAG_NOAUTOREMOVE;
}
play_sfx("redirect");
}
p->pos = trail_pos(t, ARGS.pos, ARGS.vstart, ARGS.vend, ARGS.x, ARGS.width, turn_length);
cmplx dpos = p->pos - prevpos;
p->angle = carg(dpos);
if(head) {
head->angle = p->angle;
head->pos = p->pos + 8*cnormalize(dpos);
}
prevpos = p->pos;
YIELD;
}
}
TASK(swarm_trail_fairy, { cmplx pos; MoveParams move; }) {
Enemy *e = TASK_BIND(espawn_big_fairy(ARGS.pos, ITEMS(
.points = 1,
.power = 2,
)));
e->move = ARGS.move;
int shooting_time = 200;
int interval = 10;
int nrow = difficulty_value(3, 5, 5, 7);
real width = 30 * (nrow / 5.0);
WAIT(30);
e->move.retention = 0.9;
WAIT(20);
cmplx aim = cnormalize(global.plr.pos - e->pos);
for(int t = 0; t < shooting_time/interval; t++) {
play_sfx("shot1");
for(int i = 0; i < nrow; i++) {
real x = (i/(real)nrow - 0.5);
INVOKE_TASK(swarm_trail_proj, e->pos, ARGS.move.velocity, 3*aim, x, .width = width);
}
WAIT(interval);
}
play_sfx("redirect");
e->move = move_asymptotic_halflife(0, -ARGS.move.velocity, 120);
}
TASK(swarm_trail_fairy_spawn, { int count; }) {
for(int i = 0; i < ARGS.count; i++) {
cmplx pos = VIEWPORT_W / 2.0;
cmplx vel = 5*I + 4 * cdir(-M_PI * i / (real)(ARGS.count-1));
INVOKE_TASK(swarm_trail_fairy, pos, move_linear(vel));
WAIT(60);
}
}
TASK(flower_swirl_trail, { BoxedEnemy e; }) {
Enemy *e = TASK_BIND(ARGS.e);
for(;;) {
YIELD;
PARTICLE(
.sprite = "smoothdot",
.color = RGBA(2, 1, 0.6, 0),
.pos = e->pos,
.timeout = 30,
.draw_rule = pdraw_timeout_scalefade_exp(1, 2, 1, 0, 2),
);
}
}
static inline MoveParams flower_swirl_move(cmplx dir, real angular_velocity, real radius) {
cmplx retention = 0.99 * cdir(angular_velocity);
return (MoveParams) {
.velocity = radius * cnormalize(dir) * I * cabs(1 - retention),
.retention = retention,
.acceleration = dir * (1 - retention),
};
}
TASK(flower_swirl, { cmplx pos; MoveParams move; }) {
Enemy *e = TASK_BIND(espawn_swirl(ARGS.pos, ITEMS(
.power = 1,
)));
e->move = ARGS.move;
INVOKE_TASK(flower_swirl_trail, ENT_BOX(e));
cmplx twist = cdir(M_TAU/64);
int interval = 20;
int petals = 5;
Color pcolor = *RGBA(1, 0.5, 0, 1);
int orange_cnt = difficulty_value(5, 5, 7, 7);
int red_cnt = difficulty_value(5, 7, 7, 9);
real speed = difficulty_value(1, 1, 1.5, 2);
for(int t = 0;; t++) {
if(t & 1) {
pcolor.g = 0;
petals = red_cnt;
} else {
pcolor.g = 0.5;
petals = orange_cnt;
}
play_sfx("shot2");
cmplx r = cnormalize(e->move.velocity);
for(int i = 0; i < petals; i++) {
cmplx dir = r * cdir(M_TAU / petals * i);
PROJECTILE(
.proto = pp_wave,
.color = &pcolor,
.pos = e->pos - 4 * dir,
.move = move_asymptotic_halflife(speed * 0.7 * dir, speed * -4 * dir * twist, 200),
);
}
WAIT(interval);
}
}
TASK(flower_swirl_spawn, { cmplx pos; MoveParams move; int count; int interval; }) {
for(int i = 0; i < ARGS.count; i++) {
INVOKE_TASK(flower_swirl, ARGS.pos, ARGS.move);
WAIT(ARGS.interval);
}
}
TASK(horde_fairy_motion, { BoxedEnemy e; cmplx velocity; }) {
Enemy *e = TASK_BIND(ARGS.e);
real ofs = rng_angle();
cmplx v = ARGS.velocity;
e->move = move_linear(v);
for(;;YIELD) {
e->move.velocity = v * cdir(M_PI/8 * sin(ofs + global.frames/15.0));
}
}
TASK(horde_fairy, { cmplx pos; cmplx velocity; bool blue; }) {
Enemy *e;
if(ARGS.blue) {
e = TASK_BIND(espawn_fairy_blue(ARGS.pos, ITEMS(
.points = 1,
)));
} else {
e = TASK_BIND(espawn_fairy_red(ARGS.pos, ITEMS(
.power = 1,
)));
}
INVOKE_SUBTASK(horde_fairy_motion, ENT_BOX(e), ARGS.velocity);
int interval = difficulty_value(70, 40, 30, 20);
real speed = difficulty_value(2, 2, 2.5, 3);
real lead = difficulty_value(5, 5, 3.25, 2.75);
real minrange = difficulty_value(80, 60, 40, 40);
int maxshots = difficulty_value(3, 5, 10, 20);
for(int i = 0; i < maxshots; ++i, WAIT(interval)) {
play_sfx("shot1");
cmplx diff = global.plr.pos - e->pos;
if(cabs2(diff) < minrange * minrange) {
continue;
}
cmplx aim = cnormalize(diff);
PROJECTILE(
.proto = pp_plainball,
.pos = e->pos,
.color = RGB(0.4, 0, 1),
.move = move_asymptotic_halflife(0, speed * aim, 25),
);
PROJECTILE(
.proto = pp_flea,
.pos = e->pos,
.color = RGB(0.1, 0.4, 0.8),
.move = move_asymptotic_halflife(0, speed * aim, 25 - lead),
);
}
}
TASK(horde_fairy_spawn, { int count; int interval; cmplx velocity; }) {
real p = 3.0 / ARGS.count;
real w = 0.9*VIEWPORT_W/2;
bool blue = rng_bool();
for(int t = 0; t < ARGS.count; t++) {
INVOKE_TASK(horde_fairy,
.pos = VIEWPORT_W/2 + w * triangle(t * p),
.blue = blue,
.velocity = ARGS.velocity,
);
if(((t+1) % 3) == 0) {
blue = !blue;
}
WAIT(ARGS.interval);
}
}
TASK(circle_twist_fairy_lances, { BoxedEnemy enemy; }) {
Enemy *e = TASK_BIND(ARGS.enemy);
common_charge(120, &e->pos, 0, RGBA(0.6, 0.8, 2, 0));
cmplx offset = rng_dir();
int lance_count = difficulty_value(50, 100, 200, 300);
int lance_segs = difficulty_value(10, 15, 18, 20);
int lance_dirs = difficulty_value(50, 70, 100, 100);
real split = difficulty_value(0, 0.02, 0.03, 0.05);
int fib1 = 1;
int fib2 = 1;
for(int i = 0; i < lance_count; i++) {
play_sfx("shot3");
int tmp = fib1;
fib1 = (fib1 + fib2) % lance_dirs;
fib2 = tmp;
cmplx dir = offset * cdir(M_TAU / lance_dirs * fib1);
for(int j = 0; j < lance_segs; j++) {
int s = 1 - 2 * (j & 1);
cmplx d = dir * cdir(split * s);
PROJECTILE(
.proto = pp_rice,
.pos = e->pos + 20 * d,
.color = RGB(0.3, 0.4, 1),
.move = move_asymptotic_halflife(
(0.5 + (4.5 * j) / lance_segs) * d,
5 * d,
50
),
);
}
WAIT(2);
}
}
TASK(circle_twist_fairy, { cmplx pos; cmplx target_pos; }) {
Enemy *e = TASK_BIND(espawn_super_fairy(ARGS.pos, ITEMS(
.power = 5,
.points = 5,
.bomb_fragment = 1,
)));
e->move = move_from_towards(e->pos, ARGS.target_pos, 0.01);
WAIT(50);
int circle_count = 10;
int count = 50;
int interval = difficulty_value(60, 40, 30, 20);
real twistangle = -0.01 * difficulty_value(1, 1, 0.5, 0.5);
INVOKE_SUBTASK_DELAYED(180, circle_twist_fairy_lances, ENT_BOX(e));
for(int i = 0; i < circle_count; i++) {
int s = 1-2*(i&1);
play_sfx("shot_special1");
for(int t = 0; t < count; t++) {
play_sfx_loop("shot1_loop");
cmplx dir = -I * cdir(s * M_TAU / count * t);
PROJECTILE(
.proto = pp_wave,
.pos = e->pos,
.color = RGBA(1.5, -2.3, 0, 0),
.move = {
.velocity = (2 + 0.005 * t) * dir,
.acceleration = -0.005 * dir,
.retention = 1.0 * cdir(twistangle * s)
}
);
YIELD;
}
WAIT(interval);
}
e->move = move_asymptotic_halflife(0, 2 * I, 20);
}
TASK(laserball, { cmplx origin; cmplx velocity; Color *color; real freq_factor; }) {
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_bigball,
.pos = ARGS.origin,
.move = move_asymptotic_halflife(ARGS.velocity, 0, 15),
.color = ARGS.color,
));
WAIT(60);
real speed = difficulty_value(2, 2.5, 3, 4);
real amp = 0.01 * difficulty_value(4, 8, 8, 10);
real freq = (speed / 4) * 0.25 * ARGS.freq_factor;
cmplx lv = speed * -cnormalize(ARGS.velocity);
real lt = 20;
real dt = 300;
int charges = 4;
int delay = difficulty_value(30, 20, 20, 20);
int refire = 20;
real phase = 0;
real scale = 1;
real scale_per_charge = 1 / (real)charges;
real scale_per_tick = scale_per_charge / delay;
cmplx orig_collision = p->collision_size;
cmplx orig_scale = p->scale;
for(int i = 0;; ++i) {
play_sfx("laser1");
PROJECTILE(
.pos = p->pos,
.proto = pp_flea,
.timeout = 1,
.color = RGBA(1, 1, 1, 0),
);
create_laser(p->pos, lt, dt, &p->color,
laser_rule_sine_expanding(lv, amp, freq, i * phase));
for(int t = 0; t < delay; ++t) {
YIELD;
scale -= scale_per_tick;
p->collision_size = orig_collision * scale;
p->scale = orig_scale * scale;
}
if(i == charges - 1) {
break;
}
WAIT(refire);
// phase += 3.2;
freq *= -1;
}
kill_projectile(p);
}
TASK(laserball_fairy, { cmplx pos; real freq_factor; }) {
Enemy *e = TASK_BIND(espawn_huge_fairy(ARGS.pos, ITEMS(
.power = 5,
.points = 5,
)));
ecls_anyfairy_summon(e, 120);
WAIT(30);
common_charge(90, &e->pos, 0, RGBA(0.5, 1, 0.25, 0));
int balls = 6;
int cycles = difficulty_value(1, 1, 2, 2);
for(int c = 0; c < cycles; ++c) {
play_sfx("shot1");
play_sfx("warp");
for(int i = 0; i < balls; ++i) {
cmplx v = 3 * cdir(i * (M_TAU / balls));
INVOKE_TASK(laserball, e->pos, v, RGBA(0.2, 2, 0.4, 0), ARGS.freq_factor);
}
WAIT(difficulty_value(240, 240, 120, 120));
play_sfx("shot1");
play_sfx("warp");
for(int i = 0; i < balls; ++i) {
cmplx v = 3 * cdir((i + 0.5) * (M_TAU / balls));
INVOKE_TASK(laserball, e->pos, v, RGBA(2, 1, 0.4, 0), -ARGS.freq_factor);
}
WAIT(180);
}
e->move = move_asymptotic_halflife(e->move.velocity, 3*I, 60);
}
TASK(bulletring, { BoxedEnemy owner; int num; real radius; real speed; }) {
int num_bullets = ARGS.num;
DECLARE_ENT_ARRAY(Projectile, bullets, num_bullets);
play_sfx("shot_special1");
play_sfx("warp");
real angular_velocity = ARGS.speed / ARGS.radius;
Enemy *e = NOT_NULL(ENT_UNBOX(ARGS.owner));
e->max_viewport_dist = max(e->max_viewport_dist, ARGS.radius);
real r = 1;
real a = 0;
for(int i = 0; i < num_bullets; ++i) {
ENT_ARRAY_ADD(&bullets, PROJECTILE(
.proto = pp_rice,
.color = RGB(0.4, 0, 0.8),
.flags = PFLAG_MANUALANGLE,
.move = move_linear(0),
.pos = e->pos,
.max_viewport_dist = ARGS.radius,
));
}
cmplx opos = e->pos;
for(;;YIELD) {
if(!(e = ENT_UNBOX(ARGS.owner))) {
break;
}
ENT_ARRAY_FOREACH_COUNTER(&bullets, int i, Projectile *p, {
real angle = a + (M_TAU*i)/num_bullets;
cmplx npos = e->pos + (r * (1 + 0.03 * sin(3.6 * a + 6 * angle))) * cdir(angle);
p->move.velocity = npos - p->pos;
p->angle = carg(p->move.velocity + opos - e->pos);
});
opos = e->pos;
a += angular_velocity;
approach_asymptotic_p(&r, ARGS.radius, 0.05, 1e-4);
}
ENT_ARRAY_FOREACH(&bullets, Projectile *p, {
// p->move.velocity = ARGS.speed * cdir(p->angle);
p->angle = carg(p->move.velocity);
p->move = move_asymptotic_halflife(p->move.velocity, cnormalize(p->move.velocity) * fabs(ARGS.speed), 60);
});
}
TASK(bulletring_fairy, { cmplx pos; MoveParams move; }) {
Enemy *e = TASK_BIND(espawn_huge_fairy(ARGS.pos, ITEMS(
.power = 3,
.points = 5,
)));
e->move = ARGS.move;
int bullet_rings = difficulty_value(2, 3, 4, 4);
for(int i = 0; i < bullet_rings; ++i) {
WAIT(60);
real rad = 64 + 16 * i;
real spacing = 2;
int bullets = round(rad / spacing);
INVOKE_TASK(bulletring, ENT_BOX(e), bullets, rad, ((i&1) ? 1 : -1) * 2);
}
}
TASK(bulletring_fairy_spawn, { int count; }) {
for(int i = 0; i < ARGS.count; ++i) {
real d = 120;
cmplx o[] = { d, VIEWPORT_W - d };
cmplx p = o[i % ARRAY_SIZE(o)];
INVOKE_TASK(bulletring_fairy, p, move_linear(I));
WAIT(120);
}
}
// bosses
TASK_WITH_INTERFACE(midboss_intro, BossAttack) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
boss->move = move_from_towards(boss->pos, VIEWPORT_W/2 + 100.0*I, 0.04);
}
TASK_WITH_INTERFACE(midboss_outro, BossAttack) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
for(int t = 0;; t++) {
boss->pos += t*t/900.0 * cdir(3*M_PI/2 + 0.5 * sin(t / 20.0));
YIELD;
}
}
TASK(spawn_midboss) {
STAGE_BOOKMARK_DELAYED(120, midboss);
Boss *boss = global.boss = stage3_spawn_scuttle(VIEWPORT_W/2 - 200.0*I);
glm_vec3_copy((vec3) { -10, -20, -30 }, stage3_get_draw_data()->boss_light.radiance);
boss_add_attack_task(boss, AT_Move, "Introduction", 1, 0, TASK_INDIRECT(BossAttack, midboss_intro), NULL);
boss_add_attack_task(boss, AT_Normal, "Lethal Bite", 11, 20000, TASK_INDIRECT(BossAttack, stage3_midboss_nonspell_1), NULL);
boss_add_attack_from_info(boss, &stage3_spells.mid.deadly_dance, false);
boss_add_attack_task(boss, AT_Move, "Runaway", 2, 1, TASK_INDIRECT(BossAttack, midboss_outro), NULL);
boss_engage(boss);
}
TASK(boss_appear, { BoxedBoss boss; }) {
Boss *boss = TASK_BIND(ARGS.boss);
boss->move = move_from_towards(boss->pos, VIEWPORT_W/2.0 + 100.0*I, 0.05);
}
TASK(spawn_boss) {
STAGE_BOOKMARK_DELAYED(120, boss);
stage_unlock_bgm("stage3");
Boss *boss = global.boss = stage3_spawn_wriggle(VIEWPORT_W/2 - 200.0*I);
glm_vec3_copy((vec3) { 50, 30, 80 }, stage3_get_draw_data()->boss_light.radiance);
PlayerMode *pm = global.plr.mode;
Stage3PreBossDialogEvents *e;
INVOKE_TASK_INDIRECT(Stage3PreBossDialog, pm->dialog->Stage3PreBoss, &e);
INVOKE_TASK_WHEN(&e->boss_appears, boss_appear, ENT_BOX(boss));
INVOKE_TASK_WHEN(&e->music_changes, common_start_bgm, "stage3boss");
WAIT_EVENT(&global.dialog->events.fadeout_began);
boss_add_attack_task(boss, AT_Normal, "", 11, 35000, TASK_INDIRECT(BossAttack, stage3_boss_nonspell_1), NULL);
boss_add_attack_from_info(boss, &stage3_spells.boss.moonlight_rocket, false);
boss_add_attack_task(boss, AT_Normal, "", 40, 35000, TASK_INDIRECT(BossAttack, stage3_boss_nonspell_2), NULL);
boss_add_attack_from_info(boss, &stage3_spells.boss.moths_to_a_flame, false);
boss_add_attack_task(boss, AT_Normal, "", 40, 35000, TASK_INDIRECT(BossAttack, stage3_boss_nonspell_3), NULL);
boss_add_attack_from_info(boss, &stage3_spells.boss.firefly_storm, false);
boss_add_attack_from_info(boss, &stage3_spells.extra.light_singularity, false);
boss_engage(boss);
}
TASK(flower_swirls_alternating) {
int interval = 20;
int cnt = 4;
int altinterval = interval * cnt * 2;
INVOKE_TASK(flower_swirl_spawn,
.pos = VIEWPORT_W + 10 + 160 * I,
.move = flower_swirl_move(-2 + 0.0 * I, -0.09, 120),
.count = cnt,
.interval = interval,
);
WAIT(altinterval);
INVOKE_TASK(flower_swirl_spawn,
.pos = - 10 + 160 * I,
.move = flower_swirl_move(2 + 0.0 * I, -0.09, 120),
.count = 4,
.interval = interval,
);
WAIT(altinterval);
INVOKE_TASK(flower_swirl_spawn,
.pos = VIEWPORT_W + 10 + 160 * I,
.move = flower_swirl_move(-1 + 1.0 * I, -0.09, 120),
.count = cnt,
.interval = interval,
);
WAIT(altinterval);
INVOKE_TASK(flower_swirl_spawn,
.pos = - 10 + 160 * I,
.move = flower_swirl_move(1 + 1.0 * I, -0.09, 120),
.count = 4,
.interval = interval,
);
INVOKE_TASK(flower_swirl_spawn,
.pos = VIEWPORT_W + 10 + 160 * I,
.move = flower_swirl_move(-2 + 0.0 * I, -0.09, 120),
.count = cnt,
.interval = interval,
);
}
TASK(horde_fairies_intertwined, { int count; int interval; }) {
for(int i = 0; i < ARGS.count; ++i) {
INVOKE_TASK(horde_fairy,
.pos = -20 + 60*I,
.velocity = 1,
);
INVOKE_TASK(horde_fairy,
.pos = VIEWPORT_W-+0 + 60*I,
.velocity = -1,
.blue = 1
);
WAIT(ARGS.interval);
}
}
static void welcome_swirls(void) {
int interval = 60;
int lr_stagger = 0;
int ud_stagger = interval / 2;
INVOKE_TASK_DELAYED(0, flower_swirl_spawn,
.pos = VIEWPORT_W + 10 + 200 * I,
.move = flower_swirl_move(-3 + 0.5 * I, -0.1, 20),
.count = 12,
.interval = interval,
);
INVOKE_TASK_DELAYED(ud_stagger, flower_swirl_spawn,
.pos = VIEWPORT_W + 10 + 200 * I,
.move = flower_swirl_move(-3 - 1 * I, 0.1, 20),
.count = 12,
.interval = interval,
);
INVOKE_TASK_DELAYED(lr_stagger, flower_swirl_spawn,
.pos = -10 + 200 * I,
.move = flower_swirl_move(3 + 0.5 * I, 0.1, 20),
.count = 12,
.interval = interval,
);
INVOKE_TASK_DELAYED(lr_stagger + ud_stagger, flower_swirl_spawn,
.pos = -10 + 200 * I,
.move = flower_swirl_move(3 - 1 * I, -0.1, 20),
.count = 12,
.interval = interval,
);
}
TASK(set_swing, { float v; }) {
Stage3DrawData *dd = stage3_get_draw_data();
dd->target_swing_strength = ARGS.v;
}
DEFINE_EXTERN_TASK(stage3_timeline) {
stage_start_bgm("stage3");
stage_set_voltage_thresholds(50, 125, 300, 600);
welcome_swirls();
INVOKE_TASK_DELAYED(400, swarm_trail_fairy_spawn, 5);
STAGE_BOOKMARK_DELAYED(800, horde);
INVOKE_TASK_DELAYED(800, horde_fairy_spawn,
.count = difficulty_value(20, 20, 30, 30),
.interval = 20,
.velocity = 2*I,
);
INVOKE_TASK_DELAYED(880, laserball_fairy,
.pos = 3*VIEWPORT_W/4 + 200*I,
.freq_factor = 1,
);
STAGE_BOOKMARK_DELAYED(1300, circle-twist);
INVOKE_TASK_DELAYED(1300, circle_twist_fairy,
.pos = 0,
.target_pos = VIEWPORT_W/2.0 + I*VIEWPORT_H/3.0,
);
if(global.diff > D_Normal) {
INVOKE_TASK_DELAYED(1700, laserball_fairy,
.pos = VIEWPORT_W/3 + 140*I,
.freq_factor = 1,
);
INVOKE_TASK_DELAYED(1700, laserball_fairy,
.pos = 2*VIEWPORT_W/3 + 140*I,
.freq_factor = -1,
);
} else {
INVOKE_TASK_DELAYED(1800, swarm_trail_fairy, -20 + 140*I, move_linear( 9));
INVOKE_TASK_DELAYED(1860, swarm_trail_fairy, VIEWPORT_W+20 + 110*I, move_linear(-9));
}
INVOKE_TASK_DELAYED(1900, flower_swirls_alternating);
INVOKE_TASK_DELAYED(2390, swarm_trail_fairy, VIEWPORT_W+20 + VIEWPORT_H*0.24*I, move_linear(-9));
INVOKE_TASK_DELAYED(2450, swarm_trail_fairy, -20 + VIEWPORT_H*0.32*I, move_linear( 9));
STAGE_BOOKMARK_DELAYED(2500, pre-midboss);
INVOKE_TASK_DELAYED(2700, spawn_midboss);
while(!global.boss) YIELD;
WAIT_EVENT(&global.boss->events.defeated);
STAGE_BOOKMARK(post-midboss);
INVOKE_TASK(bulletring_fairy_spawn,
.count = 10,
);
INVOKE_TASK_DELAYED(60, set_swing, 1.0f);
INVOKE_TASK_DELAYED(300, horde_fairy_spawn,
.count = 20,
.interval = 30,
.velocity = I,
);
if(global.diff > D_Easy) {
INVOKE_TASK_DELAYED(900, common_call_func, welcome_swirls);
}
INVOKE_TASK_DELAYED(1200, circle_twist_fairy,
.pos = VIEWPORT_W/2.0 - 40*I,
.target_pos = VIEWPORT_W/2.0 + I*VIEWPORT_H/3.0,
);
INVOKE_TASK_DELAYED(1600, laserball_fairy,
.pos = VIEWPORT_W/3 + 140*I,
.freq_factor = -1,
);
INVOKE_TASK_DELAYED(1600, laserball_fairy,
.pos = 2*VIEWPORT_W/3 + 140*I,
.freq_factor = 1,
);
INVOKE_TASK_DELAYED(2080, horde_fairies_intertwined,
.count = 10,
.interval = 60,
);
INVOKE_TASK_DELAYED(2240, laserball_fairy,
.pos = VIEWPORT_W/4 + 240*I,
.freq_factor = -1,
);
INVOKE_TASK_DELAYED(2240, laserball_fairy,
.pos = 3*VIEWPORT_W/4 + 240*I,
.freq_factor = 1,
);
INVOKE_TASK_DELAYED(2880, swarm_trail_fairy,
.pos = -40 + (VIEWPORT_H/2 + 20) * I,
.move = {
.velocity = 10,
.retention = 1/cdir(M_TAU/256),
},
);
INVOKE_TASK_DELAYED(2980, swarm_trail_fairy,
.pos = VIEWPORT_W+40 + (VIEWPORT_H/2 + 20) * I,
.move = {
.velocity = -10,
.retention = cdir(M_TAU/256),
},
);
STAGE_BOOKMARK_DELAYED(2950, pre-boss);
INVOKE_TASK_DELAYED(3200, set_swing, 0.2f);
WAIT(3450);
stage_unlock_bgm("stage3boss");
INVOKE_TASK(spawn_boss);
while(!global.boss) YIELD;
WAIT_EVENT(&global.boss->events.defeated);
WAIT(240);
stage3_dialog_post_boss();
WAIT_EVENT(&global.dialog->events.fadeout_began);
WAIT(5);
stage_finish(GAMEOVER_SCORESCREEN);
}