stage3: Major redesign of the stage (WIP)

Still needs work, but good enough for now.

Merges #203

Squashed commit of the following:

commit 7f30ef2393
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Fri Jan 27 23:50:42 2023 +0100

    stageinfo: finally rename stage3

commit 53ca691e68
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Fri Jan 27 23:38:36 2023 +0100

    stage3: telegraph moonlight rockets

commit 4d247877e7
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Wed Jan 18 15:54:44 2023 +0100

    stage3: moonlight rocket fixes

commit 684a167611
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Wed Jan 18 14:50:48 2023 +0100

    stage3: update background

commit bed836f56c
Author: laochailan <laochailan@web.de>
Date:   Sat Jan 7 19:52:51 2023 -0500

    stage3: make easy mode easier

commit bec659a6eb
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Wed Dec 7 20:20:18 2022 +0100

    stage3: sprinkle some random difficulty_value() calls

commit 2090821188
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sun Nov 20 07:57:33 2022 +0100

    stage3: refactor and fix deadly dance (no design update yet)

commit 5dbdeae1dd
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sun Nov 20 07:17:05 2022 +0100

    stage3: redesign scuttle nonspell

commit 70666a4074
Author: Andrei Alexeyev <0x416b617269@gmail.com>
Date:   Mon Nov 14 01:32:11 2022 +0100

    stage3: last pre-boss section draft

commit 42bb538ff5
Author: Andrei Alexeyev <0x416b617269@gmail.com>
Date:   Mon Nov 14 01:28:32 2022 +0100

    stage3: bunch of swarm-trail fairy fixes

commit 5938783591
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sat Nov 12 01:40:23 2022 +0100

    stage3: more misery

commit 84342f1973
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Nov 8 16:35:11 2022 +0100

    stage3: post-midboss up to climax

commit b40f71bbc6
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Wed Nov 2 18:43:54 2022 +0100

    stage3: experimental post-midboss pattern

commit b5710faa04
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Wed Nov 2 18:12:49 2022 +0100

    stage3: deadly dance fixes

commit f85aa25c00
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Wed Nov 2 18:08:02 2022 +0100

    stage3: first sparks of difficulty balance

commit 33b65b7fbe
Author: laochailan <laochailan@web.de>
Date:   Sun Oct 16 10:27:24 2022 -0400

    stage3: align swarm fairies

commit 2556a0de9e
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Mon Sep 26 05:35:53 2022 +0200

    stage3: more progress on the first half

commit 86fc38255b
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sat Sep 24 04:10:57 2022 +0200

    stage3: add some swirls after superfairy

commit b0134e82c7
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sat Sep 24 04:10:11 2022 +0200

    stage3: experimental laserball fairy

commit 88d7646927
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sat Sep 24 04:09:14 2022 +0200

    stage3: tweak horde fairies

commit b97da7a9e9
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Sep 13 17:08:12 2022 +0200

    stage3: wip circletwist fairy tweaks

commit ee9234a72b
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Mon Sep 5 04:41:55 2022 +0200

    wip swirls

commit 26c7a35e04
Author: laochailan <laochailan@web.de>
Date:   Sun Jan 2 11:29:41 2022 +0100

    stage3 wip

commit d1a263223f
Author: laochailan <laochailan@web.de>
Date:   Fri Nov 12 05:57:07 2021 +0100

    wip wip wip

commit c2527b05cf
Author: laochailan <laochailan@web.de>
Date:   Thu Sep 30 20:38:38 2021 +0200

    stage3 redesign: some first prototypes

commit 0c2855e3ca
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Thu May 6 21:59:48 2021 +0300

    stage3: some preliminary fixes

commit fddf314db9
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sun Feb 28 07:36:04 2021 +0200

    stage3: port rest of night ignite

commit 1cd84d7834
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sat Feb 27 04:50:46 2021 +0200

    stage3: port night ignite slaves

commit cb991be703
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Sat Feb 27 04:26:26 2021 +0200

    stage3: finish porting firefly storm

commit 0fb9733829
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Wed Jun 23 02:44:14 2021 +0300

    stage3: partial firefly storm port

commit ba174893a0
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Feb 23 02:28:13 2021 +0200

    stage3: enable all wriggle attacks

commit 03e1dd8078
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Tue Feb 23 02:22:35 2021 +0200

    stage3: port wriggle nons

commit 3308255579
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Mon Feb 22 16:27:50 2021 +0200

    stage3: fix some indent issues

commit b1c1115d7f
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Mon Feb 22 16:23:07 2021 +0200

    stage3: fix deprecations in moonlight rocket

commit 990489e960
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Thu Feb 4 08:53:00 2021 +0200

    stage3: port moonlight rocket

commit 859592a89b
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Thu Feb 4 07:31:58 2021 +0200

    stage3: basic port of wriggle slaves

commit b88b4f5249
Author: Andrei Alexeyev <akari@taisei-project.org>
Date:   Mon Feb 1 06:28:30 2021 +0200

    stage3: port Light Singularity

commit db6bb13326
Author: Alice D <34408664+StarWitch@users.noreply.github.com>
Date:   Wed Mar 18 23:25:25 2020 -0400

    stage3: preliminary coroutinization (#197)

    * first enemy of Stage 3 converted

    * review suggestions

    * add interval for burst_swirls

    * mid-commit

    * fairy group (second spawned enemies)

    * forgot to use my own variable

    * comment changes

    * better understanding of second arg of create_enemy*

    * fix subtasks (wasn't using them the correct way)

    * swirls that go from one side to the other

    * burst fairies complete

    * all complete up until midboss (stage 3 coroutines)

    * remove sub-rule for projectiles in charge_fairy

    * entire stage on coroutines now (boss spellcards missing)

    * rip out more old/dead code, make file better organized

    * timing changes

    * remove old background (looks jank now with new timing)

    * PR review changes

    * slight readability changes

    * Scuttle lethal_bite implemented

    * PR changes (ignore wriggle, she's still a WIP)

    * revert Info.plist entry (for separate PR)

    * finish(?) Scuttle's deadly dance, plus some PR changes

Co-authored-by: Alice D <34408664+StarWitch@users.noreply.github.com>
Co-authored-by: Lukas Weber <laochailan@web.de>
This commit is contained in:
Andrei Alexeyev 2023-01-27 23:57:12 +01:00
parent 12af051d48
commit 558541e2cc
No known key found for this signature in database
GPG key ID: 72D26128040B9690
49 changed files with 1624 additions and 1412 deletions

Binary file not shown.

View file

@ -3,3 +3,5 @@ ambient_map = stage3/ground_ambient
diffuse_map = stage3/ground_diffuse
normal_map = stage3/ground_normal
roughness_map = stage3/ground_roughness
ao_map = stage3/ground_ao

Binary file not shown.

View file

@ -3,3 +3,6 @@ ambient_map = stage3/leaves_ambient
diffuse_map = stage3/leaves_diffuse
normal_map = stage3/leaves_normal
roughness_map = stage3/leaves_roughness
roughness = 1.7
ao_map = stage3/leaves_ao

Binary file not shown.

View file

@ -3,3 +3,5 @@ ambient_map = stage3/rocks_ambient
diffuse_map = stage3/rocks_diffuse
normal_map = stage3/rocks_normal
roughness_map = stage3/rocks_roughness
ao_map = stage3/rocks_ao

Binary file not shown.

View file

@ -3,3 +3,5 @@ ambient_map = stage3/trees_ambient
diffuse_map = stage3/trees_diffuse
normal_map = stage3/trees_normal
roughness_map = stage3/trees_roughness
ao_map = stage3/trees_ao

Binary file not shown.

View file

@ -130,7 +130,7 @@ static void stageinfo_fill(StagesExports *e) {
// id procs type title subtitle spells diff
add_stage(1, e->stage1.procs, STAGE_STORY, "Stage 1", "Misty Lake", e->stage1.spells, D_Any);
add_stage(2, e->stage2.procs, STAGE_STORY, "Stage 2", "Walk Along the Border", e->stage2.spells, D_Any);
add_stage(3, e->stage3.procs, STAGE_STORY, "Stage 3", "Through the Tunnel of Light", e->stage3.spells, D_Any);
add_stage(3, e->stage3.procs, STAGE_STORY, "Stage 3", "Mountain Ascent", e->stage3.spells, D_Any);
add_stage(4, e->stage4.procs, STAGE_STORY, "Stage 4", "Forgotten Mansion", e->stage4.spells, D_Any);
add_stage(5, e->stage5.procs, STAGE_STORY, "Stage 5", "Climbing the Tower of Babel", e->stage5.spells, D_Any);
add_stage(6, e->stage6.procs, STAGE_STORY, "Stage 6", "Roof of the World", e->stage6.spells, D_Any);

View file

@ -13,13 +13,47 @@
#include "global.h"
#include "stageutils.h"
// TODO
#include "util/glm.h"
TASK(animate_bg) {
for(;;) {
YIELD;
Stage3DrawData *dd = stage3_get_draw_data();
Camera3D *cam = &stage_3d_context.cam;
dd->target_swing_strength = 0.2f;
for(int t = 0;; ++t, YIELD) {
float f = 1.0f / sqrtf(1.0f + t / 500.0f);
glm_vec3_copy((vec3) { f, f, sqrtf(f) }, dd->ambient_color);
float swing = sin(t / 100.0) * dd->swing_strength;
cam->pos[0] = swing;
cam->rot.yaw = swing * -8.0f;
fapproach_asymptotic_p(&dd->swing_strength, dd->target_swing_strength, 0.005f, 1e-3f);
vec4 f0 = { 0.60, 0.30, 0.60, 1.0 };
vec4 f1 = { 0.20, 0.10, 0.30, 1.0 };
glm_vec4_lerp(f0, f1, 1 - f, dd->fog_color);
vec3 e0 = { 2.50, 0.80, 0.50 };
vec3 e1 = { 0.20, 0.30, 0.50 };
glm_vec3_lerp(e0, e1, 1 - f, dd->environment_color);
glm_vec3_scale(dd->environment_color, 0.5 * f, dd->environment_color);
stage3d_update(&stage_3d_context);
if(global.boss && !boss_is_fleeing(global.boss)) {
fapproach_asymptotic_p(&dd->boss_light_alpha, 1, 0.02f, 1e-3f);
} else {
fapproach_asymptotic_p(&dd->boss_light_alpha, 0, 0.02f, 1e-3f);
}
if(dd->boss_light_alpha > 0 && global.boss) {
vec3 r;
camera3d_unprojected_ray(cam, global.boss->pos, r);
glm_vec3_scale(r, 14, r);
glm_vec3_add(cam->pos, r, dd->boss_light.pos);
}
}
}

View file

@ -29,42 +29,39 @@ static uint stage3_bg_pos(Stage3D *s3d, vec3 cam, float maxrange) {
}
static void stage3_bg_setup_pbr_lighting(Camera3D *cam) {
PointLight3D lights[] = {
// TODO animate colors
{ { 0, 0, 10000 }, { 10, 42, 30 } },
{ { 0, 0, 0 }, { 10, 10, 10 } },
PointLight3D lights[2] = {
{ { 0, 0, 0 }, { 10, 10, 10 } },
};
if(global.boss) {
vec3 r;
cmplx bpos = global.boss->pos;
if(cimag(bpos) < 0) { // to make the light (dis)appear continuously
bpos = creal(bpos) + I*pow(fabs(cimag(bpos))*0.1,2)*cimag(bpos);
}
camera3d_unprojected_ray(cam,bpos,r);
glm_vec3_scale(r, 9, r);
glm_vec3_add(cam->pos, r, lights[0].pos);
PointLight3D *boss_light = &lights[1];
uint nlights = 1;
if(stage3_draw_data->boss_light_alpha > 0) {
glm_vec3_scale(
stage3_draw_data->boss_light.radiance,
stage3_draw_data->boss_light_alpha,
boss_light->radiance
);
glm_vec3_copy(stage3_draw_data->boss_light.pos, boss_light->pos);
nlights++;
}
vec3 r;
camera3d_unprojected_ray(cam, global.plr.pos,r);
camera3d_unprojected_ray(cam, global.plr.pos, r);
glm_vec3_scale(r, 5, r);
glm_vec3_add(cam->pos, r, lights[1].pos);
glm_vec3_add(cam->pos, r, lights[0].pos);
if(global.frames > 6000) { // wriggle
lights[0].radiance[0] = 20;
lights[0].radiance[1] = 10;
lights[0].radiance[2] = 40;
}
camera3d_set_point_light_uniforms(cam, ARRAY_SIZE(lights), lights);
camera3d_set_point_light_uniforms(cam, nlights, lights);
}
static void stage3_bg_setup_pbr_env(Camera3D *cam, PBREnvironment *env) {
Stage3DrawData *dd = stage3_get_draw_data();
stage3_bg_setup_pbr_lighting(cam);
float f = 1.0f / (1.0f + global.frames / 1000.0f);
glm_vec3_copy((vec3) { f, f, sqrtf(f) }, env->ambient_color);
glm_vec3_copy(dd->ambient_color, env->ambient_color);
glm_vec3_copy(dd->environment_color, env->environment_color);
camera3d_apply_inverse_transforms(cam, env->cam_inverse_transform);
env->environment_map = stage3_draw_data->envmap;
env->disable_tonemap = true;
}
static void stage3_bg_ground_draw(vec3 pos) {
@ -114,6 +111,8 @@ void stage3_drawsys_init(void) {
pbr_load_model(&stage3_draw_data->models.ground, "stage3/ground", "stage3/ground"); pbr_load_model(&stage3_draw_data->models.leaves, "stage3/leaves", "stage3/leaves");
pbr_load_model(&stage3_draw_data->models.rocks, "stage3/rocks", "stage3/rocks");
pbr_load_model(&stage3_draw_data->models.trees, "stage3/trees", "stage3/trees");
stage3_draw_data->envmap = res_texture("stage3/envmap");
}
void stage3_drawsys_shutdown(void) {
@ -122,13 +121,16 @@ void stage3_drawsys_shutdown(void) {
}
static bool stage3_fog(Framebuffer *fb) {
r_shader("zbuf_fog");
Stage3DrawData *dd = stage3_get_draw_data();
r_shader("zbuf_fog_tonemap");
r_uniform_sampler("depth", r_framebuffer_get_attachment(fb, FRAMEBUFFER_ATTACH_DEPTH));
r_uniform_vec4("fog_color", 0.8, 0.5, 1, 1.0);
r_uniform_float("start", 0.6);
r_uniform_vec4_vec("fog_color", dd->fog_color);
r_uniform_float("start", 0.9);
r_uniform_float("end", 2);
r_uniform_float("exponent", 10);
r_uniform_float("exponent", 1);
r_uniform_float("curvature", 0);
float e = 1;
r_uniform_vec3("exposure", e, e, e);
draw_framebuffer_tex(fb, VIEWPORT_W, VIEWPORT_H);
r_shader_standard();
return true;

View file

@ -20,6 +20,18 @@ typedef struct Stage3DrawData {
PBRModel rocks;
PBRModel trees;
} models;
Texture *envmap;
vec3 environment_color;
vec3 ambient_color;
vec4 fog_color;
float boss_light_alpha;
PointLight3D boss_light;
float swing_strength;
float target_swing_strength;
} Stage3DrawData;
Stage3DrawData *stage3_get_draw_data(void)

View file

@ -10,4 +10,5 @@
#include "taisei.h"
#define ENTITIES_STAGE3(X, ...) \
X(WriggleSlave, __VA_ARGS__) \
END_OF_ENTITIES

View file

@ -12,104 +12,95 @@
#include "../wriggle.h"
#include "global.h"
#include "common_tasks.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(slave, { BoxedBoss boss; real rot_speed; real rot_initial; int level; }) {
Boss *boss = TASK_BIND(ARGS.boss);
WriggleSlave *slave = stage3_host_wriggle_slave(boss->pos);
static int wriggle_nonspell_slave(Enemy *e, int time) {
TIMER(&time)
INVOKE_SUBTASK(wriggle_slave_follow,
.slave = ENT_BOX(slave),
.boss = ENT_BOX(boss),
.rot_speed = ARGS.rot_speed,
.rot_initial = ARGS.rot_initial
);
int level = e->args[3];
float angle = e->args[2] * (time / 70.0 + e->args[1]);
cmplx dir = cexp(I*angle);
Boss *boss = (Boss*)REF(e->args[0]);
int delay = difficulty_value(9, 8, 7, 6);
if(!boss)
return ACTION_DESTROY;
AT(EVENT_DEATH) {
free_ref(e->args[0]);
return 1;
if(ARGS.level > 2) {
delay += 4;
}
if(time < 0)
return 1;
WAIT(1);
GO_TO(e, boss->pos + (100 + 20 * e->args[2] * sin(time / 100.0)) * dir, 0.03)
int d = 10 - global.diff;
if(level > 2)
d += 4;
if(!(time % d)) {
play_sound("shot1");
for(int t = 0;; t += WAIT(delay)) {
play_sfx("shot1");
cmplx aim = cnormalize(boss->pos - slave->pos);
PROJECTILE(
.proto = pp_rice,
.pos = e->pos,
.pos = slave->pos,
.color = RGB(0.7, 0.2, 0.1),
.rule = linear,
.args = { 3 * cexp(I*carg(boss->pos - e->pos)) },
.move = move_linear(3 * aim),
);
if(!(time % (d*2)) || level > 1) {
if(!(t % (delay * 2)) || ARGS.level > 1) {
PROJECTILE(
.proto = pp_thickrice,
.pos = e->pos,
.pos = slave->pos,
.color = RGB(0.7, 0.7, 0.1),
.rule = linear,
.args = { 2.5 * cexp(I*carg(boss->pos - e->pos)) },
.move = move_linear(2.5 * aim),
);
}
if(level > 2) {
if(ARGS.level > 2) {
PROJECTILE(
.proto = pp_wave,
.pos = e->pos,
.color = RGB(0.3, 0.1 + 0.6 * psin(time / 25.0), 0.7),
.rule = linear,
.args = { 2 * cexp(I*carg(boss->pos - e->pos)) },
.pos = slave->pos,
.color = RGB(0.3, 0.1 + 0.6 * psin(t / 25.0), 0.7),
.move = move_linear(2 * aim),
);
}
}
return 1;
}
static void wriggle_nonspell_common(Boss *boss, int time, int level) {
TIMER(&time)
int i, j, cnt = 3 + global.diff;
static void wriggle_nonspell_common(Boss *boss, int level) {
int n = difficulty_value(4, 5, 6, 7);
AT(0) for(j = -1; j < 2; j += 2) for(i = 0; i < cnt; ++i)
create_enemy4c(boss->pos, ENEMY_IMMUNE, wriggle_slave_visual, wriggle_nonspell_slave, add_ref(boss), i*2*M_PI/cnt, j, level);
AT(EVENT_DEATH) {
enemy_kill_all(&global.enemies);
return;
for(int i = 0; i < n; ++i) {
INVOKE_SUBTASK(slave, ENT_BOX(boss), 1/70.0, i*M_TAU/n, level);
INVOKE_SUBTASK(slave, ENT_BOX(boss), -1/70.0, -i*M_TAU/n, level);
}
if(time < 0) {
GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/3, 0.05)
return;
Rect wander_bounds = viewport_bounds(120);
wander_bounds.bottom = 180 + 20 * level;
real wander_dist = 60 + 10 * level;
boss->move = move_towards(boss->pos, 0.02);
for(;;) {
WAIT(120);
boss->move.attraction_point = common_wander(boss->pos, wander_dist, wander_bounds);
WAIT(120);
}
FROM_TO(120, 240, 1)
GO_TO(boss, VIEWPORT_W/3 + VIEWPORT_H*I/3, 0.03)
FROM_TO(360, 480, 1)
GO_TO(boss, VIEWPORT_W - VIEWPORT_W/3 + VIEWPORT_H*I/3, 0.03)
FROM_TO(600, 720, 1)
GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/3, 0.03)
STALL;
}
void stage3_boss_nonspell1(Boss *boss, int time) {
wriggle_nonspell_common(boss, time, 1);
DEFINE_EXTERN_TASK(stage3_boss_nonspell_1) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
wriggle_nonspell_common(boss, 1);
}
void stage3_boss_nonspell2(Boss *boss, int time) {
wriggle_nonspell_common(boss, time, 2);
DEFINE_EXTERN_TASK(stage3_boss_nonspell_2) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
wriggle_nonspell_common(boss, 2);
}
void stage3_boss_nonspell3(Boss *boss, int time) {
wriggle_nonspell_common(boss, time, 3);
DEFINE_EXTERN_TASK(stage3_boss_nonspell_3) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
BEGIN_BOSS_ATTACK(&ARGS);
wriggle_nonspell_common(boss, 3);
}

View file

@ -11,89 +11,104 @@
#include "nonspells.h"
#include "../scuttle.h"
#include "common_tasks.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(bite_bullet, { cmplx pos; cmplx vel; }) {
Color phase_colors[] = {
{ 2.0, 0.0, 0.1, 1.0 },
{ 1.2, 0.3, 0.1, 1.0 },
{ 0.3, 0.7, 0.1, 1.0 },
};
static int scuttle_lethbite_proj(Projectile *p, int time) {
if(time < 0) {
return ACTION_ACK;
}
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_wave,
.pos = ARGS.pos,
.color = phase_colors,
.max_viewport_dist = 50,
));
#define A0_PROJ_START 120
#define A0_PROJ_CHARGE 20
TIMER(&time)
int aimed_speed = difficulty_value(3.0, 3.5, 4.5, 5.0);
FROM_TO(A0_PROJ_START, A0_PROJ_START + A0_PROJ_CHARGE, 1)
return 1;
p->move = move_asymptotic_simple(ARGS.vel, 2.0);
WAIT(120);
AT(A0_PROJ_START + A0_PROJ_CHARGE + 1) if(p->type != PROJ_DEAD) {
p->args[1] = 3;
p->args[0] = (3 + 2 * global.diff / (float)D_Lunatic) * cexp(I*carg(global.plr.pos - p->pos));
for(int phase = 1; phase < ARRAY_SIZE(phase_colors); ++phase) {
p->move.acceleration = 0;
p->flags |= PFLAG_MANUALANGLE;
int cnt = 3, i;
for(i = 0; i < cnt; ++i) {
tsrand_fill(2);
for(int i = 0; i < 20; ++i, YIELD) {
fapproach_asymptotic_p(&p->angle, carg(global.plr.pos - p->pos), 0.2, 1e-3);
}
play_sfx("redirect");
play_sfx("shot1");
cmplx aim = cnormalize(global.plr.pos - p->pos);
p->angle = carg(aim);
p->color = phase_colors[phase];
spawn_projectile_highlight_effect(p);
p->move = move_asymptotic_simple(aimed_speed * cnormalize(aim), 3.0);
PROJECTILE(
.proto = pp_thickrice,
.pos = p->pos,
.color = RGB(0.4, 0.3, 1.0),
.move = move_linear(-aim * 0.5),
);
for(int i = 0; i < 3; ++i) {
cmplx v = rng_dir();
v *= rng_range(1, 1.5);
v += aim * aimed_speed;
PARTICLE(
.pos = p->pos,
.sprite = "smoothdot",
.color = RGBA(0.8, 0.6, 0.6, 0),
.draw_rule = Shrink,
.rule = enemy_flare,
.timeout = 100,
.args = {
cexp(I*(M_PI*anfrand(0))) * (1 + afrand(1)),
add_ref(p)
},
);
float offset = global.frames/15.0;
if(global.diff > D_Hard && global.boss) {
offset = M_PI+carg(global.plr.pos-global.boss->pos);
}
PROJECTILE(
.proto = pp_thickrice,
.pos = p->pos,
.color = RGB(0.4, 0.3, 1.0),
.rule = linear,
.args = {
-cexp(I*(i*2*M_PI/cnt + offset)) * (1.0 + (global.diff > D_Normal))
},
.draw_rule = pdraw_timeout_scale(1, 0.01),
.timeout = rng_irange(20, 40),
.move = move_asymptotic_simple(v, 2),
);
}
play_sound("redirect");
play_sound("shot1");
spawn_projectile_highlight_effect(p);
WAIT(60);
}
return asymptotic(p, time);
#undef A0_PROJ_START
#undef A0_PROJ_CHARGE
}
void stage3_midboss_nonspell1(Boss *boss, int time) {
int i;
TIMER(&time)
DEFINE_EXTERN_TASK(stage3_midboss_nonspell_1) {
STAGE_BOOKMARK(non1);
GO_TO(boss, VIEWPORT_W/2+VIEWPORT_W/3*sin(time/300) + I*cimag(boss->pos), 0.01)
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
boss->move = move_towards(VIEWPORT_W/2 + 100.0*I, 0.03);
BEGIN_BOSS_ATTACK(&ARGS);
FROM_TO_INT(0, 90000, 72 + 6 * (D_Lunatic - global.diff), 0, 1) {
int cnt = 21 - 1 * (D_Lunatic - global.diff);
Rect bounds = viewport_bounds(80);
bounds.bottom = 210;
for(i = 0; i < cnt; ++i) {
cmplx v = (2 - psin((fmax(3, global.diff+1)*2*M_PI*i/(float)cnt) + time)) * cexp(I*2*M_PI/cnt*i);
PROJECTILE(
.proto = pp_wave,
.pos = boss->pos - v * 50,
.color = _i % 2? RGB(0.7, 0.3, 0.0) : RGB(0.3, .7, 0.0),
.rule = scuttle_lethbite_proj,
.args = { v, 2.0 },
int time = 0;
int cnt = difficulty_value(18, 19, 20, 21);
int shape = difficulty_value(3, 3, 4, 5);
int delay = 120;
cmplx origin;
Color charge_color = *RGBA(1, 0, 0, 0);
for(;;) {
origin = boss->pos;
boss->move.attraction_point = common_wander(boss->pos, 200, bounds);
time += common_charge_static(delay, origin, &charge_color);
for(int i = 0; i < cnt; ++i) {
play_sfx("shot_special1");
cmplx v = (2 - psin((shape*M_TAU*i/(real)cnt) + time)) * cdir(M_TAU/cnt*i);
INVOKE_TASK(bite_bullet,
.pos = origin - v * 25,
.vel = v,
);
}
// FIXME: better sound
play_sound("shot_special1");
delay = imax(60, delay - 10);
}
}

View file

@ -11,9 +11,7 @@
#include "boss.h"
void stage3_midboss_nonspell1(Boss *boss, int time);
void stage3_boss_nonspell1(Boss *boss, int time);
void stage3_boss_nonspell2(Boss *boss, int time);
void stage3_boss_nonspell3(Boss *boss, int time);
DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_midboss_nonspell_1, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_boss_nonspell_1, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_boss_nonspell_2, BossAttack);
DECLARE_EXTERN_TASK_WITH_INTERFACE(stage3_boss_nonspell_3, BossAttack);

View file

@ -39,5 +39,6 @@ Boss *stage3_spawn_scuttle(cmplx pos) {
boss_set_portrait(scuttle, "scuttle", NULL, "normal");
scuttle->glowcolor = *RGB(0.5, 0.6, 0.3);
scuttle->shadowcolor = *RGBA_MUL_ALPHA(0.7, 0.3, 0.1, 0.5);
scuttle->zoomcolor = *RGB(0.4, 0.1, 0.4);
return scuttle;
}

View file

@ -14,108 +14,114 @@
#include "common_tasks.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(spawner_proj, { cmplx pos; MoveParams move; Color *color; int t; int i; }) {
Projectile *p = TASK_BIND(PROJECTILE(
.proto = pp_wave,
.pos = ARGS.pos,
.color = ARGS.color,
.move = ARGS.move,
));
static int scuttle_poison(Projectile *p, int time) {
int result = accelerated(p, time);
int t = ARGS.t;
int i = ARGS.i;
if(time < 0)
return result;
WAIT(150);
real a = (M_PI/(5 + global.diff) * i * 2);
if(!(time % (57 - global.diff * 3)) && p->type != PROJ_DEAD) {
float a = p->args[2];
float t = p->args[3] + time;
play_sfx("redirect");
PROJECTILE(
.proto = (frand() > 0.5)? pp_thickrice : pp_rice,
.pos = p->pos,
.color = RGB(0.3, 0.7 + 0.3 * psin(a/3.0 + t/20.0), 0.3),
.rule = accelerated,
.args = {
0,
0.005*cexp(I*(M_PI*2 * sin(a/5.0 + t/20.0))),
},
);
play_sound("redirect");
}
return result;
PROJECTILE(
.proto = rng_chance(0.5) ? pp_thickrice : pp_rice,
.pos = p->pos,
.color = RGB(0.3, 0.7 + 0.3 * psin(a/3.0 + t/20.0), 0.3),
.move = move_accelerated(0, 0.005 * cdir((M_TAU * sin(a / 5.0 + t / 20.0))))
);
}
void scuttle_deadly_dance(Boss *boss, int time) {
int i;
TIMER(&time)
TASK(spawners, { BoxedBoss boss; real tfactor; }) {
Boss *boss = TASK_BIND(ARGS.boss);
int count = difficulty_value(15, 18, 21, 24);
real tfactor = ARGS.tfactor;
if(time < 0) {
return;
}
for(int time = 0;; time += WAIT(70)) {
real angle_ofs = rng_angle();
play_sfx("shot_special1");
AT(0) {
aniplayer_queue(&boss->ani, "dance", 0);
}
play_sfx_loop("shot1_loop");
FROM_TO(0, 120, 1)
GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/2, 0.03)
if(time > 30) {
float angle_ofs = frand() * M_PI * 2;
double t = time * 1.5 * (0.4 + 0.3 * global.diff);
double moverad = fmin(160, time/2.7);
GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/2 + sin(t/50.0) * moverad * cexp(I * M_PI_2 * t/100.0), 0.03)
if(!(time % 70)) {
for(i = 0; i < 15; ++i) {
double a = M_PI/(5 + global.diff) * i * 2;
PROJECTILE(
.proto = pp_wave,
.pos = boss->pos,
.color = RGB(0.3, 0.3 + 0.7 * psin(a*3 + time/50.0), 0.3),
.rule = scuttle_poison,
.args = {
0,
0.02 * cexp(I*(angle_ofs+a+time/10.0)),
a,
time
}
);
}
play_sound("shot_special1");
}
if(global.diff > D_Easy && !(time % 35)) {
int cnt = global.diff * 2;
for(i = 0; i < cnt; ++i) {
PROJECTILE(
.proto = pp_ball,
.pos = boss->pos,
.color = RGB(1.0, 1.0, 0.3),
.rule = asymptotic,
.args = {
(0.5 + 3 * psin(time + M_PI/3*2*i)) * cexp(I*(angle_ofs + time / 20.0 + M_PI/cnt*i*2)),
1.5
}
);
}
play_sound("shot1");
}
}
if(!(time % 3)) {
for(i = -1; i < 2; i += 2) {
double c = psin(time/10.0);
PROJECTILE(
.proto = pp_crystal,
for(int i = 0; i < count; ++i) {
real a = (M_PI/(5 + global.diff) * i * 2);
INVOKE_TASK(spawner_proj,
.pos = boss->pos,
.color = RGBA_MUL_ALPHA(0.3 + c * 0.7, 0.6 - c * 0.3, 0.3, 0.7),
.rule = linear,
.args = {
10 * cexp(I*(carg(global.plr.pos - boss->pos) + (M_PI/4.0 * i * (1-time/2500.0)) * (1 - 0.5 * psin(time/15.0))))
}
.color = RGB(0.3, 0.3 + 0.7 * psin((M_PI/(5 + global.diff) * i * 2) * 3 + time/50.0), 0.3),
.move = move_accelerated(0, 0.02 * cdir(angle_ofs + a + time/10.0)),
.t = time * tfactor,
.i = i,
);
}
}
}
TASK(balls, { BoxedBoss boss; }) {
Boss *boss = TASK_BIND(ARGS.boss);
int count = difficulty_value(2, 4, 6, 8);
for(int time = 0;; time += WAIT(35)) {
real angle_ofs = rng_angle();
play_sfx("shot1");
for(int i = 0; i < count; ++i) {
PROJECTILE(
.proto = pp_ball,
.pos = boss->pos,
.color = RGB(1.0, 1.0, 0.3),
.move = move_asymptotic_simple(
(0.5 + 3 * psin(time + M_PI / 3 * 2 * i)) * cdir(angle_ofs + time / 20.0 + M_PI / count * i * 2),
1.5
)
);
}
}
}
TASK(limiters, { BoxedBoss boss; }) {
Boss *boss = TASK_BIND(ARGS.boss);
for(int time = 0;; time += WAIT(3)) {
play_sfx_loop("shot1_loop");
for(int i = -1; i < 2; i += 2) {
real c = psin(time/10.0);
cmplx aim = cnormalize(global.plr.pos - boss->pos);
cmplx v = 10 * (aim + (M_PI/4.0 * i * (1 - time / 2500.0)) * (1 - 0.5 * psin(time / 15.0)));
PROJECTILE(
.proto = pp_crystal,
.pos = boss->pos,
.color = RGBA_MUL_ALPHA(0.3 + c * 0.7, 0.6 - c * 0.3, 0.3, 0.7),
.move = move_linear(v),
);
}
}
}
DEFINE_EXTERN_TASK(stage3_spell_deadly_dance) {
STAGE_BOOKMARK(dance);
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
boss->move = move_towards(VIEWPORT_W/2 + VIEWPORT_H*I/2, 0.08);
BEGIN_BOSS_ATTACK(&ARGS);
boss->move.attraction = 0;
aniplayer_queue(&boss->ani, "dance", 0);
real tfactor = difficulty_value(1.05, 1.5, 1.95, 2.4);
INVOKE_SUBTASK(limiters, ENT_BOX(boss));
INVOKE_SUBTASK(spawners, ENT_BOX(boss), .tfactor = tfactor);
if(global.diff > D_Easy) {
INVOKE_SUBTASK(balls, ENT_BOX(boss));
}
for(int time = 0;; ++time, YIELD) {
real t = time * tfactor;
real moverad = fmin(160, time/2.7);
boss->pos = VIEWPORT_W/2 + VIEWPORT_H*I/2 + sin(t/50.0) * moverad * cdir(M_PI/2 * t/100.0);
}
}

View file

@ -14,9 +14,6 @@
#include "common_tasks.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
DEPRECATED_DRAW_RULE
static void wriggle_fstorm_proj_draw(Projectile *p, int time, ProjDrawRuleArgs args) {
float f = 1-fmin(time/60.0,1);
r_mat_mv_push();
@ -29,7 +26,7 @@ static void wriggle_fstorm_proj_draw(Projectile *p, int time, ProjDrawRuleArgs a
Sprite *s = p->sprite;
Color c = p->color;
c.a = 0;
p->sprite = get_sprite("proj/ball");
p->sprite = res_sprite("proj/ball");
r_mat_mv_scale(f,f,f);
ProjDrawCore(p, &c);
p->sprite = s;
@ -38,85 +35,79 @@ static void wriggle_fstorm_proj_draw(Projectile *p, int time, ProjDrawRuleArgs a
r_mat_mv_pop();
}
static int wriggle_fstorm_proj(Projectile *p, int time) {
if(time < 0) {
return ACTION_ACK;
}
TASK(fstorm_bullet, { BoxedBoss boss; ProjPrototype *proto; cmplx pos; cmplx vel; int convert_time; }) {
Projectile *p = TASK_BIND(PROJECTILE(
.proto = ARGS.proto,
.pos = ARGS.pos,
.color = RGB(0.2, 0.2, 0.6),
.move = move_linear(ARGS.vel),
));
if(cabs(global.plr.pos-p->pos) > 100) {
p->args[2]+=1;
} else {
p->args[2]-=1;
if(creal(p->args[2]) < 0)
p->args[2] = 0;
}
int t = 0;
int turntime = rint(creal(p->args[0]));
int t = rint(creal(p->args[2]));
if(t < turntime) {
float f = t/(float)turntime;
p->color = *RGB(0.3+0.7*(1 - pow(1 - f, 4)), 0.3+0.3*f*f, 0.7-0.7*f);
}
if(t == turntime && global.boss) {
p->args[1] = global.boss->pos-p->pos;
p->args[1] *= 2/cabs(p->args[1]);
p->angle = carg(p->args[1]);
p->birthtime = global.frames;
p->draw_rule = (ProjDrawRule) { wriggle_fstorm_proj_draw };
p->sprite = NULL;
projectile_set_prototype(p, pp_rice);
spawn_projectile_highlight_effect(p);
for(int i = 0; i < 3; ++i) {
tsrand_fill(2);
PARTICLE(
.sprite = "flare",
.pos = p->pos,
.rule = linear,
.timeout = 60,
.args = { (1+afrand(0))*cexp(I*tsrand_a(1)) },
.draw_rule = Shrink,
);
for(;;YIELD) {
if(cabs(global.plr.pos - p->pos) > 100) {
++t;
} else {
t = imax(0, t - 1);
}
play_sound_ex("redirect", 3, false);
if(t >= ARGS.convert_time) {
break;
}
real f = t / (real)ARGS.convert_time;
p->color = *RGB(0.3 + 0.7 * (1 - pow(1 - f, 4)), 0.3 + 0.3 * f * f, 0.7 - 0.7 * f);
}
p->pos += p->args[1];
return ACTION_NONE;
Boss *boss = NOT_NULL(ENT_UNBOX(ARGS.boss));
p->move.velocity = 2 * cnormalize(boss->pos - p->pos);
p->birthtime = global.frames;
p->draw_rule = (ProjDrawRule) { wriggle_fstorm_proj_draw };
p->sprite = NULL;
projectile_set_prototype(p, pp_rice);
spawn_projectile_highlight_effect(p);
for(int i = 0; i < 3; ++i) {
RNG_ARRAY(r, 2);
PARTICLE(
.sprite = "flare",
.pos = p->pos,
.timeout = 60,
.move = move_linear((1 + vrng_real(r[0])) * vrng_dir(r[1])),
.draw_rule = pdraw_timeout_scale(2, 0.01),
);
}
play_sfx_ex("redirect", 3, false);
}
void wriggle_firefly_storm(Boss *boss, int time) {
TIMER(&time)
if(time < 0) {
GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/2, 0.05)
return;
}
DEFINE_EXTERN_TASK(stage3_spell_firefly_storm) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
boss->move = move_towards(VIEWPORT_W/2 + VIEWPORT_H*I/2, 0.05);
BEGIN_BOSS_ATTACK(&ARGS);
bool lun = global.diff == D_Lunatic;
aniplayer_queue(&boss->ani, "fly", 0);
AT(0) {
aniplayer_queue(&boss->ani,"fly",0);
}
int convert_time = difficulty_value(40, 55, 75, 100);
cmplx bullet_rotation = cdir(difficulty_value(0.6, 0.6, 0.6, 0));
FROM_TO_SND("shot1_loop", 30, 9000, 2) {
int i, cnt = 2;
for(i = 0; i < cnt; ++i) {
float r = tanh(sin(_i/200.));
float v = lun ? cos(_i/150.)/pow(cosh(atanh(r)),2) : 0.5;
cmplx pos = 230*cexp(I*(_i*0.301+2*M_PI/cnt*i))*r;
WAIT(30);
PROJECTILE(
for(int cycle = 0;; ++cycle, WAIT(2)) {
int cnt = 2;
for(int i = 0; i < cnt; ++i) {
real r = fmax(0.05, tanh(sin(cycle / 200.0)));
real v = lun ? cos(cycle / 150.0) / pow(cosh(atanh(r)), 2) : 0.5;
cmplx pos = 230 * cdir(cycle * 0.301 + M_TAU / cnt * i) * r;
INVOKE_SUBTASK(fstorm_bullet,
.boss = ENT_BOX(boss),
.proto = (global.diff >= D_Hard) && !(i%10) ? pp_bigball : pp_ball,
.pos = boss->pos+pos,
.color = RGB(0.2,0.2,0.6),
.rule = wriggle_fstorm_proj,
.args = {
(global.diff == D_Easy) ? 40 : 100-25*(!lun)-20*(global.diff == D_Normal),
cexp(I*(!lun)*0.6)*pos/cabs(pos)*(1+v)
},
.pos = boss->pos + pos,
.vel = bullet_rotation * cnormalize(pos) * (1 + v),
.convert_time = convert_time
);
}
}

View file

@ -14,111 +14,95 @@
#include "common_tasks.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(singularity_laser, { cmplx pos; cmplx vel; real amp; real freq; }) {
Laser *l = TASK_BIND(create_laser(ARGS.pos,
200, 10000, RGBA(0.0, 0.2, 1.0, 0.0), las_sine_expanding,
NULL, ARGS.vel, ARGS.amp, ARGS.freq, 0
));
static void wriggle_singularity_laser_logic(Laser *l, int time) {
if(time == EVENT_BIRTH) {
l->width = 0;
l->speed = 0;
l->timeshift = l->timespan;
l->unclearable = true;
return;
}
laser_make_static(l);
l->unclearable = true;
if(time == 140) {
play_sound("laser1");
}
real spin_factor = difficulty_value(1.05, 1.4, 1.75, 2.1);
laser_charge(l, time, 150, 10 + 10 * psin(l->args[0] + time / 60.0));
l->args[3] = time / 10.0;
l->args[0] *= cexp(I*(M_PI/500.0) * (0.7 + 0.35 * global.diff));
l->color = *HSLA((carg(l->args[0]) + M_PI) / (M_PI * 2), 1.0, 0.5, 0.0);
}
void wriggle_light_singularity(Boss *boss, int time) {
TIMER(&time)
AT(EVENT_DEATH) {
return;
}
time -= 120;
if(time < 0) {
GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/2, 0.05)
return;
}
AT(0) {
int cnt = 2 + global.diff;
for(int i = 0; i < cnt; ++i) {
double aofs = 0;
if(global.diff == D_Hard || global.diff == D_Easy) {
aofs = 0.7;
}
cmplx vel = 2 * cexp(I*(aofs + M_PI / 4 + M_PI * 2 * i / (double)cnt));
double amp = (4.0/cnt) * (M_PI/5.0);
double freq = 0.05;
create_laser(boss->pos, 200, 10000, RGBA(0.0, 0.2, 1.0, 0.0), las_sine_expanding,
wriggle_singularity_laser_logic, vel, amp, freq, 0);
for(int t = 0;; ++t, YIELD) {
if(t == 140) {
play_sfx("laser1");
}
play_sound("charge_generic");
aniplayer_queue(&boss->ani, "main", 0);
laser_charge(l, t, 150, 10 + 10 * psin(l->args[0] + t / 60.0));
l->args[3] = t / 10.0; // phase
l->args[0] *= cexp(I*(M_PI/500.0) * spin_factor);
l->color = *HSLA((carg(l->args[0]) + M_PI) / (M_PI * 2), 1.0, 0.5, 0.0);
}
}
DEFINE_EXTERN_TASK(stage3_spell_light_singularity) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
boss->move = move_towards(VIEWPORT_W/2 + VIEWPORT_H*I/2, 0.05);
BEGIN_BOSS_ATTACK(&ARGS);
WAIT(100);
aniplayer_queue(&boss->ani, "specialshot_charge", 1);
aniplayer_queue(&boss->ani, "specialshot_release", 1);
aniplayer_queue(&boss->ani, "main", 0);
WAIT(20);
int nlasers = difficulty_value(3, 4, 5, 6);
int nbullets = difficulty_value(8, 10, 12, 14);
real laser_angle_ofs = difficulty_value(0.7, 0, 0.7, 0);
real bullet_speed = difficulty_value(1, 0.8, 0.6, 0.4);
ProjPrototype *ptypes[] = {
pp_thickrice,
pp_rice,
pp_bullet,
pp_wave,
pp_ball,
pp_plainball,
pp_bigball,
pp_soul,
};
int bullets_cycle = 0;
for(int i = 0; i < nlasers; ++i) {
INVOKE_TASK(singularity_laser,
.pos = boss->pos,
.vel = 2 * cdir(laser_angle_ofs + M_PI / 4 + M_PI * 2 * i / (real)nlasers),
.amp = (4.0 / nlasers) * (M_PI / 5),
.freq = 0.05
);
}
if(time > 120) {
play_sfx_loop("shot1_loop");
}
if(time == 0) {
return;
}
if(!((time+30) % 300)) {
for(;;YIELD) {
WAIT(270);
aniplayer_queue(&boss->ani, "specialshot_charge", 1);
aniplayer_queue(&boss->ani, "specialshot_release", 1);
aniplayer_queue(&boss->ani, "main", 0);
}
WAIT(30);
if(!(time % 300)) {
ProjPrototype *ptype = NULL;
ProjPrototype *ptype = ptypes[bullets_cycle];
switch(time / 300 - 1) {
case 0: ptype = pp_thickrice; break;
case 1: ptype = pp_rice; break;
case 2: ptype = pp_bullet; break;
case 3: ptype = pp_wave; break;
case 4: ptype = pp_ball; break;
case 5: ptype = pp_plainball; break;
case 6: ptype = pp_bigball; break;
default: ptype = pp_soul; break;
if(bullets_cycle < ARRAY_SIZE(ptypes) - 1) {
++bullets_cycle;
}
int cnt = 6 + 2 * global.diff;
float colorofs = frand();
real colorofs = rng_real();
for(int i = 0; i < cnt; ++i) {
double a = ((M_PI*2.0*i)/cnt);
cmplx dir = cexp(I*a);
for(int i = 0; i < nbullets; ++i) {
real f = i / (real)nbullets;
cmplx dir = cdir(M_TAU * f);
PROJECTILE(
.proto = ptype,
.pos = boss->pos,
.color = HSLA(a/(M_PI*2) + colorofs, 1.0, 0.5, 0),
.rule = asymptotic,
.args = {
dir * (1.2 - 0.2 * global.diff),
20
},
.color = HSLA(f + colorofs, 1.0, 0.5, 0),
.move = move_asymptotic_simple(dir * bullet_speed, 20),
);
}
play_sound("shot_special1");
play_sfx("shot_special1");
}
}

View file

@ -14,21 +14,192 @@
#include "common_tasks.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(aimcircle, { int lifetime; int focustime; }) {
auto *p = TASK_BIND(PARTICLE(
.sprite_ptr = res_sprite("fairy_circle"),
.layer = LAYER_PARTICLE_LOW,
.flags = PFLAG_NOMOVE | PFLAG_REQUIREDPARTICLE | PFLAG_MANUALANGLE | PFLAG_NOAUTOREMOVE,
.pos = global.plr.pos,
.angle_delta = M_TAU/60,
.color = RGB(2, 0.75, 0.5),
));
void wriggle_moonlight_rocket(Boss *boss, int time) {
int i, j, cnt = 1 + global.diff;
TIMER(&time)
int spawntime = 30;
int despawntime = 30;
AT(EVENT_DEATH) {
enemy_kill_all(&global.enemies);
return;
for(int t = 0; t < ARGS.lifetime; ++t, YIELD) {
if(t < ARGS.focustime) {
capproach_asymptotic_p(&p->pos, global.plr.pos, 0.2, 1e-3);
} else if(t == ARGS.focustime) {
p->pos = global.plr.pos;
}
float sf = fminf(t / (float)spawntime, 1.0f);
float sf2 = fminf(t / (float)(spawntime * 2), 1.0f);
float df = fminf((ARGS.lifetime - t - 1) / (float)despawntime, 1.0f);
df = glm_ease_back_out(df);
p->opacity = glm_ease_quad_out(sf) * glm_ease_quad_in(df) * 0.1f;
sf = glm_ease_quad_in(sf);
sf = glm_ease_back_out(sf) * 0.9 + sf * 0.1;
p->scale = (1+I) * (1.0f + 6 * (1 - sf));
p->angle -= p->angle_delta * 10.0f * (1 - sf2);
}
if(time < 0)
GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/2.5, 0.05)
else if(time == 0) {
for(j = -1; j < 2; j += 2) for(i = 0; i < cnt; ++i)
create_enemy3c(boss->pos, ENEMY_IMMUNE, wriggle_slave_visual, wriggle_spell_slave, add_ref(boss), i*2*M_PI/cnt, j);
kill_projectile(p);
}
TASK(cancel_event, { CoEvent *event; }) {
coevent_cancel(ARGS.event);
}
TASK(laser_bullet, { BoxedProjectile p; BoxedLaser l; CoEvent *event; int event_time; }) {
Laser *l = NOT_NULL(ENT_UNBOX(ARGS.l));
if(ARGS.event) {
INVOKE_TASK_AFTER(&TASK_EVENTS(THIS_TASK)->finished, cancel_event, ARGS.event);
}
Projectile *p = TASK_BIND(ARGS.p);
for(int t = 0; (l = ENT_UNBOX(ARGS.l)); ++t, YIELD) {
p->pos = l->prule(l, t);
if(t == 0) {
p->prevpos = p->pos;
}
if(t == ARGS.event_time && ARGS.event) {
coevent_signal(ARGS.event);
break;
}
}
kill_projectile(p);
}
TASK(rocket, { BoxedBoss boss; cmplx pos; cmplx dir; Color color; real phase; real accel_rate; int rocket_time; }) {
Boss *boss = TASK_BIND(ARGS.boss);
real dt = ARGS.rocket_time;
Laser *l = create_lasercurve4c(
ARGS.pos, dt, dt, &ARGS.color, las_sine_expanding, 2.5*ARGS.dir, M_PI/20, 0.2, ARGS.phase
);
Projectile *p = PROJECTILE(
.proto = pp_ball,
.color = RGB(1.0, 0.4, 0.6),
.flags = PFLAG_NOMOVE,
);
COEVENTS_ARRAY(phase2, explosion) events;
TASK_HOST_EVENTS(events);
INVOKE_TASK(laser_bullet, ENT_BOX(p), ENT_BOX(l), &events.phase2, dt);
WAIT_EVENT_OR_DIE(&events.phase2);
p = PROJECTILE(
.pos = p->pos,
.proto = pp_plainball,
.color = RGB(1.0, 0.4, 0.6),
.flags = PFLAG_NOMOVE,
);
play_sfx("redirect");
cmplx dist = global.plr.pos - p->pos;
cmplx accel = ARGS.accel_rate * cnormalize(dist);
dt = sqrt(2 * cabs(dist) / ARGS.accel_rate);
dt += 2 * rng_f64s();
l = create_lasercurve2c(p->pos, dt, dt, RGBA(0.4, 0.9, 1.0, 0.0), las_accel, 0, accel);
l->width = 15;
INVOKE_TASK(laser_bullet, ENT_BOX(p), ENT_BOX(l), &events.explosion, dt);
WAIT_EVENT_OR_DIE(&events.explosion);
// if we get here, p must be still alive and valid
int cnt = 22;
real rot = (global.frames - NOT_NULL(boss->current)->starttime) * 0.0037 * global.diff;
Color *c = HSLA(fmod(rot, M_TAU) / (M_TAU), 1.0, 0.5, 0);
real boost = difficulty_value(4, 6, 8, 10);
for(int i = 0; i < cnt; ++i) {
real f = (real)i/cnt;
cmplx dir = cdir(M_TAU * f + rot);
cmplx v = (1.0 + psin(M_TAU * 9 * f)) * dir;
PROJECTILE(
.proto = pp_thickrice,
.pos = p->pos,
.color = c,
.move = move_asymptotic_simple(v, boost),
);
}
real to = rng_range(30, 35);
real scale = rng_range(2, 2.5);
PARTICLE(
.proto = pp_blast,
.pos = p->pos,
.color = c,
.timeout = to,
.draw_rule = pdraw_timeout_scalefade(0.01, scale, 1, 0),
.angle = rng_angle(),
);
// FIXME: better sound
play_sfx("enemydeath");
play_sfx("shot1");
play_sfx("shot1_special");
kill_projectile(p);
}
TASK(rocket_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
);
int rocket_time = 60;
int warn_time = 20;
int rperiod = difficulty_value(220, 200, 180, 160);
real laccel = difficulty_value(0.15, 0.2, 0.25, 0.3);
WAIT(rperiod/2);
for(;;WAIT(rperiod)) {
play_sfx("laser1");
INVOKE_TASK_DELAYED(rocket_time - warn_time, aimcircle, 60 + warn_time, warn_time);
INVOKE_TASK(rocket, ENT_BOX(boss), slave->pos, dir, *RGBA(1.0, 1.0, 0.5, 0.0), 0, laccel, rocket_time);
INVOKE_TASK(rocket, ENT_BOX(boss), slave->pos, dir, *RGBA(0.5, 1.0, 0.5, 0.0), M_PI, laccel, rocket_time);
}
}
DEFINE_EXTERN_TASK(stage3_spell_moonlight_rocket) {
Boss *boss = INIT_BOSS_ATTACK(&ARGS);
boss->move = move_towards(VIEWPORT_W/2 + VIEWPORT_H*I/2.5, 0.05);
BEGIN_BOSS_ATTACK(&ARGS);
int nslaves = difficulty_value(2, 3, 4, 5);
for(int i = 0; i < nslaves; ++i) {
INVOKE_SUBTASK(rocket_slave, ENT_BOX(boss), 1/70.0, i*M_TAU/nslaves);
INVOKE_SUBTASK(rocket_slave, ENT_BOX(boss), -1/70.0, -i*M_TAU/nslaves);
}
// keep subtasks alive
STALL;
}

View file

@ -11,110 +11,152 @@
#include "spells.h"
#include "../wriggle.h"
#include "refs.h"
#include "common_tasks.h"
#include "global.h"
MODERNIZE_THIS_FILE_AND_REMOVE_ME
TASK(slave, { BoxedBoss boss; real rot_speed; real rot_initial; }) {
Boss *boss = TASK_BIND(ARGS.boss);
cmplx dir;
static int wriggle_ignite_laserbullet(Projectile *p, int time) {
if(time == EVENT_DEATH) {
free_ref(p->args[0]);
return ACTION_ACK;
} else if(time < 0) {
return ACTION_ACK;
}
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
);
Laser *laser = (Laser*)REF(p->args[0]);
WAIT(300);
if(laser) {
p->args[3] = laser->prule(laser, time - p->args[1]) - p->pos;
}
for(;;WAIT(180)) {
int cnt = 5, i;
p->angle = carg(p->args[3]);
p->pos = p->pos + p->args[3];
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)),
);
return ACTION_NONE;
}
static void wriggle_ignite_warnlaser_logic(Laser *l, int time) {
if(time == EVENT_BIRTH) {
l->width = 0;
return;
}
if(time < 0) {
return;
}
if(time == 90) {
play_sound_ex("laser1", 30, false);
}
laser_charge(l, time, 90, 10);
l->color = *color_lerp(RGBA(0.2, 0.2, 1, 0), RGBA(1, 0.2, 0.2, 0), time / l->deathtime);
}
static void wriggle_ignite_warnlaser(Laser *l) {
float f = 6;
create_laser(l->pos, 90, 120, RGBA(1, 1, 1, 0), l->prule, wriggle_ignite_warnlaser_logic, f*l->args[0], l->args[1], f*l->args[2], l->args[3]);
}
void wriggle_night_ignite(Boss *boss, int time) {
TIMER(&time)
float dfactor = global.diff / (float)D_Lunatic;
if(time == EVENT_DEATH) {
enemy_kill_all(&global.enemies);
return;
}
if(time < 0) {
GO_TO(boss, VIEWPORT_W/2 + VIEWPORT_H*I/3, 0.05)
return;
}
AT(0) for(int j = -1; j < 2; j += 2) for(int i = 0; i < 7; ++i) {
create_enemy4c(boss->pos, ENEMY_IMMUNE, wriggle_slave_visual, wriggle_spell_slave, add_ref(boss), i*2*M_PI/7, j, 1);
}
FROM_TO_INT(0, 1000000, 180, 120, 10) {
float dt = 200;
float lt = 100 * dfactor;
float a = _ni*M_PI/2.5 + _i + time;
float b = 0.3;
float c = 0.3;
cmplx vel = 2 * cexp(I*a);
double amp = M_PI/5;
double freq = 0.05;
Laser *l1 = create_lasercurve3c(boss->pos, lt, dt, RGBA(b, b, 1.0, 0.0), las_sine_expanding, vel, amp, freq);
wriggle_ignite_warnlaser(l1);
Laser *l2 = create_lasercurve3c(boss->pos, lt * 1.5, dt, RGBA(1.0, b, b, 0.0), las_sine_expanding, vel, amp, freq - 0.002 * fmin(global.diff, D_Hard));
wriggle_ignite_warnlaser(l2);
Laser *l3 = create_lasercurve3c(boss->pos, lt, dt, RGBA(b, b, 1.0, 0.0), las_sine_expanding, vel, amp, freq - 0.004 * fmin(global.diff, D_Hard));
wriggle_ignite_warnlaser(l3);
for(int i = 0; i < 5 + 15 * dfactor; ++i) {
#define LASERBULLLET(pproto, clr, laser) \
PROJECTILE(.proto = (pproto), .pos = boss->pos, .color = (clr), .rule = wriggle_ignite_laserbullet, .args = { add_ref(laser), i })
LASERBULLLET(pp_plainball, RGBA(c, c, 1.0, 0), l1);
LASERBULLLET(pp_bigball, RGBA(1.0, c, c, 0), l2);
LASERBULLLET(pp_plainball, RGBA(c, c, 1.0, 0), l3);
#undef LASERBULLLET
// FIXME: better sound
play_sound("shot1");
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_sound_ex("shot_special1", 1, false);
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, NULL,
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_towards(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

@ -11,8 +11,8 @@
#include "boss.h"
void scuttle_deadly_dance(Boss*, int t);
void wriggle_moonlight_rocket(Boss*, int t);
void wriggle_night_ignite(Boss*, int t);
void wriggle_firefly_storm(Boss*, int t);
void wriggle_light_singularity(Boss*, int t);
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_deadly_dance, BossAttack);

View file

@ -15,8 +15,6 @@
#include "scuttle.h"
#include "spells/spells.h"
#include "timeline.h"
#include "wriggle.h"
#include "scuttle.h"
#include "global.h"
#include "portrait.h"
@ -31,28 +29,28 @@ struct stage3_spells_s stage3_spells = {
.mid = {
.deadly_dance = {
{ 0, 1, 2, 3}, AT_SurvivalSpell, "Venom Sign “Deadly Dance”", 14, 40000,
scuttle_deadly_dance, stage3_draw_scuttle_spellbg, BOSS_DEFAULT_GO_POS, 3
NULL, stage3_draw_scuttle_spellbg, VIEWPORT_W/2.0+100*I, 1, TASK_INDIRECT_INIT(BossAttack, stage3_spell_deadly_dance)
},
},
.boss = {
.moonlight_rocket = {
{ 6, 7, 8, 9}, AT_Spellcard, "Firefly Sign “Moonlight Rocket”", 40, 40000,
wriggle_moonlight_rocket, stage3_draw_wriggle_spellbg, BOSS_DEFAULT_GO_POS, 3
NULL, stage3_draw_wriggle_spellbg, VIEWPORT_W/2.0+100*I, 1, TASK_INDIRECT_INIT(BossAttack, stage3_spell_moonlight_rocket)
},
.wriggle_night_ignite = {
{10, 11, 12, 13}, AT_Spellcard, "Light Source “Wriggle Night Ignite”", 50, 46000,
wriggle_night_ignite, stage3_draw_wriggle_spellbg, BOSS_DEFAULT_GO_POS, 3
NULL, stage3_draw_wriggle_spellbg, VIEWPORT_W/2.0+100*I, 1, TASK_INDIRECT_INIT(BossAttack, stage3_spell_night_ignite)
},
.firefly_storm = {
{14, 15, 16, 17}, AT_Spellcard, "Bug Sign “Firefly Storm”", 45, 45000,
wriggle_firefly_storm, stage3_draw_wriggle_spellbg, BOSS_DEFAULT_GO_POS, 3
NULL, stage3_draw_wriggle_spellbg, VIEWPORT_W/2.0+100*I, 1, TASK_INDIRECT_INIT(BossAttack, stage3_spell_firefly_storm)
},
},
.extra.light_singularity = {
{ 0, 1, 2, 3}, AT_ExtraSpell, "Lamp Sign “Light Singularity”", 75, 45000,
wriggle_light_singularity, stage3_draw_wriggle_spellbg, BOSS_DEFAULT_GO_POS, 3
NULL, stage3_draw_wriggle_spellbg, VIEWPORT_W/2.0+100*I, 1, TASK_INDIRECT_INIT(BossAttack, stage3_spell_light_singularity)
},
};
@ -61,6 +59,7 @@ static void stage3_start(void) {
stage3_bg_init_fullstage();
stage_start_bgm("stage3");
stage_set_voltage_thresholds(50, 125, 300, 600);
INVOKE_TASK(stage3_timeline);
}
static void stage3_spellpractice_start(void) {
@ -87,6 +86,8 @@ static void stage3_preload(void) {
portrait_preload_face_sprite("scuttle", "normal", RESF_DEFAULT);
preload_resources(RES_BGM, RESF_OPTIONAL, "stage3", "stage3boss", NULL);
preload_resources(RES_TEXTURE, RESF_DEFAULT,
"ibl_brdf_lut",
"stage3/envmap",
"stage3/spellbg1",
"stage3/spellbg2",
"stage3/wspellbg",
@ -131,7 +132,6 @@ StageProcs stage3_procs = {
.draw = stage3_draw,
.end = stage3_end,
.preload = stage3_preload,
.event = stage3_events,
.shader_rules = stage3_bg_effects,
.postprocess_rules = stage3_postprocess,
.spellpractice_procs = &(StageProcs) {

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,6 @@
#pragma once
#include "taisei.h"
void stage3_events(void);
#include "coroutine.h"
#define STAGE3_MIDBOSS_TIME 1765
DECLARE_EXTERN_TASK(stage3_timeline);

View file

@ -38,228 +38,108 @@ Boss *stage3_spawn_wriggle(cmplx pos) {
return wriggle;
}
void wriggle_slave_visual(Enemy *e, int time, bool render) {
if(time < 0)
return;
static void wriggle_slave_draw(EntityInterface *e) {
WriggleSlave *slave = ENT_CAST(e, WriggleSlave);
int time = global.frames - slave->spawn_time;
r_draw_sprite(&(SpriteParams) {
.pos.as_cmplx = slave->pos,
.sprite_ptr = slave->sprites.circle,
.rotation.angle = DEG2RAD * 7 * time,
.scale.as_cmplx = slave->scale,
.color = &slave->color,
});
}
TASK(wriggle_slave_particles, { BoxedWriggleSlave slave; }) {
WriggleSlave *slave = TASK_BIND(ARGS.slave);
int period = 5;
WAIT(rng_irange(0, period));
for(;;WAIT(period)) {
cmplx vel = 2 * rng_dir();
if(render) {
r_draw_sprite(&(SpriteParams) {
.sprite = "fairy_circle",
.rotation.angle = DEG2RAD * 7 * time,
.scale.both = 0.7,
.color = RGBA(0.8, 1.0, 0.4, 0),
.pos = { creal(e->pos), cimag(e->pos) },
});
} else if(time % 5 == 0) {
tsrand_fill(2);
PARTICLE(
.sprite = "smoothdot",
.pos = 5*cexp(2*I*M_PI*afrand(0)),
.sprite_ptr = slave->sprites.particle,
.pos = slave->pos,
.color = RGBA(0.6, 0.6, 0.5, 0),
.draw_rule = Shrink,
.rule = enemy_flare,
.timeout = 60,
.args = {
0.3*cexp(2*M_PI*I*afrand(1)),
add_ref(e),
},
.draw_rule = pdraw_timeout_scale(2, 0.01),
.timeout = 20,
.move = move_linear(vel),
);
}
}
DEPRECATED_DRAW_RULE
static void wriggle_slave_part_draw(Projectile *p, int t, ProjDrawRuleArgs args) {
float b = 1 - t / (double)p->timeout;
r_mat_mv_push();
r_mat_mv_translate(creal(p->pos), cimag(p->pos), 0);
ProjDrawCore(p, color_mul_scalar(COLOR_COPY(&p->color), b));
r_mat_mv_pop();
void stage3_init_wriggle_slave(WriggleSlave *slave, cmplx pos) {
slave->pos = pos;
slave->spawn_time = global.frames;
slave->ent.draw_layer = LAYER_BOSS - 1;
slave->ent.draw_func = wriggle_slave_draw;
slave->sprites.circle = res_sprite("fairy_circle");
slave->sprites.particle = res_sprite("part/smoothdot");
INVOKE_TASK(wriggle_slave_particles, ENT_BOX(slave));
}
static int wriggle_rocket_laserbullet(Projectile *p, int time) {
if(time == EVENT_DEATH) {
free_ref(p->args[0]);
return ACTION_ACK;
} else if(time < 0) {
return ACTION_ACK;
}
WriggleSlave *stage3_host_wriggle_slave(cmplx pos) {
WriggleSlave *slave = TASK_HOST_ENT(WriggleSlave);
TASK_HOST_EVENTS(slave->events);
stage3_init_wriggle_slave(slave, pos);
if(time >= creal(p->args[1])) {
if(p->args[2]) {
cmplx dist = global.plr.pos - p->pos;
cmplx accel = (0.1 + 0.2 * (global.diff / (float)D_Lunatic)) * dist / cabs(dist);
float deathtime = sqrt(2*cabs(dist)/cabs(accel));
// TODO spawn animation
// INVOKE_TASK(wriggle_slave_fadein, ENT_BOX(slave));
slave->color = *RGBA(0.8, 1.0, 0.4, 0);
slave->scale = (1 + I) * 0.7;
Laser *l = create_lasercurve2c(p->pos, deathtime, deathtime, RGBA(0.4, 0.9, 1.0, 0.0), las_accel, 0, accel);
l->width = 15;
// TODO despawn animation
// INVOKE_TASK_AFTER(&slave->events.despawned, wriggle_slave_fadeout, ENT_BOX(slave));
PROJECTILE(
.proto = p->proto,
.pos = p->pos,
.color = &p->color,
.draw_rule = p->draw_rule,
.rule = wriggle_rocket_laserbullet,
.args = {
add_ref(l),
deathtime,
}
);
play_sound("redirect");
play_sound("shot_special1");
} else {
int cnt = 22, i;
float rot = (global.frames - global.boss->current->starttime) * 0.0037 * (global.diff);
Color *c = HSLA(fmod(rot, M_PI*2)/(M_PI/2), 1.0, 0.5, 0);
for(i = 0; i < cnt; ++i) {
float f = (float)i/cnt;
PROJECTILE(
.proto = pp_thickrice,
.pos = p->pos,
.color = c,
.rule = asymptotic,
.args = {
(1.0 + psin(M_PI*18*f)) * cexp(I*(2.0*M_PI*f+rot)),
2 + 2 * global.diff
},
);
}
PARTICLE(
.proto = pp_blast,
.pos = p->pos,
.color = c,
.timeout = 35 - 5 * frand(),
.draw_rule = GrowFade,
.args = { 0, 1 + 0.5 * frand() },
.angle = M_PI * 2 * frand(),
);
// FIXME: better sound
play_sound("enemydeath");
play_sound("shot1");
play_sound("shot3");
}
return ACTION_DESTROY;
}
Laser *laser = (Laser*)REF(p->args[0]);
if(!laser)
return ACTION_DESTROY;
p->pos = laser->prule(laser, time);
return 1;
return slave;
}
int wriggle_spell_slave(Enemy *e, int time) {
TIMER(&time)
void stage3_despawn_wriggle_slave(WriggleSlave *slave) {
coevent_signal_once(&slave->events.despawned);
}
float angle = e->args[2] * (time / 70.0 + e->args[1]);
cmplx dir = cexp(I*angle);
Boss *boss = (Boss*)REF(e->args[0]);
DEFINE_EXTERN_TASK(wriggle_slave_damage_trail) {
WriggleSlave *slave = TASK_BIND(ARGS.slave);
ShaderProgram *pshader = res_shader("sprite_default");
if(!boss)
return ACTION_DESTROY;
AT(EVENT_BIRTH) {
e->ent.draw_layer = LAYER_BULLET - 1;
}
AT(EVENT_DEATH) {
free_ref(e->args[0]);
return 1;
}
GO_TO(e, boss->pos + 100 * sin(time / 100.0) * dir, 0.03)
if(!(time % 2)) {
float c = 0.5 * psin(time / 25.0);
for(;;WAIT(2)) {
float t = (global.frames - slave->spawn_time) / 25.0;
float c = 0.5f * psinf(t);
PROJECTILE(
// FIXME: add prototype, or shove it into the basic ones somehow,
// or just replace this with some thing else
.sprite_ptr = get_sprite("part/smoothdot"),
// XXX: Do we want this to be a special snowflake without a ProjPrototype?
.sprite_ptr = slave->sprites.particle,
.size = 16 + 16*I,
.collision_size = 7.2 + 7.2*I,
.pos = e->pos,
.pos = slave->pos,
.color = RGBA(1.0 - c, 0.5, 0.5 + c, 0),
.draw_rule = wriggle_slave_part_draw,
.draw_rule = pdraw_timeout_fade(1, 0),
.timeout = 60,
.shader = "sprite_default",
.shader_ptr = pshader,
.flags = PFLAG_NOCLEAR | PFLAG_NOCLEAREFFECT | PFLAG_NOCOLLISIONEFFECT | PFLAG_NOSPAWNEFFECTS,
);
}
// moonlight rocket rockets
int rocket_period = (160 + 20 * (D_Lunatic - global.diff));
if(!creal(e->args[3]) && !((time + rocket_period/2) % rocket_period)) {
Laser *l;
float dt = 60;
l = create_lasercurve4c(e->pos, dt, dt, RGBA(1.0, 1.0, 0.5, 0.0), las_sine_expanding, 2.5*dir, M_PI/20, 0.2, 0);
PROJECTILE(
.proto = pp_ball,
.pos = e->pos,
.color = RGB(1.0, 0.4, 0.6),
.rule = wriggle_rocket_laserbullet,
.args = {
add_ref(l), dt-1, 1
}
);
l = create_lasercurve4c(e->pos, dt, dt, RGBA(0.5, 1.0, 0.5, 0.0), las_sine_expanding, 2.5*dir, M_PI/20, 0.2, M_PI);
PROJECTILE(
.proto = pp_ball,
.pos = e->pos,
.color = RGB(1.0, 0.4, 0.6),
.rule = wriggle_rocket_laserbullet,
.args = {
add_ref(l), dt-1, 1
},
);
play_sound("laser1");
}
// night ignite balls
if(creal(e->args[3]) && global.diff > D_Easy) {
FROM_TO(300, 1000000, 180) {
int cnt = 5, i;
for(i = 0; i < cnt; ++i) {
PROJECTILE(
.proto = pp_ball,
.pos = e->pos,
.color = RGBA(0.5, 1.0, 0.5, 0),
.rule = accelerated,
.args = {
0, 0.02 * cexp(I*i*2*M_PI/cnt)
},
);
if(global.diff > D_Hard) {
PROJECTILE(
.proto = pp_ball,
.pos = e->pos,
.color = RGBA(1.0, 1.0, 0.5, 0),
.rule = accelerated,
.args = {
0, 0.01 * cexp(I*i*2*M_PI/cnt)
},
);
}
}
// FIXME: better sound
play_sound("shot_special1");
}
}
return 1;
}
DEFINE_EXTERN_TASK(wriggle_slave_follow) {
WriggleSlave *slave = TASK_BIND(ARGS.slave);
Boss *boss = NOT_NULL(ENT_UNBOX(ARGS.boss));
MoveParams move = move_towards(0, 0.03);
cmplx dir = cdir(ARGS.rot_initial);
cmplx r = cdir(ARGS.rot_speed);
for(;(boss = ENT_UNBOX(ARGS.boss)); YIELD) {
real t = global.frames - slave->spawn_time;
move.attraction_point = boss->pos + 100 * sin(t / 100) * dir;
move_update(&slave->pos, &move);
if(ARGS.out_dir) {
*ARGS.out_dir = dir;
}
dir *= r;
}
}

View file

@ -11,8 +11,33 @@
#include "boss.h"
DEFINE_ENTITY_TYPE(WriggleSlave, {
struct {
Sprite *circle, *particle;
} sprites;
cmplx pos;
int spawn_time;
Color color;
cmplxf scale;
COEVENTS_ARRAY(
despawned
) events;
});
Boss *stage3_spawn_wriggle(cmplx pos);
void stage3_draw_wriggle_spellbg(Boss *boss, int time);
void wriggle_slave_visual(Enemy *e, int time, bool render);
int wriggle_spell_slave(Enemy *e, int time);
void stage3_init_wriggle_slave(WriggleSlave *slave, cmplx pos);
WriggleSlave *stage3_host_wriggle_slave(cmplx pos);
void stage3_despawn_wriggle_slave(WriggleSlave *slave);
DECLARE_EXTERN_TASK(wriggle_slave_damage_trail, { BoxedWriggleSlave slave; });
DECLARE_EXTERN_TASK(wriggle_slave_follow, {
BoxedWriggleSlave slave;
BoxedBoss boss;
real rot_speed;
real rot_initial;
cmplx *out_dir;
});