diff --git a/src/stages/stage3/meson.build b/src/stages/stage3/meson.build index 22dfea61..e05fa217 100644 --- a/src/stages/stage3/meson.build +++ b/src/stages/stage3/meson.build @@ -9,7 +9,7 @@ stage3_src = files( 'spells/light_singularity.c', 'spells/logic_bomb.c', 'spells/moonlight_rocket.c', - 'spells/night_ignite.c', + 'spells/moths_to_a_flame.c', 'stage3.c', 'timeline.c', ) diff --git a/src/stages/stage3/spells/moths_to_a_flame.c b/src/stages/stage3/spells/moths_to_a_flame.c new file mode 100644 index 00000000..c31b47d5 --- /dev/null +++ b/src/stages/stage3/spells/moths_to_a_flame.c @@ -0,0 +1,312 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#include "taisei.h" + +#include "spells.h" +#include "../wriggle.h" + +#include "common_tasks.h" +#include "global.h" + +typedef struct SunContext { + int bullets_absorbed; +} SunContext; + +typedef struct SunArgs { + BoxedProjectile ent; + SunContext *context; // Valid if UNBOX(ent) != NULL +} SunArgs; + +TASK(colorshift, { BoxedProjectile p; Color target_color; }) { + auto p = TASK_BIND(ARGS.p); + + for(;;YIELD) { + color_approach(&p->color, &ARGS.target_color, 0.01); + } +} + +TASK(attraction, { SunArgs sun; BoxedProjectile p; cmplx dir; real twist; }) { + auto p = TASK_BIND(ARGS.p); + p->flags = PFLAG_NOAUTOREMOVE; + + p->move = move_asymptotic(p->move.velocity, p->move.velocity * 0.01 + ARGS.dir, 0.8); + p->move.retention *= cdir(ARGS.twist); + p->move.attraction_point = NOT_NULL(ENT_UNBOX(ARGS.sun.ent))->pos; + + WAIT(60); + play_sfx("redirect"); + + p->color.r = 1; + spawn_projectile_highlight_effect(p); + + int rampuptime = 60; + float cf = 0.998; + + for(int i = 0; i < rampuptime; ++i, YIELD) { + p->color.g *= cf; + p->move.attraction = 0.25 * i / (rampuptime - 1.0); + } + + for(;;YIELD) { + auto sun = ENT_UNBOX(ARGS.sun.ent); + + if(sun) { + p->move.attraction_point = sun->pos; + } + + p->color.g *= cf; + p->color.a *= cf * cf; + p->move.attraction += 0.0001; + + real mindist = 12; + if(cabs2(p->pos - p->move.attraction_point) < mindist * mindist) { + play_sfx("shot1"); + + int cnt = 2; + for(int i = 0; i < cnt; ++i) { + cmplx r = cdir(M_TAU/4 * lerp(-1, 1, i / (cnt - 1.0))); + real speed = 0.5; + auto f = PROJECTILE( + .proto = pp_flea, + .pos = p->pos, + // .move = move_asymptotic_simple(0.5 * rng_dir(), 10), + .move = move_asymptotic_simple(speed * cnormalize(p->move.velocity) * r, 10), + .flags = PFLAG_NOSPAWNFADE, + .color = RGBA(3, 1.5, 1, 0), + ); + + INVOKE_TASK(colorshift, ENT_BOX(f), *RGB(0.5, 0.2, 0.2)); + } + + if(sun) { + if(!ARGS.sun.context->bullets_absorbed++) { + play_sfx("boom"); + + PARTICLE( + .pos = sun->pos, + .sprite = "blast_huge_halo", + .color = &sun->color, + .flags = PFLAG_MANUALANGLE, + .angle = rng_angle(), + .timeout = 120, + .draw_rule = pdraw_timeout_scalefade(0, 2, 2, 0), + ); + } + } + + kill_projectile(p); + } + } +} + +TASK(spawner, { SunArgs sun; cmplx dir; int num_bullets; }) { + auto sun = NOT_NULL(ENT_UNBOX(ARGS.sun.ent)); + + real t = 120; + real d = hypot(VIEWPORT_W, VIEWPORT_H); + real m = 120; + cmplx o = sun->pos + d * ARGS.dir; + + auto p = TASK_BIND(PROJECTILE( + .proto = pp_ball, + .pos = o, + .color = RGBA(0.5, 1.0, 0.5, 0), + .move = move_accelerated(0, -ARGS.dir * (d - m) / (0.5 * t * t)), + .flags = PFLAG_NOAUTOREMOVE, + )); + + real lspeed = difficulty_value(2, 2, 3, 3); + + auto l = create_lasercurve2c( + sun->pos, t/8, t, RGBA(0, 0.5, 1, 0), las_accel, 0, -lspeed*p->move.acceleration); + l->width = 20; + create_laserline_ab(o, sun->pos, 10, t, t, RGBA(0, 0.5, 1, 0)); + play_sfx("laser1"); + play_sfx("boon"); + + int t0 = t * 0.92; + int t1 = t - t0; + + WAIT(t0); + p->move = move_dampen(p->move.velocity, 0.9); + WAIT(t1); + play_sfx("shot_special1"); + + RADIAL_LOOP(l, ARGS.num_bullets, rng_dir()) { + auto c = PROJECTILE( + .proto = pp_rice, + .pos = p->pos, + .color = RGBA(0, 0.7, 0.25, 1), + .flags = PFLAG_NOSPAWNFLARE, + .move = move_linear(p->move.velocity), + ); + + INVOKE_TASK(attraction, ARGS.sun, ENT_BOX(c), l.dir, 0.1 * (1 - 2 * (l.i & 1))); + } + + PROJECTILE( + .color = RGBA(1.5, 1.0, 0.5, 0), + .pos = p->pos, + .proto = pp_soul, + .timeout = 1, + .move = move_linear(p->move.velocity), + ); + + kill_projectile(p); +} + +static void sun_move(Projectile *p) { + if(p->move.acceleration) { + return; + } + + real a = difficulty_value(0.001, 0.001, 0.002, 0.002); + p->move = move_accelerated(p->move.velocity, a * cnormalize(global.plr.pos - p->pos)); + + auto halo = res_sprite("part/blast_huge_halo"); + + PARTICLE( + .pos = p->pos, + .sprite_ptr = halo, + .color = color_add(COLOR_COPY(&p->color), RGBA(0, 0, 1, 0)), + .scale = crealf(p->scale) * p->sprite->w / halo->w, + .flags = PFLAG_MANUALANGLE | PFLAG_REQUIREDPARTICLE, + .angle = rng_angle(), + .timeout = 20, + .draw_rule = pdraw_timeout_scalefade(2, 1, 5, 0), + ); + + play_sfx("warp"); +} + +TASK(sun_move, { BoxedProjectile sun; }) { + sun_move(TASK_BIND(ARGS.sun)); +} + +TASK(sun_flare, { BoxedProjectile sun; }) { + auto p = TASK_BIND(ARGS.sun); + auto halo = res_sprite("part/blast_huge_rays"); + + int d = 2; + for(;;WAIT(d)) { + float smear = 2; + PARTICLE( + .sprite_ptr = halo, + .scale = crealf(p->scale) * p->sprite->w / halo->w, + .pos = p->pos, + .angle = rng_angle(), // p->angle, + .flags = PFLAG_NOMOVE | PFLAG_MANUALANGLE, + .timeout = 10 * smear, + .draw_rule = pdraw_timeout_scalefade(1, 1.25, 1, 0), + .color = &p->color, + .shader_ptr = p->shader, + .layer = p->ent.draw_layer, + .opacity = d/smear, + ); + } +} + +TASK(sun, { cmplx pos; SunArgs *out_sunargs; int max_absorb; }) { + auto p = TASK_BIND(PROJECTILE( + .proto = pp_bigball, + .pos = ARGS.pos, + .color = RGBA(1.5, 1.0, 0.2, 0), + .flags = PFLAG_INDESTRUCTIBLE | PFLAG_NOCLEAR | PFLAG_MANUALANGLE, + .angle_delta = 1.2, + )); + + INVOKE_TASK(sun_flare, ENT_BOX(p)); + + SunContext sctx = {}; + *ARGS.out_sunargs = (SunArgs) { ENT_BOX(p), &sctx }; + + cmplx size0 = p->size; + cmplx csize0 = p->collision_size * 0.8 + p->size * 0.2; + real max_size = difficulty_value(9, 10, 12, 14); + real absorb_scale = 1.0 / ARGS.max_absorb; + + auto timed_move = cotask_box(INVOKE_SUBTASK_DELAYED(600, sun_move, ENT_BOX(p))); + + for(;;YIELD) { + real f = sctx.bullets_absorbed * absorb_scale; + real s = lerp(1, max_size, f); + + p->size = size0 * s; + p->collision_size = csize0 * s; + p->scale = CMPLX(s, s); + p->color.g = lerpf(1.0f, 0.25f, f); + + if(sctx.bullets_absorbed >= ARGS.max_absorb) { + break; + } + } + + CANCEL_TASK(timed_move); + sun_move(p); +} + +TASK(slave, { BoxedBoss boss; cmplx target_pos; }) { + Boss *boss = TASK_BIND(ARGS.boss); + + WriggleSlave *slave = stage3_host_wriggle_slave(boss->pos); + + MoveParams m = move_from_towards(slave->pos, ARGS.target_pos, 0.05); + INVOKE_SUBTASK(common_move, &slave->pos, m); + + PROJECTILE( + .color = RGBA(0.1, 1.0, 0.25, 0), + .pos = slave->pos, + .proto = pp_soul, + .timeout = 1, + .move = m, + ); + + WAIT(80); + + int num_spawners = difficulty_value(8, 8, 12, 12); + int bullets_per_spawner = difficulty_value(20, 30, 30, 40); + int max_bullets = num_spawners * bullets_per_spawner; + + SunArgs sun_args; + INVOKE_TASK(sun, slave->pos, &sun_args, max_bullets); + + cmplx dir = I; // rng_dir(); + cmplx r = cdir(M_TAU/num_spawners); + + for(int i = 0; i < num_spawners; ++i) { + INVOKE_TASK(spawner, sun_args, dir, bullets_per_spawner); + dir *= r; + } + + play_sfx("shot_special1"); +} + +DEFINE_EXTERN_TASK(stage3_spell_moths_to_a_flame) { + Boss *boss = INIT_BOSS_ATTACK(&ARGS); + boss->move = move_from_towards(boss->pos, VIEWPORT_W/2 + VIEWPORT_H*I/3, 0.05); + BEGIN_BOSS_ATTACK(&ARGS); + int cycle = 500; + int charge_time = 120; + + Rect bounds = viewport_bounds(64); + bounds.bottom = VIEWPORT_H/3; + + for(;;) { + boss->move.attraction_point = common_wander(boss->pos, 200, bounds); + aniplayer_soft_switch(&boss->ani, "specialshot_charge", 1); + aniplayer_queue(&boss->ani, "specialshot_hold", 0); + common_charge(charge_time, &boss->pos, 0, RGBA(0, 1, 0.2, 0)); + aniplayer_queue(&boss->ani, "specialshot_release", 1); + aniplayer_queue(&boss->ani, "main", 0); + boss->move.attraction_point = common_wander(boss->pos, 92, bounds); + cmplx aim = 0.5 * (global.plr.pos - boss->pos); + INVOKE_SUBTASK(slave, ENT_BOX(boss), boss->pos + aim); + WAIT(cycle - charge_time); + } +} diff --git a/src/stages/stage3/spells/night_ignite.c b/src/stages/stage3/spells/night_ignite.c deleted file mode 100644 index dde63d90..00000000 --- a/src/stages/stage3/spells/night_ignite.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * This software is licensed under the terms of the MIT License. - * See COPYING for further information. - * --- - * Copyright (c) 2011-2019, Lukas Weber . - * Copyright (c) 2012-2019, Andrei Alexeyev . -*/ - -#include "taisei.h" - -#include "spells.h" -#include "../wriggle.h" - -#include "common_tasks.h" -#include "global.h" - -TASK(slave, { BoxedBoss boss; real rot_speed; real rot_initial; }) { - Boss *boss = TASK_BIND(ARGS.boss); - cmplx dir; - - WriggleSlave *slave = stage3_host_wriggle_slave(boss->pos); - INVOKE_SUBTASK(wriggle_slave_damage_trail, ENT_BOX(slave)); - INVOKE_SUBTASK(wriggle_slave_follow, - .slave = ENT_BOX(slave), - .boss = ENT_BOX(boss), - .rot_speed = ARGS.rot_speed, - .rot_initial = ARGS.rot_initial, - .out_dir = &dir - ); - - WAIT(300); - - for(;;WAIT(180)) { - int cnt = 5, i; - - for(i = 0; i < cnt; ++i) { - PROJECTILE( - .proto = pp_ball, - .pos = slave->pos, - .color = RGBA(0.5, 1.0, 0.5, 0), - .move = move_accelerated(0, 0.02 * cdir(i*M_TAU/cnt)), - ); - - if(global.diff > D_Hard) { - PROJECTILE( - .proto = pp_ball, - .pos = slave->pos, - .color = RGBA(1.0, 1.0, 0.5, 0), - .move = move_accelerated(0, 0.01 * cdir(i*2*M_TAU/cnt)), - ); - } - } - - // FIXME: better sound - play_sfx("shot_special1"); - } -} - -TASK(warnlaser, { BoxedLaser base_laser; }) { - Laser *b = NOT_NULL(ENT_UNBOX(ARGS.base_laser)); - - real f = 6; - Laser *l = TASK_BIND(create_laser( - b->pos, 90, 120, RGBA(1, 1, 1, 0), b->prule, - f*b->args[0], b->args[1], f*b->args[2], b->args[3] - )); - - laser_make_static(l); - - for(int t = 0;; ++t, YIELD) { - if(t == 90) { - play_sfx_ex("laser1", 30, false); - } - - laser_charge(l, t, 90, 10); - l->color = *color_lerp(RGBA(0.2, 0.2, 1, 0), RGBA(1, 0.2, 0.2, 0), t / l->deathtime); - } -} - -TASK(laserbullet, { ProjPrototype *proto; Color *color; BoxedLaser laser; cmplx pos; real time_offset; }) { - Projectile *p = TASK_BIND(PROJECTILE( - .proto = ARGS.proto, - .color = ARGS.color, - .pos = ARGS.pos, - .move = move_linear(0), - )); - - int t = 0; - for(Laser *l; (l = ENT_UNBOX(ARGS.laser)); ++t, YIELD) { - p->move.velocity = l->prule(l, t + ARGS.time_offset) - p->pos; - } -} - -DEFINE_EXTERN_TASK(stage3_spell_night_ignite) { - Boss *boss = INIT_BOSS_ATTACK(&ARGS); - boss->move = move_from_towards(boss->pos, VIEWPORT_W/2 + VIEWPORT_H*I/3, 0.05); - BEGIN_BOSS_ATTACK(&ARGS); - - int nslaves = 7; - - for(int i = 0; i < nslaves; ++i) { - INVOKE_SUBTASK(slave, ENT_BOX(boss), 1/70.0, i*M_TAU/nslaves); - INVOKE_SUBTASK(slave, ENT_BOX(boss), -1/70.0, -i*M_TAU/nslaves); - } - - real dt = 200; - real lt = difficulty_value(25, 50, 75, 100); - real amp = M_PI/5; - real freq = 0.05; - int laserbullets = difficulty_value(8, 12, 16, 20); - - int t = 0; - - for(int cycle = 0;; ++cycle, t += WAIT(180)) { - for(int step = 0; step < 12; ++step, t += WAIT(10)) { - real a = step * M_PI/2.5 + cycle + t; - cmplx vel = 2 * cdir(a); - - Laser *l1 = create_lasercurve3c( - boss->pos, lt, dt, - RGBA(0.3, 0.3, 1.0, 0.0), - las_sine_expanding, vel, amp, freq - ); - INVOKE_TASK(warnlaser, ENT_BOX(l1)); - - Laser *l2 = create_lasercurve3c( - boss->pos, lt * 1.5, dt, - RGBA(1.0, 0.3, 0.3, 0.0), - las_sine_expanding, vel, amp, freq - 0.002 * imin(global.diff, D_Hard) - ); - INVOKE_TASK(warnlaser, ENT_BOX(l2)); - - Laser *l3 = create_lasercurve3c( - boss->pos, lt, dt, - RGBA(0.3, 0.3, 1.0, 0.0), - las_sine_expanding, vel, amp, freq - 0.004 * imin(global.diff, D_Hard) - ); - INVOKE_TASK(warnlaser, ENT_BOX(l3)); - - for(int i = 0; i < laserbullets; ++i) { - #define LASERBULLLET(_proto, _color, _laser) \ - INVOKE_TASK(laserbullet, \ - .proto = _proto, \ - .color = _color, \ - .laser = ENT_BOX(_laser), \ - .pos = boss->pos, \ - .time_offset = -i \ - ) - - LASERBULLLET(pp_plainball, RGBA(0.3, 0.3, 1.0, 0), l1); - LASERBULLLET(pp_bigball, RGBA(1.0, 0.3, 0.3, 0), l2); - LASERBULLLET(pp_plainball, RGBA(0.3, 0.3, 1.0, 0), l3); - - play_sfx("shot1"); - } - - play_sfx_ex("shot_special1", 1, false); - } - } - - STALL; -} diff --git a/src/stages/stage3/spells/spells.h b/src/stages/stage3/spells/spells.h index e28323af..8382286f 100644 --- a/src/stages/stage3/spells/spells.h +++ b/src/stages/stage3/spells/spells.h @@ -14,5 +14,5 @@ DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_spell_firefly_storm, BossAttack); DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_spell_light_singularity, BossAttack); DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_spell_moonlight_rocket, BossAttack); -DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_spell_night_ignite, BossAttack); +DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_spell_moths_to_a_flame, BossAttack); DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_spell_logic_bomb, BossAttack); diff --git a/src/stages/stage3/stage3.c b/src/stages/stage3/stage3.c index 87f7ad2b..e7faa755 100644 --- a/src/stages/stage3/stage3.c +++ b/src/stages/stage3/stage3.c @@ -40,9 +40,9 @@ struct stage3_spells_s stage3_spells = { TASK_INDIRECT_INIT(BossAttack, stage3_spell_moonlight_rocket), stage3_draw_wriggle_spellbg, VIEWPORT_W/2.0+100*I, 3, }, - .wriggle_night_ignite = { - {10, 11, 12, 13}, AT_Spellcard, "Light Source “Wriggle Night Ignite”", 50, 46000, - TASK_INDIRECT_INIT(BossAttack, stage3_spell_night_ignite), + .moths_to_a_flame = { + {18, 19, 20, 21}, AT_Spellcard, "Attractor “Moths to a Flame”", 90, 70000, + TASK_INDIRECT_INIT(BossAttack, stage3_spell_moths_to_a_flame), stage3_draw_wriggle_spellbg, VIEWPORT_W/2.0+100*I, 3, }, .firefly_storm = { diff --git a/src/stages/stage3/stage3.h b/src/stages/stage3/stage3.h index c69bf006..c2853fb5 100644 --- a/src/stages/stage3/stage3.h +++ b/src/stages/stage3/stage3.h @@ -21,7 +21,7 @@ extern struct stage3_spells_s { struct { AttackInfo moonlight_rocket; - AttackInfo wriggle_night_ignite; + AttackInfo moths_to_a_flame; AttackInfo firefly_storm; } boss; diff --git a/src/stages/stage3/timeline.c b/src/stages/stage3/timeline.c index 1bfdcd39..8f2e9631 100644 --- a/src/stages/stage3/timeline.c +++ b/src/stages/stage3/timeline.c @@ -586,7 +586,7 @@ TASK(spawn_boss) { 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.wriggle_night_ignite, false); + 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);