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

800 lines
21 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 "background_anim.h"
#include "cirno.h"
#include "nonspells/nonspells.h" // IWYU pragma: keep
#include "spells/spells.h" // IWYU pragma: keep
#include "stage1.h"
#include "stages/common_imports.h"
TASK(burst_fairy, { BoxedEnemy e; cmplx target_pos; cmplx exit_dir; }) {
Enemy *e = TASK_BIND(ARGS.e);
e->move = move_from_towards(e->pos, ARGS.target_pos, 0.03);
WAIT(difficulty_value(120, 80, 60, 60));
play_sfx("shot1");
int n = difficulty_value(0, 1, 3, 5);
for(int i = -n; i <= n; i++) {
cmplx aim = cdir(carg(global.plr.pos - e->pos) + 0.2 * i);
PROJECTILE(
.proto = pp_crystal,
.pos = e->pos,
.color = RGB(0.2, 0.3, 0.5),
.move = move_asymptotic_simple(aim * (2 + 0.5 * global.diff), 5),
);
}
WAIT(1);
e->move.attraction = 0;
e->move.acceleration = 0.04 * ARGS.exit_dir;
e->move.retention = 1;
}
TASK(circletoss_shoot_circle, { BoxedEnemy e; int duration; int interval; }) {
Enemy *e = TASK_BIND(ARGS.e);
int cnt = ARGS.duration / ARGS.interval;
double angle_step = M_TAU / cnt;
for(int i = 0; i < cnt; ++i) {
play_sfx_loop("shot1_loop");
e->move.velocity *= 0.8;
cmplx aim = cdir(angle_step * i);
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.color = RGB(0.6, 0.2, 0.7),
.move = move_asymptotic_simple(2 * aim, i * 0.5),
);
WAIT(ARGS.interval);
}
}
TASK(circletoss_shoot_toss, { BoxedEnemy e; int times; int duration; int period; }) {
Enemy *e = TASK_BIND(ARGS.e);
real spread = difficulty_value(0.05, 0.1, 0.15, 0.2);
while(ARGS.times--) {
for(int i = ARGS.duration; i--;) {
play_sfx_loop("shot1_loop");
real aim_angle = carg(global.plr.pos - e->pos) + spread * 0.5 * rng_sreal();
cmplx aim = cdir(aim_angle) * rng_range(1, 3);
PROJECTILE(
.proto = pp_thickrice,
.pos = e->pos,
.color = RGB(0.2, 0.4, 0.8),
.move = move_asymptotic_simple(aim, 3),
);
WAIT(1);
}
WAIT(ARGS.period - ARGS.duration);
}
}
TASK(circletoss_fairy, { cmplx pos; cmplx velocity; cmplx exit_accel; int exit_time; }) {
Enemy *e = TASK_BIND(espawn_big_fairy(ARGS.pos, ITEMS(.points = 3, .power = 1)));
e->move = move_linear(ARGS.velocity);
ARGS.exit_time -= WAIT(difficulty_value(80, 80, 60, 60));
INVOKE_SUBTASK(circletoss_shoot_circle, ENT_BOX(e),
.duration = 40,
.interval = difficulty_value(3, 3, 2, 2)
);
if(global.diff > D_Easy) {
INVOKE_SUBTASK_DELAYED(30, circletoss_shoot_toss, ENT_BOX(e),
.times = 4,
.period = 150,
.duration = difficulty_value(-1, 15, 26, 33)
);
}
WAIT(ARGS.exit_time);
e->move.acceleration += ARGS.exit_accel;
STALL;
}
TASK(sinepass_swirl_move, { BoxedEnemy e; cmplx v; cmplx sv; }) {
Enemy *e = TASK_BIND(ARGS.e);
cmplx sv = ARGS.sv;
cmplx v = ARGS.v;
for(;;) {
sv -= im(e->pos - e->pos0) * 0.03 * I;
e->pos += sv * 0.4 + v;
YIELD;
}
}
TASK(sinepass_swirl, { cmplx pos; cmplx vel; cmplx svel; }) {
Enemy *e = TASK_BIND(espawn_swirl(ARGS.pos, ITEMS(.points = 1)));
INVOKE_TASK(sinepass_swirl_move, ENT_BOX(e), ARGS.vel, ARGS.svel);
WAIT(difficulty_value(40, 40, 15, 10));
int shot_interval = difficulty_value(155, 80, 30, 20);
for(;;) {
play_sfx("shot1");
cmplx aim = cnormalize(global.plr.pos - e->pos);
aim *= difficulty_value(2, 2, 2.5, 3);
PROJECTILE(
.proto = pp_ball,
.pos = e->pos,
.color = RGB(0.8, 0.8, 0.4),
.move = move_asymptotic_simple(aim, 5),
);
WAIT(shot_interval);
}
}
TASK(circle_fairy, { cmplx pos; cmplx target_pos; }) {
Enemy *e = TASK_BIND(espawn_fairy_red(ARGS.pos, ITEMS(.power = 2)));
e->move = move_from_towards(e->pos, ARGS.target_pos, 0.005);
e->move.retention = 0.8;
WAIT(60);
int shot_interval = 2;
int shot_count = difficulty_value(10, 10, 20, 25);
int round_interval = 120 - shot_interval * shot_count;
for(int round = 0; round < 2; ++round) {
double a_ofs = rng_angle();
for(int i = 0; i < shot_count; ++i) {
cmplx aim;
aim = circle_dir_ofs((round & 1) ? i : shot_count - i, shot_count, a_ofs);
aim *= difficulty_value(1.7, 2.0, 2.5, 2.5);
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.color = RGB(0.6, 0.2, 0.7),
.move = move_asymptotic_simple(aim, i * 0.5),
);
play_sfx_loop("shot1_loop");
WAIT(shot_interval);
}
e->move.attraction_point += 30 * rng_dir();
WAIT(round_interval);
}
WAIT(10);
e->move.attraction = 0;
e->move.retention = 1;
e->move.acceleration = -0.04 * I * cdir(rng_range(0, M_TAU / 12));
STALL;
}
TASK(drop_swirl, { cmplx pos; cmplx vel; cmplx accel; }) {
Enemy *e = TASK_BIND(espawn_swirl(ARGS.pos, ITEMS(.points = 2)));
e->move = move_accelerated(ARGS.vel, ARGS.accel);
int shot_interval = difficulty_value(120, 120, 30, 20);
WAIT(difficulty_value(50, 35, 20, 20));
while(true) {
cmplx aim = cnormalize(global.plr.pos - e->pos);
aim *= 1 + 0.3 * global.diff + rng_real();
play_sfx("shot1");
PROJECTILE(
.proto = pp_ball,
.pos = e->pos,
.color = RGB(0.8, 0.8, 0.4),
.move = move_linear(aim),
);
WAIT(shot_interval);
}
}
TASK(multiburst_fairy, { BoxedEnemy e; cmplx target_pos; cmplx exit_accel; }) {
Enemy *e = TASK_BIND(ARGS.e);
e->move = move_from_towards(e->pos, ARGS.target_pos, 0.05);
WAIT(difficulty_value(120, 60, 60, 60));
int burst_interval = difficulty_value(40, 30, 18, 16);
int bursts = 4;
for(int i = 0; i < bursts; ++i) {
play_sfx("shot1");
int n = difficulty_value(0, 0, 2, 3);
for(int j = -n; j <= n; j++) {
cmplx aim = cdir(carg(global.plr.pos - e->pos) + j / 5.0);
aim *= 2.5;
PROJECTILE(
.proto = pp_crystal,
.pos = e->pos,
.color = RGB(0.2, 0.3, 0.5),
.move = move_linear(aim),
);
}
WAIT(burst_interval);
}
WAIT(10);
e->move.attraction = 0;
e->move.retention = 1;
e->move.acceleration = ARGS.exit_accel;
}
TASK(instantcircle_fairy_shoot, { BoxedEnemy e; int cnt; double speed; double boost; }) {
Enemy *e = TASK_BIND(ARGS.e);
play_sfx("shot_special1");
for(int i = 0; i < ARGS.cnt; ++i) {
cmplx vel = ARGS.speed * circle_dir(i, ARGS.cnt);
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.color = RGB(0.6, 0.2, 0.7),
.move = move_asymptotic_simple(vel, ARGS.boost),
);
}
}
TASK(instantcircle_fairy, { cmplx pos; cmplx target_pos; cmplx exit_accel; }) {
Enemy *e = TASK_BIND(espawn_big_fairy(ARGS.pos, ITEMS(.points = 4, .power = 2)));
e->move = move_from_towards(e->pos, ARGS.target_pos, 0.04);
BoxedEnemy be = ENT_BOX(e);
INVOKE_TASK_DELAYED(75, instantcircle_fairy_shoot, be,
.cnt = difficulty_value(20, 22, 24, 28),
.speed = 1.5,
.boost = 2.0
);
if(global.diff > D_Easy) {
INVOKE_TASK_DELAYED(95, instantcircle_fairy_shoot, be,
.cnt = difficulty_value(0, 26, 29, 32),
.speed = difficulty_value(0, 2.0, 3.0, 3.0),
.boost = 3.0
);
}
WAIT(200);
e->move.attraction = 0;
e->move.retention = 1;
e->move.acceleration = ARGS.exit_accel;
}
TASK(waveshot, { cmplx pos; cmplx dir; real spread; real freq; int shots; int interval; } ) {
for(int i = 0; i < ARGS.shots; ++i) {
cmplx v = 4 * ARGS.dir * cdir(ARGS.spread * triangle(ARGS.freq * i));
play_sfx_loop("shot1_loop");
PROJECTILE(
.proto = pp_thickrice,
.pos = ARGS.pos,
.color = RGBA(0.0, 0.5 * (1.0 - i / (ARGS.shots - 1.0)), 1.0, 1),
.move = move_asymptotic(-4 * v, v, 0.9),
// .move = move_accelerated(-v, 0.02 * v),
);
WAIT(ARGS.interval);
}
}
TASK(waveshot_fairy, { cmplx pos; cmplx exit_accel; }) {
Enemy *e = TASK_BIND(espawn_big_fairy(ARGS.pos, ITEMS(.points = 4, .power = 2)));
ecls_anyfairy_summon(e, 120);
cmplx dir = cnormalize(global.plr.pos - e->pos);
cmplx ofs = -24 * dir;
common_charge(60, &e->pos, ofs, RGBA(0.0, 0.25, 0.5, 0));
real spread = difficulty_value(M_PI/20, M_PI/18, M_PI/16, M_PI/14);
real interval = difficulty_value(3, 2, 1, 1);
real shots = 60 / interval;
real frequency = 60 * (1.0/12.0) / shots;
shots += 1;
INVOKE_SUBTASK(waveshot, e->pos + ofs, dir, rng_sign() * spread, frequency, shots, interval);
WAIT(120);
e->move.attraction = 0;
e->move.retention = 0.8;
e->move.acceleration = ARGS.exit_accel;
}
TASK(explosion_fairy, { cmplx pos; cmplx exit_accel; }) {
Enemy *e = TASK_BIND(espawn_huge_fairy(ARGS.pos, ITEMS(.points = 8)));
INVOKE_SUBTASK_DELAYED(80, common_charge, {
.time = 120,
.pos = e->pos,
.color = RGBA(1.0, 0, 0.2, 0),
.sound = COMMON_CHARGE_SOUNDS,
});
ecls_anyfairy_summon(e, 120);
WAIT(80);
int cnt = difficulty_value(30, 30, 60, 60);
int trails = difficulty_value(0, 2, 3, 4);
real speed = difficulty_value(2, 2, 4, 4);
real ofs = rng_angle();
play_sfx("shot_special1");
for(int i = 0; i < cnt; ++i) {
cmplx aim = speed * circle_dir_ofs(i, cnt, ofs);
real s = 0.5 + 0.5 * triangle(0.1 * i);
Color clr;
if(s == 1) {
clr = *RGB(1, 0, 0);
} else {
clr = *color_lerp(
RGB(0.1, 0.6, 1.0),
RGB(1.0, 0.0, 0.3),
s * s
);
color_mul(&clr, &clr);
}
PROJECTILE(
.proto = s == 1 ? pp_bigball : pp_ball,
.pos = e->pos,
.color = &clr,
.move = move_asymptotic_simple(aim, 1 + 8 * s),
);
for(int j = 0; j < trails; ++j) {
aim *= 0.8;
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.color = &clr,
.move = move_asymptotic_simple(aim, 1 + 8 * s),
);
}
}
WAIT(10);
e->move.attraction = 0;
e->move.retention = 0.8;
e->move.acceleration = ARGS.exit_accel;
}
// opening. projectile bursts
TASK(burst_fairies_1) {
for(int i = 3; i--;) {
INVOKE_TASK(burst_fairy,
.e = espawn_fairy_blue_box(VIEWPORT_W/2 + 70, ITEMS(.points = 1)),
.target_pos = VIEWPORT_W/2 + 70 + (120 + 42 * i)*I,
.exit_dir = 1 + 0.6*I
);
INVOKE_TASK(burst_fairy,
.e = espawn_fairy_red_box(VIEWPORT_W/2 - 70, ITEMS(.power = 1)),
.target_pos = VIEWPORT_W/2 - 70 + (120 + 42 * i)*I,
.exit_dir = -1 + 0.6*I
);
WAIT(25);
}
}
// more bursts. fairies move \ / like
TASK(burst_fairies_2) {
for(int i = 3; i--;) {
real ofs = 70 + i * 40;
EnemySpawner s0, s1;
ItemCounts i0, i1;
if(i & 1) {
s0 = espawn_fairy_red;
i0 = *ITEMS(.power = 1);
s1 = espawn_fairy_blue;
i1 = *ITEMS(.points = 1);
} else {
s0 = espawn_fairy_blue;
i0 = *ITEMS(.points = 1);
s1 = espawn_fairy_red;
i1 = *ITEMS(.power = 1);
}
cmplx p0 = ofs;
cmplx p1 = VIEWPORT_W - ofs;
INVOKE_TASK(burst_fairy, ENT_BOX(s0(p0, &i0)), p0 + 120*I, 0.6*I + 1);
WAIT(15);
INVOKE_TASK(burst_fairy, ENT_BOX(s1(p1, &i1)), p1 + 120*I, 0.6*I - 1);
WAIT(15);
}
}
TASK(burst_fairies_3) {
for(int i = 10; i--;) {
cmplx pos = VIEWPORT_W/2 - 200 * sin(1.17 * global.frames);
INVOKE_TASK(burst_fairy,
(i & 1)
? espawn_fairy_blue_box(pos, ITEMS(.points = 1))
: espawn_fairy_red_box(pos, ITEMS(.power = 1)),
pos + 120*I, rng_sign()
);
WAIT(60);
}
}
// swirl, sine pass
TASK(sinepass_swirls, { int duration; real level; real dir; }) {
int duration = ARGS.duration;
real dir = ARGS.dir;
cmplx pos = CMPLX(ARGS.dir < 0 ? VIEWPORT_W : 0, ARGS.level);
int delay = difficulty_value(30, 30, 15, 10);
for(int t = 0; t < duration; t += delay) {
INVOKE_TASK(sinepass_swirl, pos, 3.5 * dir, 7.0 * I);
WAIT(delay);
}
}
// big fairies, circle + projectile toss
TASK(circletoss_fairies_1) {
for(int i = 0; i < 2; ++i) {
INVOKE_TASK(circletoss_fairy,
.pos = VIEWPORT_W * i + VIEWPORT_H / 3 * I,
.velocity = 2 - 4 * i - 0.3 * I,
.exit_accel = 0.03 * (1 - 2 * i) - 0.04 * I ,
.exit_time = (global.diff > D_Easy) ? 500 : 240
);
WAIT(250);
}
}
TASK(drop_swirls, { int cnt; cmplx pos; cmplx vel; cmplx accel; }) {
for(int i = 0; i < ARGS.cnt; ++i) {
INVOKE_TASK(drop_swirl, ARGS.pos, ARGS.vel, ARGS.accel);
WAIT(20);
}
}
TASK(schedule_swirls) {
INVOKE_TASK(drop_swirls, 25, VIEWPORT_W/3, 2*I, 0.06);
WAIT(400);
INVOKE_TASK(drop_swirls, 25, 200*I, 4, -0.06*I);
}
TASK(circle_fairies_1) {
for(int i = 0; i < 3; ++i) {
for(int j = 0; j < 3; ++j) {
INVOKE_TASK(circle_fairy, VIEWPORT_W - 64, VIEWPORT_W/2 - 100 + 200 * I + 128 * j);
WAIT(60);
}
WAIT(90);
for(int j = 0; j < 3; ++j) {
INVOKE_TASK(circle_fairy, 64, VIEWPORT_W/2 + 100 + 200 * I - 128 * j);
WAIT(60);
}
WAIT(240);
}
}
TASK(multiburst_fairies_1) {
int rows = difficulty_value(1, 2, 3, 3);
int row_interval = difficulty_value(0, 160, 120, 120);
int cols = 5;
int col_interval = difficulty_value(30, 10, 10, 10);
for(int row = 0; row < rows; ++row) {
for(int col = 0; col < cols; ++col) {
cmplx pos = rng_range(0, VIEWPORT_W);
cmplx target_pos = 64 + 64 * col + I * (64 * row + 100);
cmplx exit_accel = 0.02 * I + 0.03;
Enemy *e;
if(row & 1) {
e = espawn_fairy_red(pos, ITEMS(.power = 2));
} else {
e = espawn_fairy_blue(pos, ITEMS(.points = 3));
}
INVOKE_TASK(multiburst_fairy, ENT_BOX(e), target_pos, exit_accel);
WAIT(col_interval);
}
WAIT(row_interval);
}
}
TASK(instantcircle_fairies, { int duration; int seq_ofs; }) {
int interval = difficulty_value(160, 130, 100, 70);
real xofs[] = { 0.32, -0.85, 0.93, -0.42, -0.9 };
real yofs[] = { -0.69, -0.93, 0.80, 0.21, -0.96, -0.53, 0.95 };
for(int t = ARGS.duration, i = ARGS.seq_ofs; t > 0; t -= interval, ++i) {
log_debug("i = %i; x=%f, y=%f", i, xofs[i % ARRAY_SIZE(xofs)], yofs[i % ARRAY_SIZE(yofs)]);
real x = VIEWPORT_W/2 + 205 * xofs[i % ARRAY_SIZE(xofs)];
real y = VIEWPORT_H/2 + 100 * yofs[i % ARRAY_SIZE(yofs)];
INVOKE_TASK(instantcircle_fairy, x, x+y*I, 0.2 * I);
WAIT(interval);
}
}
TASK(waveshot_fairies, { int duration; }) {
int interval = 200;
for(int t = ARGS.duration; t > 0; t -= interval) {
real x = VIEWPORT_W/2 + round(rng_sreal() * 69);
real y = rng_range(200, 240);
INVOKE_TASK(waveshot_fairy, x+y*I, 0.15 * I);
WAIT(interval);
}
}
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.0 + 200.0*I, 0.035);
}
TASK_WITH_INTERFACE(midboss_flee, BossAttack) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
boss->move = move_from_towards(boss->pos, -250 + 30 * I, 0.02);
}
TASK(spawn_midboss) {
STAGE_BOOKMARK_DELAYED(120, midboss);
Boss *boss = global.boss = stage1_spawn_cirno(VIEWPORT_W + 220 + 30.0*I);
boss_add_attack_task(boss, AT_Move, "Introduction", 2, 0, TASK_INDIRECT(BossAttack, midboss_intro), NULL);
boss_add_attack_task(boss, AT_Normal, "Icy Storm", 20, 24000, TASK_INDIRECT(BossAttack, stage1_midboss_nonspell_1), NULL);
if(global.diff > D_Normal) {
boss_add_attack_from_info(boss, &stage1_spells.mid.perfect_freeze, false);
}
boss_add_attack_task(boss, AT_Move, "Flee", 2, 0, TASK_INDIRECT(BossAttack, midboss_flee), NULL);
boss_engage(boss);
WAIT(60);
stage1_bg_enable_snow();
}
TASK(tritoss_fairy, { cmplx pos; cmplx end_velocity; }) {
Enemy *e = TASK_BIND(espawn_super_fairy(ARGS.pos, ITEMS(.points = 5, .power = 6)));
INVOKE_SUBTASK_DELAYED(120, common_charge, {
.pos = e->pos,
.time = 60,
.color = RGBA(0.1, 0.2, 1.0, 0),
.sound = COMMON_CHARGE_SOUNDS,
});
ecls_anyfairy_summon(e, 180);
int interval = difficulty_value(12, 9, 5, 3);
int rounds = 680/interval;
for(int k = 0; k < rounds; k++) {
play_sfx("shot1");
float a = M_PI / 30.0 * ((k/7) % 30) + 0.1 * rng_f32();
int n = difficulty_value(3,4,4,5);
for(int i = 0; i < n; i++) {
PROJECTILE(
.proto = pp_thickrice,
.pos = e->pos,
.color = RGB(0.2, 0.4, 0.8),
.move = move_asymptotic_simple(2*cdir(a+2.0*M_PI/n*i), 3),
);
}
if(k == rounds/2 || k == rounds-1) {
play_sfx("shot_special1");
int n2 = difficulty_value(20, 23, 26, 30);
for(int i = 0; i < n2; i++) {
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.color = RGB(0.6, 0.2, 0.7),
.move = move_asymptotic_simple(1.5*cdir(2*M_PI/n2*i),2),
);
if(global.diff > D_Easy) {
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.color = RGB(0.6, 0.2, 0.7),
.move = move_asymptotic_simple(3*cdir(2*M_PI/n2*i), 3.0),
);
}
}
}
WAIT(interval);
}
WAIT(60);
e->move = move_asymptotic_simple(ARGS.end_velocity, -1);
}
TASK(boss_appear, { BoxedBoss boss; }) {
Boss *boss = NOT_NULL(ENT_UNBOX(ARGS.boss));
boss->move = move_from_towards(boss->pos, VIEWPORT_W/2.0 + 100.0*I, 0.05);
}
TASK(spawn_boss) {
STAGE_BOOKMARK(boss);
stage_unlock_bgm("stage1");
Boss *boss = global.boss = stage1_spawn_cirno(-230 + 100.0*I);
PlayerMode *pm = global.plr.mode;
Stage1PreBossDialogEvents *e;
INVOKE_TASK_INDIRECT(Stage1PreBossDialog, pm->dialog->Stage1PreBoss, &e);
INVOKE_TASK_WHEN(&e->boss_appears, boss_appear, ENT_BOX(boss));
INVOKE_TASK_WHEN(&e->music_changes, common_start_bgm, "stage1boss");
WAIT_EVENT(&global.dialog->events.fadeout_began);
STAGE_BOOKMARK(boss-postdialog);
boss_add_attack_task(boss, AT_Normal, "Iceplosion 0", 20, 26000, TASK_INDIRECT(BossAttack, stage1_boss_nonspell_1), NULL);
boss_add_attack_from_info(boss, &stage1_spells.boss.crystal_rain, false);
boss_add_attack_task(boss, AT_Normal, "Iceplosion 1", 20, 30000, TASK_INDIRECT(BossAttack, stage1_boss_nonspell_2), NULL);
if(global.diff > D_Normal) {
boss_add_attack_from_info(boss, &stage1_spells.boss.snow_halation, false);
}
boss_add_attack_from_info(boss, &stage1_spells.boss.icicle_cascade, false);
boss_add_attack_from_info(boss, &stage1_spells.extra.crystal_blizzard, false);
boss_engage(boss);
}
static void stage1_dialog_post_boss(void) {
PlayerMode *pm = global.plr.mode;
INVOKE_TASK_INDIRECT(Stage1PostBossDialog, pm->dialog->Stage1PostBoss);
}
DEFINE_EXTERN_TASK(stage1_timeline) {
INVOKE_TASK_DELAYED(100, burst_fairies_1);
INVOKE_TASK_DELAYED(240, burst_fairies_2);
INVOKE_TASK_DELAYED(440, sinepass_swirls, 180, 100, 1);
INVOKE_TASK_DELAYED(480, circletoss_fairies_1);
INVOKE_TASK_DELAYED(660, circle_fairies_1);
INVOKE_TASK_DELAYED(900, schedule_swirls);
INVOKE_TASK_DELAYED(1500, burst_fairies_3);
INVOKE_TASK_DELAYED(2200, multiburst_fairies_1);
INVOKE_TASK_DELAYED(2200, common_call_func, stage1_bg_raise_camera);
STAGE_BOOKMARK_DELAYED(2500, pre-midboss);
INVOKE_TASK_DELAYED(2700, spawn_midboss);
while(!global.boss) YIELD;
int midboss_time = WAIT_EVENT(&global.boss->events.defeated).frames;
int filler_time = 2180;
int time_ofs = -midboss_time;
log_debug("midboss_time = %i; filler_time = %i; time_ofs = %i", midboss_time, filler_time, time_ofs);
STAGE_BOOKMARK(post-midboss);
int swirl_spam_time = 1260;
for(int i = 0; i < /*swirl_spam_time*/ filler_time; i += 30) {
int o = ((int[]) { 0, 1, 0, -1 })[(i / 60) % 4];
INVOKE_TASK_DELAYED(i + time_ofs, sinepass_swirls, 40, 132 + 32 * o, 1 - 2 * ((i / 60) & 1));
}
time_ofs += swirl_spam_time;
INVOKE_TASK_DELAYED(time_ofs, burst_fairies_1);
int instacircle_time = filler_time - swirl_spam_time - 100;
for(int t = 0, i = 1; t < instacircle_time; t += 180, ++i) {
// INVOKE_TASK_DELAYED(i + time_ofs, sinepass_swirls, 80, 132, 1);
INVOKE_TASK_DELAYED(120 + t + time_ofs, instantcircle_fairies, 120, i);
}
WAIT(filler_time - midboss_time);
STAGE_BOOKMARK(post-midboss-filler);
INVOKE_TASK_DELAYED(100, circletoss_fairy, -25 + VIEWPORT_H/3*I, 1 - 0.5*I, 0.01 * ( 1 - I), 200);
INVOKE_TASK_DELAYED(125, circletoss_fairy, VIEWPORT_W+25 + VIEWPORT_H/3*I, -1 - 0.5*I, 0.01 * (-1 - I), 200);
if(global.diff > D_Normal) {
INVOKE_TASK_DELAYED(115, circletoss_fairy, -25 + 2*VIEWPORT_H/3*I, 1 - 0.5*I, 0.01 * ( 1 - I), 200);
INVOKE_TASK_DELAYED(140, circletoss_fairy, VIEWPORT_W+25 + 2*VIEWPORT_H/3*I, -1 - 0.5*I, 0.01 * (-1 - I), 200);
}
STAGE_BOOKMARK_DELAYED(180, waveshot-fairies);
INVOKE_TASK_DELAYED(180, waveshot_fairies, 600);
INVOKE_TASK_DELAYED(400, burst_fairies_3);
INVOKE_TASK_DELAYED(430, burst_fairies_3);
STAGE_BOOKMARK_DELAYED(1000, post-midboss-filler-2);
INVOKE_TASK_DELAYED(1000, burst_fairies_1);
INVOKE_TASK_DELAYED(1000, explosion_fairy, VIEWPORT_W-80 + 120*I, -0.2+0.1*I);
INVOKE_TASK_DELAYED(1140, explosion_fairy, 80 + 220*I, 0.2+0.1*I);
STAGE_BOOKMARK_DELAYED(1400, post-midboss-filler-3);
INVOKE_TASK_DELAYED(1400, drop_swirls, 25, 2*VIEWPORT_W/3, 2*I, -0.06);
if(global.diff > D_Easy) {
INVOKE_TASK_DELAYED(1600, drop_swirls, 25, VIEWPORT_W/3, 2*I, 0.06);
}
INVOKE_TASK_DELAYED(1420, tritoss_fairy, VIEWPORT_W / 2 + 220*I, -2.6 * I);
INVOKE_TASK_DELAYED(1820, circle_fairy, VIEWPORT_W + 42 + 300*I, VIEWPORT_W - 130 + 240*I);
INVOKE_TASK_DELAYED(1820, circle_fairy, - 42 + 300*I, 130 + 240*I);
if(global.diff > D_Normal) {
INVOKE_TASK_DELAYED(1880, instantcircle_fairy, VIEWPORT_W + 42 + 300*I, VIEWPORT_W - 84 + 260*I, 0.2 * (-2 - I));
INVOKE_TASK_DELAYED(1880, instantcircle_fairy, - 42 + 300*I, 84 + 260*I, 0.2 * ( 2 - I));
}
INVOKE_TASK_DELAYED(2060, waveshot_fairy, 130 + 140*I, 0.2 * (-2 - I));
INVOKE_TASK_DELAYED(2060, waveshot_fairy, VIEWPORT_W - 130 + 140*I, 0.2 * ( 2 - I));
STAGE_BOOKMARK_DELAYED(2300, pre-boss);
WAIT(2560);
INVOKE_TASK(spawn_boss);
while(!global.boss) YIELD;
WAIT_EVENT(&global.boss->events.defeated);
stage_unlock_bgm("stage1boss");
WAIT(120);
stage1_bg_disable_snow();
WAIT(120);
stage1_dialog_post_boss();
WAIT_EVENT(&global.dialog->events.fadeout_began);
WAIT(5);
stage_finish(GAMEOVER_SCORESCREEN);
}