stage3: replace night ignite with a new spell card

This commit is contained in:
Andrei Alexeyev 2023-08-09 05:33:38 +02:00
parent 323183061b
commit d1f8ae883a
No known key found for this signature in database
GPG key ID: 72D26128040B9690
7 changed files with 319 additions and 169 deletions

View file

@ -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',
)

View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#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);
}
}

View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#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;
}

View file

@ -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);

View file

@ -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 = {

View file

@ -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;

View file

@ -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);