839 lines
20 KiB
C
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);
|
|
}
|