1 /*
2  * This software is licensed under the terms of the MIT License.
3  * See COPYING for further information.
4  * ---
5  * Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
6  * Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
7  */
8 
9 #include "taisei.h"
10 
11 #include "stage5_events.h"
12 #include "stage5.h"
13 #include "global.h"
14 
stage5_dialog_post_midboss(void)15 static Dialog *stage5_dialog_post_midboss(void) {
16 	PlayerMode *pm = global.plr.mode;
17 	Dialog *d = dialog_create();
18 	dialog_set_image(d, DIALOG_LEFT, pm->character->dialog_base_sprite_name);
19 	pm->dialog->stage5_post_midboss(d);
20 	assert(d->count == 1);
21 	d->actions->timeout = global.frames + 120;
22 	return d;
23 }
24 
stage5_dialog_pre_boss(void)25 static Dialog *stage5_dialog_pre_boss(void) {
26 	PlayerMode *pm = global.plr.mode;
27 	Dialog *d = dialog_create();
28 	dialog_set_image(d, DIALOG_LEFT, pm->character->dialog_base_sprite_name);
29 	dialog_set_image(d, DIALOG_RIGHT, "dialog/iku");
30 	pm->dialog->stage5_pre_boss(d);
31 	dialog_add_action(d, DIALOG_SET_BGM, "stage5boss");
32 	return d;
33 }
34 
stage5_dialog_post_boss(void)35 static Dialog *stage5_dialog_post_boss(void) {
36 	PlayerMode *pm = global.plr.mode;
37 	Dialog *d = dialog_create();
38 	dialog_set_image(d, DIALOG_LEFT, pm->character->dialog_base_sprite_name);
39 	dialog_set_image(d, DIALOG_RIGHT, "dialog/iku");
40 	pm->dialog->stage5_post_boss(d);
41 	return d;
42 }
43 
stage5_greeter(Enemy * e,int t)44 static int stage5_greeter(Enemy *e, int t) {
45 	TIMER(&t)
46 	AT(EVENT_KILLED) {
47 		spawn_items(e->pos, ITEM_POINTS, 2, ITEM_POWER, 2);
48 		return 1;
49 	}
50 
51 	e->moving = true;
52 	e->dir = creal(e->args[0]) < 0;
53 
54 	FROM_TO(0, 50, 1) {
55 		GO_TO(e, e->pos0 + e->args[0] * 50, 0.05);
56 	}
57 
58 	if(t > 200)
59 		e->pos += e->args[0];
60 
61 	FROM_TO(80, 180, 20) {
62 		for(int i = -(int)global.diff; i <= (int)global.diff; i++) {
63 			PROJECTILE(
64 				.proto = pp_bullet,
65 				.pos = e->pos,
66 				.color = RGB(0.0, 0.0, 1.0),
67 				.rule = asymptotic,
68 				.args = {
69 					(3.5+(global.diff == D_Lunatic))*cexp(I*carg(global.plr.pos-e->pos) + 0.06*I*i),
70 					5
71 				}
72 			);
73 		}
74 
75 		play_sound("shot1");
76 	}
77 
78 	return 1;
79 }
80 
stage5_lightburst(Enemy * e,int t)81 static int stage5_lightburst(Enemy *e, int t) {
82 	TIMER(&t);
83 	AT(EVENT_KILLED) {
84 		spawn_items(e->pos, ITEM_POINTS, 4, ITEM_POWER, 2);
85 		return 1;
86 	}
87 
88 	FROM_TO(0, 70, 1) {
89 		GO_TO(e, e->pos0 + e->args[0] * 70, 0.05);
90 	}
91 
92 	if(t > 200)
93 		e->pos += e->args[0];
94 
95 	FROM_TO_SND("shot1_loop", 20, 300, 5) {
96 		int c = 5+global.diff;
97 		for(int i = 0; i < c; i++) {
98 			cmplx n = cexp(I*carg(global.plr.pos) + 2.0*I*M_PI/c*i);
99 			PROJECTILE(
100 				.proto = pp_ball,
101 				.pos = e->pos + 50*n*cexp(-0.4*I*_i*global.diff),
102 				.color = RGB(0.3, 0, 0.7),
103 				.rule = asymptotic,
104 				.args = { 3*n, 3 }
105 			);
106 		}
107 
108 		play_sound("shot2");
109 	}
110 
111 	return 1;
112 }
113 
stage5_swirl(Enemy * e,int t)114 static int stage5_swirl(Enemy *e, int t) {
115 	TIMER(&t);
116 	AT(EVENT_KILLED) {
117 		spawn_items(e->pos, ITEM_POINTS, 1);
118 		return 1;
119 	}
120 
121 	if(t > creal(e->args[1]) && t < cimag(e->args[1]))
122 		e->args[0] *= e->args[2];
123 
124 	e->pos += e->args[0];
125 
126 	FROM_TO(0, 400, 26-global.diff*4) {
127 		for(int i = 1; i >= -1; i -= 2) {
128 			PROJECTILE(
129 				.proto = pp_bullet,
130 				.pos = e->pos,
131 				.color = RGB(0.3, 0.4, 0.5),
132 				.rule = asymptotic,
133 				.args = { i*2*e->args[0]*I/cabs(e->args[0]), 3 }
134 			);
135 		}
136 
137 		play_sound("shot1");
138 	}
139 
140 	return 1;
141 }
142 
stage5_limiter(Enemy * e,int t)143 static int stage5_limiter(Enemy *e, int t) {
144 	TIMER(&t);
145 	AT(EVENT_KILLED) {
146 		spawn_items(e->pos, ITEM_POINTS, 4, ITEM_POWER, 4);
147 		return 1;
148 	}
149 
150 	e->pos += e->args[0];
151 
152 	FROM_TO_SND("shot1_loop", 0, 1200, 3) {
153 		uint f = PFLAG_NOSPAWNFADE;
154 		double base_angle = carg(global.plr.pos - e->pos);
155 
156 		for(int i = 1; i >= -1; i -= 2) {
157 			double a = i * 0.2 - 0.1 * (global.diff / 4) + i * 3.0 / (_i + 1);
158 			cmplx aim = cexp(I * (base_angle + a));
159 
160 			PROJECTILE(
161 				.proto = pp_rice,
162 				.pos = e->pos,
163 				.color = RGB(0.5,0.1,0.2),
164 				.rule = asymptotic,
165 				.args = { 10*aim, 2 },
166 				.flags = f,
167 			);
168 		}
169 	}
170 
171 	return 1;
172 }
173 
stage5_laserfairy(Enemy * e,int t)174 static int stage5_laserfairy(Enemy *e, int t) {
175 	TIMER(&t)
176 	AT(EVENT_KILLED) {
177 		spawn_items(e->pos, ITEM_POINTS, 5, ITEM_POWER, 5);
178 		return 1;
179 	}
180 
181 	FROM_TO(0, 100, 1) {
182 		GO_TO(e, e->pos0 + e->args[0] * 100, 0.05);
183 	}
184 
185 	if(t > 700)
186 		e->pos -= e->args[0];
187 
188 	FROM_TO(100, 700, (7-global.diff)*(1+(int)creal(e->args[1]))) {
189 		cmplx n = cexp(I*carg(global.plr.pos-e->pos)+(0.2-0.02*global.diff)*I*_i);
190 		float fac = (0.5+0.2*global.diff);
191 		create_lasercurve2c(e->pos, 100, 300, RGBA(0.7, 0.3, 1, 0), las_accel, fac*4*n, fac*0.05*n);
192 		PROJECTILE(
193 			.proto = pp_plainball,
194 			.pos = e->pos,
195 			.color = RGBA(0.7, 0.3, 1, 0),
196 			.rule = accelerated,
197 			.args = { fac*4*n, fac*0.05*n }
198 		);
199 		play_sound_ex("shot_special1", 0, true);
200 	}
201 
202 	return 1;
203 }
204 
stage5_miner(Enemy * e,int t)205 static int stage5_miner(Enemy *e, int t) {
206 	TIMER(&t);
207 	AT(EVENT_KILLED) {
208 		spawn_items(e->pos, ITEM_POINTS, 2);
209 		return 1;
210 	}
211 
212 	e->pos += e->args[0];
213 
214 	FROM_TO(0, 600, 5-global.diff/2) {
215 		tsrand_fill(2);
216 		PROJECTILE(
217 			.proto = pp_rice,
218 			.pos = e->pos + 20*cexp(2.0*I*M_PI*afrand(0)),
219 			.color = RGB(0,0,cabs(e->args[0])),
220 			.rule = linear,
221 			.args = { cexp(2.0*I*M_PI*afrand(1)) }
222 		);
223 		play_sound_ex("shot3", 0, false);
224 	}
225 
226 	return 1;
227 }
228 
lightning_particle(cmplx pos,int t)229 static void lightning_particle(cmplx pos, int t) {
230 	if(!(t % 5)) {
231 		char *part = frand() > 0.5 ? "lightning0" : "lightning1";
232 		PARTICLE(
233 			.sprite = part,
234 			.pos = pos,
235 			.color = RGBA(1.0, 1.0, 1.0, 0.0),
236 			.timeout = 20,
237 			.draw_rule = Fade,
238 			.flags = PFLAG_REQUIREDPARTICLE,
239 			.angle = frand()*2*M_PI,
240 		);
241 	}
242 }
243 
stage5_magnetto(Enemy * e,int t)244 static int stage5_magnetto(Enemy *e, int t) {
245 	TIMER(&t);
246 
247 	AT(EVENT_KILLED) {
248 		spawn_items(e->pos, ITEM_POINTS, 5, ITEM_POWER, 5);
249 		return 1;
250 	}
251 
252 	if(t < 0) {
253 		return 1;
254 	}
255 
256 	cmplx offset = (frand()-0.5)*10 + (frand()-0.5)*10.0*I;
257 	lightning_particle(e->pos + 3*offset, t);
258 
259 	FROM_TO(0, 70, 1) {
260 		GO_TO(e, e->args[0], 0.1);
261 	}
262 
263 	AT(140) {
264 		play_sound("redirect");
265 		play_sound_delayed("redirect", 0, false, 180);
266 	}
267 
268 	FROM_TO(140, 320, 1) {
269 		e->pos += 3 * cexp(I*(carg(e->args[1] - e->pos) + M_PI/2));
270 		GO_TO(e, e->args[1], pow((t - 140) / 300.0, 3));
271 	}
272 
273 	FROM_TO_SND("shot1_loop", 140, 280, 1 + 2 * (1 + D_Lunatic - global.diff)) {
274 		// cmplx dir = cexp(I*carg(global.plr.pos - e->pos));
275 
276 		for(int i = 0; i < 2 - (global.diff == D_Easy); ++i) {
277 			cmplx dir = cexp(I*(M_PI*i + M_PI/8*sin(2*(t-140)/70.0 * M_PI) + carg(e->args[1] - e->pos)));
278 
279 			PROJECTILE(
280 				.proto = pp_ball,
281 				.pos = e->pos,
282 				.color = RGBA(0.1 + 0.5 * pow((t - 140) / 140.0, 2), 0.0, 0.8, 0.0),
283 				.rule = accelerated,
284 				.args = {
285 					(-2 + (global.diff == D_Hard)) * dir,
286 					0.02 * dir * (!i || global.diff != D_Lunatic),
287 				},
288 			);
289 		}
290 	}
291 
292 	AT(380) {
293 		return ACTION_DESTROY;
294 	}
295 
296 	return 1;
297 }
298 
stage5_explosion(Enemy * e,int t)299 static int stage5_explosion(Enemy *e, int t) {
300 	TIMER(&t)
301 	AT(EVENT_KILLED) {
302 		spawn_items(e->pos,
303 			ITEM_POINTS, 5,
304 			ITEM_POWER, 5,
305 			ITEM_LIFE, creal(e->args[1])
306 		);
307 
308 		PARTICLE(
309 			.sprite = "blast_huge_rays",
310 			.color = color_add(RGBA(0, 0.2 + 0.5 * frand(), 0.5 + 0.5 * frand(), 0.0), RGBA(1, 1, 1, 0)),
311 			.pos = e->pos,
312 			.timeout = 60 + 10 * frand(),
313 			.draw_rule = ScaleFade,
314 			.args = { 0, 0, (0 + 3*I) * (1 + 0.2 * frand()) },
315 			.angle = frand() * 2 * M_PI,
316 			.layer = LAYER_PARTICLE_HIGH | 0x42,
317 			.flags = PFLAG_REQUIREDPARTICLE,
318 		);
319 
320 		PARTICLE(
321 			.sprite = "blast_huge_halo",
322 			.pos = e->pos,
323 			.color = RGBA(0.3 * frand(), 0.3 * frand(), 1.0, 0),
324 			.timeout = 200 + 24 * frand(),
325 			.draw_rule = ScaleFade,
326 			.args = { 0, 0, (0.25 + 2.5*I) * (1 + 0.2 * frand()) },
327 			.layer = LAYER_PARTICLE_HIGH | 0x41,
328 			.angle = frand() * 2 * M_PI,
329 			.flags = PFLAG_REQUIREDPARTICLE,
330 		);
331 
332 		play_sound("boom");
333 		return 1;
334 	}
335 
336 	FROM_TO(0, 80, 1) {
337 		GO_TO(e, e->pos0 + e->args[0] * 80, 0.05);
338 	}
339 
340 	FROM_TO(90, 300, 7-global.diff) {
341 		PROJECTILE(
342 			.proto = pp_soul,
343 			.pos = e->pos,
344 			.color = RGBA(0, 0, 1, 0),
345 			.rule = asymptotic,
346 			.args = { 4*cexp(0.5*I*_i), 3 }
347 		);
348 		play_sound("shot_special1");
349 	}
350 
351 	FROM_TO(200, 720, 6-global.diff) {
352 		for(int i = 1; i >= -1; i -= 2) {
353 			PROJECTILE(
354 				.proto = pp_rice,
355 				.pos = e->pos,
356 				.color = RGB(1,0,0),
357 				.rule = asymptotic,
358 				.args = { i*2*cexp(-0.3*I*_i+frand()*I), 3 }
359 			);
360 		}
361 
362 		play_sound("shot3");
363 	}
364 
365 	FROM_TO(500-30*(global.diff-D_Easy), 800, 100-10*global.diff) {
366 		create_laserline(e->pos, 10*cexp(I*carg(global.plr.pos-e->pos)+0.04*I*(1-2*frand())), 60, 120, RGBA(1, 0.3, 1, 0));
367 		play_sound_delayed("laser1", 0, true, 45);
368 	}
369 
370 	return 1;
371 }
372 
iku_slave_visual(Enemy * e,int t,bool render)373 static void iku_slave_visual(Enemy *e, int t, bool render) {
374 	if(render) {
375 		return;
376 	}
377 
378 	cmplx offset = (frand()-0.5)*10 + (frand()-0.5)*10.0*I;
379 
380 	if(e->args[2] && !(t % 5)) {
381 		lightning_particle(e->pos + 3*offset, t);
382 	}
383 
384 	if(!(t % 3)) {
385 		float alpha = 1;
386 
387 		if(!e->args[2]) {
388 			alpha *= 0.03;
389 		}
390 
391 		Color *clr = RGBA_MUL_ALPHA(0.1*alpha, 0.1*alpha, 0.6*alpha, 0.5*alpha);
392 		clr->a = 0;
393 
394 		PARTICLE(
395 			.sprite = "lightningball",
396 			.pos = 0,
397 			.color = clr,
398 			.draw_rule = Fade,
399 			.rule = enemy_flare,
400 			.timeout = 50,
401 			.args = { offset*0.1, add_ref(e) },
402 			.flags = PFLAG_REQUIREDPARTICLE,
403 		);
404 	}
405 }
406 
iku_mid_intro(Boss * b,int t)407 static void iku_mid_intro(Boss *b, int t) {
408 	TIMER(&t);
409 
410 	b->pos += -1-7.0*I+10*t*(cimag(b->pos)<-200);
411 
412 	FROM_TO(90, 110, 10) {
413 		create_enemy3c(b->pos, ENEMY_IMMUNE, iku_slave_visual, stage5_explosion, -2-0.5*_i+I*_i, _i == 1,1);
414 	}
415 
416 	AT(960)
417 		enemy_kill_all(&global.enemies);
418 }
419 
midboss_dummy(Boss * b,int t)420 static void midboss_dummy(Boss *b, int t) { }
421 
create_iku_mid(void)422 static Boss *create_iku_mid(void) {
423 	Boss *b = create_boss("Bombs?", "iku_mid", 0, VIEWPORT_W+800.0*I);
424 	b->glowcolor = *RGB(0.2, 0.4, 0.5);
425 	b->shadowcolor = *RGBA_MUL_ALPHA(0.65, 0.2, 0.75, 0.5);
426 
427 	Attack *a = boss_add_attack(b, AT_SurvivalSpell, "Discharge Bombs", 16, 10, iku_mid_intro, NULL);
428 	boss_set_attack_bonus(a, 5);
429 
430 	// suppress the boss death effects (this triggers the "boss fleeing" case)
431 	boss_add_attack(b, AT_Move, "", 0, 0, midboss_dummy, NULL);
432 
433 	return b;
434 }
435 
stage5_lightburst2(Enemy * e,int t)436 static int stage5_lightburst2(Enemy *e, int t) {
437 	TIMER(&t);
438 	AT(EVENT_KILLED) {
439 		spawn_items(e->pos, ITEM_POINTS, 4, ITEM_POWER, 4);
440 		return 1;
441 	}
442 
443 	FROM_TO(0, 70, 1) {
444 		GO_TO(e, e->pos0 + e->args[0] * 70, 0.05);
445 	}
446 
447 	if(t > 200)
448 		e->pos += e->args[0];
449 
450 	FROM_TO_SND("shot1_loop", 20, 170, 5) {
451 		int i;
452 		int c = 4+global.diff-(global.diff==D_Easy);
453 		for(i = 0; i < c; i++) {
454 			tsrand_fill(2);
455 			cmplx n = cexp(I*carg(global.plr.pos-e->pos) + 2.0*I*M_PI/c*i);
456 			PROJECTILE(
457 				.proto = pp_bigball,
458 				.pos = e->pos + 50*n*cexp(-1.0*I*_i*global.diff),
459 				.color = RGB(0.3, 0, 0.7+0.3*(_i&1)),
460 				.rule = asymptotic,
461 				.args = {
462 					2.5*n+0.25*global.diff*afrand(0)*cexp(2.0*I*M_PI*afrand(1)),
463 					3
464 				}
465 			);
466 		}
467 
468 		play_sound("shot2");
469 	}
470 
471 	return 1;
472 }
473 
stage5_superbullet(Enemy * e,int t)474 static int stage5_superbullet(Enemy *e, int t) {
475 	TIMER(&t);
476 	AT(EVENT_KILLED) {
477 		spawn_items(e->pos, ITEM_POINTS, 4, ITEM_POWER, 3);
478 		return 1;
479 	}
480 
481 	FROM_TO(0, 70, 1) {
482 		GO_TO(e, e->pos0 + e->args[0] * 70, 0.05);
483 	}
484 
485 	FROM_TO(60, 200, 1) {
486 		cmplx n = cexp(I*M_PI*sin(_i/(8.0+global.diff)+frand()*0.1)+I*carg(global.plr.pos-e->pos));
487 		PROJECTILE(
488 			.proto = pp_bullet,
489 			.pos = e->pos + 50*n,
490 			.color = RGB(0.6, 0, 0),
491 			.rule = asymptotic,
492 			.args = { 2*n, 10 }
493 		);
494 		play_sound("shot1");
495 	}
496 
497 	FROM_TO(260, 400, 1)
498 		e->pos -= e->args[0];
499 	return 1;
500 }
501 
iku_intro(Boss * b,int t)502 static void iku_intro(Boss *b, int t) {
503 	GO_TO(b, VIEWPORT_W/2+240.0*I, 0.015);
504 
505 	if(t == 160)
506 		global.dialog = stage5_dialog_pre_boss();
507 }
508 
cloud_common(void)509 static void cloud_common(void) {
510 	tsrand_fill(4);
511 	float v = (afrand(2)+afrand(3))*0.5+1.0;
512 
513 	PROJECTILE(
514 		// FIXME: add prototype, or shove it into the basic ones somehow,
515 		// or just replace this with some thing else
516 		.sprite_ptr = get_sprite("part/lightningball"),
517 		.size = 48 * (1+I),
518 		.collision_size = 21.6 * (1+I),
519 
520 		.pos = VIEWPORT_W*afrand(0)-15.0*I,
521 		.color = RGBA_MUL_ALPHA(0.2, 0.0, 0.4, 0.6),
522 		.rule = accelerated,
523 		.args = {
524 			1-2*afrand(1)+v*I,
525 			-0.01*I
526 		},
527 		.shader = "sprite_default",
528 	);
529 }
530 
iku_bolts(Boss * b,int time)531 static void iku_bolts(Boss *b, int time) {
532 	int t = time % 400;
533 	TIMER(&t);
534 
535 	FROM_TO(0, 400, 2) {
536 		cloud_common();
537 	}
538 
539 	FROM_TO(60, 400, 50) {
540 		int i, c = 10+global.diff;
541 
542 		for(i = 0; i < c; i++) {
543 			PROJECTILE(
544 				.proto = pp_ball,
545 				.pos = b->pos,
546 				.color = RGBA(0.4, 1.0, 1.0, 0),
547 				.rule = asymptotic,
548 				.args = {
549 					(i+2)*0.4*cexp(I*carg(global.plr.pos-b->pos))+0.2*(global.diff-1)*frand(),
550 					3
551 				},
552 			);
553 		}
554 
555 		play_sound("shot2");
556 		play_sound("redirect");
557 	}
558 
559 	FROM_TO(0, 70, 1)
560 		GO_TO(b, 100+300.0*I, 0.02);
561 
562 	FROM_TO(100, 200, 1)
563 		GO_TO(b, VIEWPORT_W/2+100.0*I, 0.02);
564 
565 	FROM_TO(230, 300, 1)
566 		GO_TO(b, VIEWPORT_W-100+300.0*I, 0.02);
567 
568 	FROM_TO(330, 400, 1)
569 		GO_TO(b, VIEWPORT_W/2+100.0*I, 0.02);
570 
571 }
572 
iku_atmospheric(Boss * b,int time)573 void iku_atmospheric(Boss *b, int time) {
574 	if(time < 0) {
575 		GO_TO(b, VIEWPORT_W/2+200.0*I, 0.06);
576 		return;
577 	}
578 
579 	int t = time % 500;
580 	TIMER(&t);
581 
582 	GO_TO(b,VIEWPORT_W/2+tanh(sin(time/100))*(200-100*(global.diff==D_Easy))+I*VIEWPORT_H/3+I*(cos(t/200)-1)*50,0.03);
583 
584 	FROM_TO(0, 500, 23-2*global.diff) {
585 		tsrand_fill(4);
586 		cmplx p1 = VIEWPORT_W*afrand(0) + VIEWPORT_H/2*I*afrand(1);
587 		cmplx p2 = p1 + (120+20*global.diff)*cexp(0.5*I-afrand(2)*I)*(1-2*(afrand(3) > 0.5));
588 
589 		int i;
590 		int c = 6+global.diff;
591 
592 		for(i = -c*0.5; i <= c*0.5; i++) {
593 			PROJECTILE(
594 				.proto = pp_ball,
595 				.pos = p1+(p2-p1)/c*i,
596 				.color = RGBA(1-1/(1+fabs(0.1*i)), 0.5-0.1*abs(i), 1, 0),
597 				.rule = accelerated,
598 				.args = {
599 					0, (0.004+0.001*global.diff)*cexp(I*carg(p2-p1)+I*M_PI/2+0.2*I*i)
600 				},
601 			);
602 		}
603 
604 		play_sound("shot_special1");
605 		play_sound("redirect");
606 	}
607 
608 	FROM_TO(0, 500, 7-global.diff) {
609 		if(global.diff >= D_Hard) {
610 			PROJECTILE(
611 				.proto = pp_thickrice,
612 				.pos = VIEWPORT_W*frand(),
613 				.color = RGB(0,0.3,0.7),
614 				.rule = accelerated,
615 				.args = { 0, 0.01*I }
616 			);
617 		}
618 		else {
619 			PROJECTILE(
620 				.proto = pp_rice,
621 				.pos = VIEWPORT_W*frand(),
622 				.color = RGB(0,0.3,0.7),
623 				.rule = linear,
624 				.args = { 2*I }
625 			);
626 		}
627 	}
628 }
629 
bolts2_laser(Laser * l,float t)630 static cmplx bolts2_laser(Laser *l, float t) {
631 	if(t == EVENT_BIRTH) {
632 		l->shader = r_shader_get_optional("lasers/iku_lightning");
633 		return 0;
634 	}
635 
636 	double diff = creal(l->args[2]);
637 	return creal(l->args[0])+I*cimag(l->pos) + sign(cimag(l->args[0]-l->pos))*0.06*I*t*t + (20+4*diff)*sin(t*0.025*diff+creal(l->args[0]))*l->args[1];
638 }
639 
iku_bolts2(Boss * b,int time)640 static void iku_bolts2(Boss *b, int time) {
641 	int t = time % 400;
642 	TIMER(&t);
643 
644 	// FIXME: ANOTHER one of these... get rid of this hack when attacks have proper state
645 	static bool flip_laser;
646 
647 	if(time == EVENT_BIRTH) {
648 		flip_laser = true;
649 	}
650 
651 	FROM_TO(0, 400, 2) {
652 		cloud_common();
653 	}
654 
655 	FROM_TO(0, 400, 60) {
656 		flip_laser = !flip_laser;
657 		aniplayer_queue(&b->ani, flip_laser ? "dashdown_left" : "dashdown_right", 1);
658 		aniplayer_queue(&b->ani, "main", 0);
659 		create_lasercurve3c(creal(global.plr.pos), 100, 200, RGBA(0.3, 1, 1, 0), bolts2_laser, global.plr.pos, flip_laser*2-1, global.diff);
660 		play_sound_ex("laser1", 0, false);
661 	}
662 
663 	FROM_TO_SND("shot1_loop", 0, 400, 5-global.diff)
664 		if(frand() < 0.9) {
665 			PROJECTILE(
666 				.proto = pp_plainball,
667 				.pos = b->pos,
668 				.color = RGB(0.2,0,0.8),
669 				.rule = linear,
670 				.args = { cexp(0.1*I*_i) }
671 			);
672 		}
673 
674 	FROM_TO(0, 70, 1)
675 		GO_TO(b, 100+200.0*I, 0.02);
676 
677 	FROM_TO(230, 300, 1)
678 		GO_TO(b, VIEWPORT_W-100+200.0*I, 0.02);
679 
680 }
681 
lightning_slave(Enemy * e,int t)682 static int lightning_slave(Enemy *e, int t) {
683 	if(t < 0)
684 		return 1;
685 	if(t > 200)
686 		return ACTION_DESTROY;
687 
688 	TIMER(&t);
689 
690 	e->pos += e->args[0];
691 
692 	FROM_TO(0,200,20)
693 		e->args[0] *= cexp(I * (0.25 + 0.25 * frand() * M_PI));
694 
695 	FROM_TO(0, 200, 3)
696 		if(cabs(e->pos-global.plr.pos) > 60) {
697 			Color *clr = RGBA(1-1/(1+0.01*_i), 0.5-0.01*_i, 1, 0);
698 
699 			Projectile *p = PROJECTILE(
700 				.proto = pp_wave,
701 				.pos = e->pos,
702 				.color = clr,
703 				.rule = asymptotic,
704 				.args = {
705 					0.75*e->args[0]/cabs(e->args[0])*I,
706 					10
707 				},
708 			);
709 
710 			if(projectile_in_viewport(p)) {
711 				for(int i = 0; i < 3; ++i) {
712 					tsrand_fill(2);
713 					lightning_particle(p->pos + 5 * afrand(0) * cexp(I*M_PI*2*afrand(1)), 0);
714 				}
715 
716 				play_sound_ex("shot3", 0, false);
717 				// play_sound_ex("redirect", 0, true);
718 			}
719 		}
720 
721 	return 1;
722 }
723 
zigzag_bullet(Projectile * p,int t)724 static int zigzag_bullet(Projectile *p, int t) {
725 	if(t < 0) {
726 		return ACTION_ACK;
727 	}
728 
729 	int l = 50;
730 	p->pos = p->pos0+(abs(((2*t)%l)-l/2)*I+t)*2*p->args[0];
731 
732 	if(t%2 == 0) {
733 		PARTICLE(
734 			.sprite = "lightningball",
735 			.pos = p->pos,
736 			.color = RGBA(0.1, 0.1, 0.6, 0.0),
737 			.timeout = 15,
738 			.draw_rule = Fade,
739 		);
740 	}
741 
742 	return ACTION_NONE;
743 }
744 
iku_lightning(Boss * b,int time)745 void iku_lightning(Boss *b, int time) {
746 	int t = time % 141;
747 
748 	if(time == EVENT_DEATH) {
749 		enemy_kill_all(&global.enemies);
750 		return;
751 	}
752 
753 	if(time < 0) {
754 		GO_TO(b, BOSS_DEFAULT_GO_POS, 0.03);
755 		return;
756 	}
757 
758 	TIMER(&t);
759 
760 	GO_TO(b,VIEWPORT_W/2+tanh(sin(time/100))*200+I*VIEWPORT_H/3+I*(cos(t/200)-1)*50,0.03);
761 
762 	AT(0) {
763 		play_sound("charge_generic");
764 	}
765 
766 	FROM_TO(0, 60, 1) {
767 		cmplx n = cexp(2.0*I*M_PI*frand());
768 		float l = 150*frand()+50;
769 		float s = 4+_i*0.01;
770 		float alpha = 0.5;
771 
772 		PARTICLE(
773 			.sprite = "lightningball",
774 			.pos = b->pos+l*n,
775 			.color = RGBA(0.1*alpha, 0.1*alpha, 0.6*alpha, 0),
776 			.draw_rule = Fade,
777 			.rule = linear,
778 			.timeout = l/s,
779 			.args = { -s*n },
780 		);
781 	}
782 
783 	if(global.diff >= D_Hard && time > 0 && !(time%100)) {
784 		int c = 7 + 2 * (global.diff == D_Lunatic);
785 		for(int i = 0; i<c; i++) {
786 			PROJECTILE(
787 				.proto = pp_bigball,
788 				.pos = b->pos,
789 				.color = RGBA(0.5, 0.1, 1.0, 0.0),
790 				.rule = zigzag_bullet,
791 				.args = { cexp(2*M_PI*I/c*i+I*carg(global.plr.pos-b->pos)) },
792 			);
793 		}
794 
795 		play_sound("redirect");
796 		play_sound("shot_special1");
797 	}
798 
799 	AT(100) {
800 		aniplayer_hard_switch(&b->ani, ((time/141)&1) ? "dashdown_left" : "dashdown_right",1);
801 		aniplayer_queue(&b->ani, "main", 0);
802 		int c = 40;
803 		int l = 200;
804 		int s = 10;
805 
806 		for(int i=0; i < c; i++) {
807 			cmplx n = cexp(2.0*I*M_PI*frand());
808 			PARTICLE(
809 				.sprite = "smoke",
810 				.pos = b->pos,
811 				.color = RGBA(0.4, 0.4, 1.0, 0.0),
812 				.draw_rule = Fade,
813 				.rule = linear,
814 				.timeout = l/s,
815 				.args = { s*n },
816 			);
817 		}
818 
819 		for(int i = 0; i < global.diff+1; i++){
820 			create_enemy1c(b->pos, ENEMY_IMMUNE, NULL, lightning_slave, 10*cexp(I*carg(global.plr.pos - b->pos)+2.0*I*M_PI/(global.diff+1)*i));
821 		}
822 
823 		play_sound("shot_special1");
824 	}
825 }
826 
iku_bolts3(Boss * b,int time)827 static void iku_bolts3(Boss *b, int time) {
828 	int t = time % 400;
829 	TIMER(&t);
830 
831 	FROM_TO(0, 400, 2) {
832 		cloud_common();
833 	}
834 
835 	FROM_TO(60, 400, 60) {
836 		aniplayer_queue(&b->ani, (_i&1) ? "dashdown_left" : "dashdown_right",1);
837 		aniplayer_queue(&b->ani, "main", 0);
838 		int i, c = 10+global.diff;
839 		cmplx n = cexp(I*carg(global.plr.pos-b->pos)+0.1*I-0.2*I*frand());
840 		for(i = 0; i < c; i++) {
841 			PROJECTILE(
842 				.proto = pp_ball,
843 				.pos = b->pos,
844 				.color = RGBA(0.4, 1.0, 1.0, 0.0),
845 				.rule = asymptotic,
846 				.args = {
847 					(i+2)*0.4*n+0.2*(global.diff-1)*frand(),
848 					3
849 				},
850 			);
851 		}
852 
853 		play_sound("shot2");
854 		play_sound("redirect");
855 	}
856 
857 	FROM_TO_SND("shot1_loop", 0, 400, 5-global.diff)
858 		if(frand() < 0.9) {
859 			PROJECTILE(
860 				.proto = pp_plainball,
861 				.pos = b->pos,
862 				.color = RGB(0.2,0,0.8),
863 				.rule = linear,
864 				.args = { cexp(0.1*I*_i) }
865 			);
866 		}
867 
868 	FROM_TO(0, 70, 1)
869 		GO_TO(b, 100+200.0*I, 0.02);
870 
871 	FROM_TO(230, 300, 1)
872 		GO_TO(b, VIEWPORT_W-100+200.0*I, 0.02);
873 
874 }
875 
induction_bullet_traj(Projectile * p,float t)876 static cmplx induction_bullet_traj(Projectile *p, float t) {
877 	return p->pos0 + p->args[0]*t*cexp(p->args[1]*t);
878 }
879 
induction_bullet(Projectile * p,int time)880 static int induction_bullet(Projectile *p, int time) {
881 	if(time < 0) {
882 		return ACTION_ACK;
883 	}
884 
885 	float t = time*sqrt(global.diff);
886 
887 	if(global.diff > D_Normal && !p->args[2]) {
888 		t = time*0.6;
889 		t = 230-t;
890 		if(t < 0)
891 			return ACTION_DESTROY;
892 	}
893 
894 	p->pos = induction_bullet_traj(p,t);
895 
896 	if(time == 0) {
897 		// don't lerp; the spawn position is very different on hard/lunatic and would cause false hits
898 		p->prevpos = p->pos;
899 	}
900 
901 	p->angle = carg(p->args[0]*cexp(p->args[1]*t)*(1+p->args[1]*t));
902 	return 1;
903 }
904 
cathode_laser(Laser * l,float t)905 static cmplx cathode_laser(Laser *l, float t) {
906 	if(t == EVENT_BIRTH) {
907 		l->shader = r_shader_get_optional("lasers/iku_cathode");
908 		return 0;
909 	}
910 
911 	l->args[1] = I*cimag(l->args[1]);
912 
913 	return l->pos + l->args[0]*t*cexp(l->args[1]*t);
914 }
915 
iku_cathode(Boss * b,int t)916 void iku_cathode(Boss *b, int t) {
917 	GO_TO(b, VIEWPORT_W/2+200.0*I, 0.02);
918 
919 	TIMER(&t)
920 
921 	FROM_TO(50, 18000, 70-global.diff*10) {
922 		aniplayer_hard_switch(&b->ani, (_i&1) ? "dashdown_left" : "dashdown_right",1);
923 		aniplayer_queue(&b->ani,"main",0);
924 
925 		int i;
926 		int c = 7+global.diff/2;
927 
928 		double speedmod = 1-0.3*(global.diff == D_Lunatic);
929 		for(i = 0; i < c; i++) {
930 			PROJECTILE(
931 				.proto = pp_bigball,
932 				.pos = b->pos,
933 				.color = RGBA(0.2, 0.4, 1.0, 0.0),
934 				.rule = induction_bullet,
935 				.args = {
936 					speedmod*2*cexp(2.0*I*M_PI*frand()),
937 					speedmod*0.01*I*(1-2*(_i&1)),
938 					1
939 				},
940 			);
941 			if(i < c*3/4)
942 				create_lasercurve2c(b->pos, 60, 200, RGBA(0.4, 1, 1, 0), cathode_laser, 2*cexp(2.0*I*M_PI*M_PI*frand()), 0.015*I*(1-2*(_i&1)));
943 		}
944 
945 		// XXX: better ideas?
946 		play_sound("shot_special1");
947 		play_sound("redirect");
948 		play_sound("shot3");
949 		play_sound("shot2");
950 	}
951 }
952 
iku_induction(Boss * b,int t)953 void iku_induction(Boss *b, int t) {
954 	// thwarf safespots
955 	cmplx ofs = global.diff > D_Normal ? 10*I : 0;
956 
957 	GO_TO(b, VIEWPORT_W/2+200.0*I + ofs, 0.03);
958 
959 	if(t < 0) {
960 		return;
961 	}
962 
963 	TIMER(&t);
964 	AT(0) {
965 		aniplayer_queue(&b->ani, "dashdown_wait", 0);
966 	}
967 
968 	FROM_TO_SND("shot1_loop", 0, 18000, 8) {
969 		play_sound("redirect");
970 
971 		int i,j;
972 		int c = 6;
973 		int c2 = 6-(global.diff/4);
974 		for(i = 0; i < c; i++) {
975 			for(j = 0; j < 2; j++) {
976 				Color *clr = RGBA(1-1/(1+0.1*(_i%c2)), 0.5-0.1*(_i%c2), 1.0, 0.0);
977 				float shift = 0.6*(_i/c2);
978 				float a = -0.0002*(global.diff-D_Easy);
979 				if(global.diff == D_Hard)
980 					a += 0.0005;
981 				PROJECTILE(
982 					.proto = pp_ball,
983 					.pos = b->pos,
984 					.color = clr,
985 					.rule = induction_bullet,
986 					.args = {
987 						2*cexp(2.0*I*M_PI/c*i+I*M_PI/2+I*shift),
988 						(0.01+0.001*global.diff)*I*(1-2*j)+a
989 					},
990 					.max_viewport_dist = 400*(global.diff>=D_Hard),
991 				);
992 			}
993 		}
994 
995 	}
996 }
997 
998 void iku_spell_bg(Boss *b, int t);
999 
iku_extra_find_next_slave(cmplx from,double playerbias)1000 static Enemy* iku_extra_find_next_slave(cmplx from, double playerbias) {
1001 	Enemy *nearest = NULL, *e;
1002 	double dist, mindist = INFINITY;
1003 
1004 	cmplx org = from + playerbias * cexp(I*(carg(global.plr.pos - from)));
1005 
1006 	for(e = global.enemies.first; e; e = e->next) {
1007 		if(e->args[2]) {
1008 			continue;
1009 		}
1010 
1011 		dist = cabs(e->pos - org);
1012 
1013 		if(dist < mindist) {
1014 			nearest = e;
1015 			mindist = dist;
1016 		}
1017 	}
1018 
1019 	return nearest;
1020 }
1021 
iku_extra_slave_visual(Enemy * e,int t,bool render)1022 static void iku_extra_slave_visual(Enemy *e, int t, bool render) {
1023 	iku_slave_visual(e, t, render);
1024 
1025 	if(render) {
1026 		return;
1027 	}
1028 
1029 	if(e->args[2] && !(t % 5)) {
1030 		cmplx offset = (frand()-0.5)*30 + (frand()-0.5)*20.0*I;
1031 		PARTICLE(
1032 			.sprite = "smoothdot",
1033 			.pos = offset,
1034 			.color = e->args[1] ? RGBA(1.0, 0.5, 0.0, 0.0) : RGBA(0.0, 0.5, 0.5, 0.0),
1035 			.draw_rule = Shrink,
1036 			.rule = enemy_flare,
1037 			.timeout = 50,
1038 			.args = {
1039 				(-50.0*I-offset)/50.0,
1040 				add_ref(e)
1041 			},
1042 		);
1043 	}
1044 }
1045 
iku_extra_trigger_bullet(Projectile * p,int t)1046 static int iku_extra_trigger_bullet(Projectile *p, int t) {
1047 	if(t == EVENT_DEATH) {
1048 		free_ref(p->args[1]);
1049 		return ACTION_ACK;
1050 	}
1051 
1052 	if(t < 0) {
1053 		return ACTION_ACK;
1054 	}
1055 
1056 	Enemy *target = REF(p->args[1]);
1057 
1058 	if(!target) {
1059 		return ACTION_DESTROY;
1060 	}
1061 
1062 	if(creal(p->args[2]) < 0) {
1063 		linear(p, t);
1064 		if(cabs(p->pos - target->pos) < 5) {
1065 			p->pos = target->pos;
1066 			target->args[1] = 1;
1067 			p->args[2] = 55 - 5 * global.diff;
1068 			target->args[3] = global.frames + p->args[2];
1069 			play_sound("shot_special1");
1070 		}
1071 	} else {
1072 		p->args[2] = approach(creal(p->args[2]), 0, 1);
1073 		play_loop("charge_generic");
1074 	}
1075 
1076 	if(creal(p->args[2]) == 0) {
1077 		int cnt = 6 + 2 * global.diff;
1078 		for(int i = 0; i < cnt; ++i) {
1079 			cmplx dir = cexp(I*(t + i*2*M_PI/cnt));
1080 
1081 			PROJECTILE(
1082 				.proto = pp_bigball,
1083 				.pos = p->pos,
1084 				.color = RGBA(1.0, 0.5, 0.0, 0.0),
1085 				.rule = asymptotic,
1086 				.args = { 1.1*dir, 5 },
1087 			);
1088 
1089 			PROJECTILE(
1090 				.proto = pp_bigball,
1091 				.pos = p->pos,
1092 				.color = RGBA(0.0, 0.5, 1.0, 0.0),
1093 				.rule = asymptotic,
1094 				.args = { dir, 10 },
1095 			);
1096 		}
1097 		global.shake_view += 5;
1098 		global.shake_view_fade = 0.2;
1099 		aniplayer_hard_switch(&global.boss->ani,"main_mirror",0);
1100 		play_sound("boom");
1101 		return ACTION_DESTROY;
1102 	}
1103 
1104 	p->angle = global.frames + t;
1105 
1106 	tsrand_fill(5);
1107 
1108 	PARTICLE(
1109 		.sprite = afrand(0) > 0.5 ? "lightning0" : "lightning1",
1110 		.pos = p->pos + 3 * (anfrand(1)+I*anfrand(2)),
1111 		.angle = afrand(3) * 2 * M_PI,
1112 		.color = RGBA(1.0, 0.7 + 0.2 * anfrand(4), 0.4, 0.0),
1113 		.timeout = 20,
1114 		.draw_rule = GrowFade,
1115 		.args = { 0, 2.4 },
1116 	);
1117 
1118 	return ACTION_NONE;
1119 }
1120 
iku_extra_fire_trigger_bullet(void)1121 static void iku_extra_fire_trigger_bullet(void) {
1122 	Enemy *e = iku_extra_find_next_slave(global.boss->pos, 250);
1123 
1124 	aniplayer_hard_switch(&global.boss->ani,"dashdown_left",1);
1125 	aniplayer_queue(&global.boss->ani,"main",0);
1126 	if(!e) {
1127 		return;
1128 	}
1129 
1130 	Boss *b = global.boss;
1131 
1132 	PROJECTILE(
1133 		.proto = pp_soul,
1134 		.pos = b->pos,
1135 		.color = RGBA(0.2, 0.2, 1.0, 0.0),
1136 		.rule = iku_extra_trigger_bullet,
1137 		.args = {
1138 			3*cexp(I*carg(e->pos - b->pos)),
1139 			add_ref(e),
1140 			-1
1141 		},
1142 		.flags = PFLAG_NOCLEAR,
1143 	);
1144 
1145 	play_sound("shot_special1");
1146 	play_sound("enemydeath");
1147 	play_sound("shot2");
1148 }
1149 
iku_extra_slave(Enemy * e,int t)1150 static int iku_extra_slave(Enemy *e, int t) {
1151 	GO_TO(e, e->args[0], 0.05);
1152 
1153 	if(e->args[1]) {
1154 		if(creal(e->args[1]) < 2) {
1155 			e->args[1] += 1;
1156 			return 0;
1157 		}
1158 
1159 		if(global.frames == creal(e->args[3])) {
1160 			cmplx o2 = e->args[2];
1161 			e->args[2] = 0;
1162 			Enemy *new = iku_extra_find_next_slave(e->pos, 75);
1163 			e->args[2] = o2;
1164 
1165 			if(new && e != new) {
1166 				e->args[1] = 0;
1167 				e->args[2] = new->args[2] = 600;
1168 				new->args[1] = 1;
1169 				new->args[3] = global.frames + 55 - 5 * global.diff;
1170 
1171 				Laser *l = create_laserline_ab(e->pos, new->pos, 10, 30, e->args[2], RGBA(0.3, 1, 1, 0));
1172 				l->ent.draw_layer = LAYER_LASER_LOW;
1173 				l->unclearable = true;
1174 
1175 				if(global.diff > D_Easy) {
1176 					int cnt = floor(global.diff * 2.5), i;
1177 					double r = frand() * 2 * M_PI;
1178 
1179 					for(i = 0; i < cnt; ++i) {
1180 						PROJECTILE(
1181 							.proto = pp_rice,
1182 							.pos = e->pos,
1183 							.color = RGBA(1, 1, 0, 0),
1184 							.rule = asymptotic,
1185 							.args = { 2*cexp(I*(r+i*2*M_PI/cnt)), 2 },
1186 						);
1187 					}
1188 
1189 					play_sound("shot2");
1190 				}
1191 
1192 				play_sound("redirect");
1193 			} else {
1194 				Enemy *o;
1195 				Laser *l;
1196 				int cnt = 6 + 2 * global.diff, i;
1197 
1198 				global.shake_view = 0;
1199 				global.shake_view_fade = 0.2;
1200 
1201 				e->args[2] = 1;
1202 
1203 				for(o = global.enemies.first; o; o = o->next) {
1204 					if(!o->args[2])
1205 						continue;
1206 
1207 					for(i = 0; i < cnt; ++i) {
1208 						PROJECTILE(
1209 							.proto = pp_ball,
1210 							.pos = o->pos,
1211 							.color = RGBA(0, 1, 1, 0),
1212 							.rule = asymptotic,
1213 							.args = { 1.5*cexp(I*(t + i*2*M_PI/cnt)), 8},
1214 						);
1215 					}
1216 
1217 					o->args[1] = 0;
1218 					o->args[2] = 0;
1219 
1220 					global.shake_view += 1;
1221 				}
1222 
1223 				for(l = global.lasers.first; l; l = l->next) {
1224 					l->deathtime = global.frames - l->birthtime + 20;
1225 				}
1226 				play_sound("boom");
1227 				iku_extra_fire_trigger_bullet();
1228 			}
1229 		}
1230 	}
1231 
1232 	if(e->args[2]) {
1233 		e->args[2] -= 1;
1234 	}
1235 
1236 	return 0;
1237 }
1238 
iku_extra(Boss * b,int t)1239 void iku_extra(Boss *b, int t) {
1240 	TIMER(&t);
1241 
1242 	AT(EVENT_DEATH) {
1243 		enemy_kill_all(&global.enemies);
1244 	}
1245 
1246 	if(t < 0) {
1247 		return;
1248 	}
1249 
1250 	GO_TO(b, VIEWPORT_W/2+50.0*I, 0.02);
1251 
1252 	AT(0) {
1253 		int i, j;
1254 		int cnt = 5;
1255 		double step = VIEWPORT_W / (double)cnt;
1256 
1257 		for(i = 0; i < cnt; ++i) {
1258 			for(j = 0; j < cnt; ++j) {
1259 				cmplx epos = step * (0.5 + i) + (step * j + 125) * I;
1260 				create_enemy4c(b->pos, ENEMY_IMMUNE, iku_extra_slave_visual, iku_extra_slave, epos, 0, 0, 1);
1261 			}
1262 		}
1263 	}
1264 
1265 	AT(60) {
1266 		iku_extra_fire_trigger_bullet();
1267 	}
1268 }
1269 
stage5_spawn_iku(cmplx pos)1270 Boss* stage5_spawn_iku(cmplx pos) {
1271 	Boss *b = create_boss("Nagae Iku", "iku", "dialog/iku", pos);
1272 	b->glowcolor = *RGBA_MUL_ALPHA(0.2, 0.4, 0.5, 0.5);
1273 	b->shadowcolor = *RGBA_MUL_ALPHA(0.65, 0.2, 0.75, 0.5);
1274 	return b;
1275 }
1276 
create_iku(void)1277 static Boss* create_iku(void) {
1278 	Boss *b = stage5_spawn_iku(VIEWPORT_W/2-200.0*I);
1279 
1280 	boss_add_attack(b, AT_Move, "Introduction", 4, 0, iku_intro, NULL);
1281 	boss_add_attack(b, AT_Normal, "Bolts1", 40, 24000, iku_bolts, NULL);
1282 	boss_add_attack_from_info(b, &stage5_spells.boss.atmospheric_discharge, false);
1283 	boss_add_attack(b, AT_Normal, "Bolts2", 45, 27000, iku_bolts2, NULL);
1284 	boss_add_attack_from_info(b, &stage5_spells.boss.artificial_lightning, false);
1285 	boss_add_attack(b, AT_Normal, "Bolts3", 50, 30000, iku_bolts3, NULL);
1286 
1287 	if(global.diff < D_Hard) {
1288 		boss_add_attack_from_info(b, &stage5_spells.boss.induction_field, false);
1289 	} else {
1290 		boss_add_attack_from_info(b, &stage5_spells.boss.inductive_resonance, false);
1291 	}
1292 	boss_add_attack_from_info(b, &stage5_spells.boss.natural_cathode, false);
1293 
1294 	boss_add_attack_from_info(b, &stage5_spells.extra.overload, false);
1295 
1296 	return b;
1297 }
1298 
stage5_events(void)1299 void stage5_events(void) {
1300 	TIMER(&global.timer);
1301 
1302 	AT(0) {
1303 		stage_start_bgm("stage5");
1304 		stage5_skip(env_get("STAGE5_TEST", 0));
1305 		stage_set_voltage_thresholds(255, 480, 860, 1250);
1306 	}
1307 
1308 	FROM_TO(60, 150, 15) {
1309 		create_enemy1c(VIEWPORT_W*(_i&1)+70.0*I+50*_i*I, 400, Fairy, stage5_greeter, 3-6*(_i&1));
1310 	}
1311 
1312 	FROM_TO(270, 320, 40)
1313 		create_enemy1c(VIEWPORT_W/4+VIEWPORT_W/2*_i, 2000, BigFairy, stage5_lightburst, 2.0*I);
1314 
1315 	FROM_TO(400, 600, 10) {
1316 		tsrand_fill(2);
1317 		create_enemy3c(200.0*I*afrand(0), 500, Swirl, stage5_swirl, 4+I, 70+20*afrand(1)+200.0*I, cexp(-0.05*I));
1318 	}
1319 
1320 	FROM_TO(700, 800, 10) {
1321 		tsrand_fill(3);
1322 		create_enemy3c(VIEWPORT_W+200.0*I*afrand(0), 500, Swirl, stage5_swirl, -4+afrand(1)*I, 70+20*afrand(2)+200.0*I, cexp(0.05*I));
1323 	}
1324 
1325 	FROM_TO(870+50*(global.diff==D_Easy), 1000, 50)
1326 		create_enemy1c(VIEWPORT_W/4+VIEWPORT_W/2*(_i&1), 2000, BigFairy, stage5_limiter, I);
1327 
1328 	AT(1000)
1329 		create_enemy1c(VIEWPORT_W/2, 9000, BigFairy, stage5_laserfairy, 2.0*I);
1330 
1331 	FROM_TO(1400+100*(D_Lunatic - global.diff), 2560, 60-5*global.diff) {
1332 		tsrand_fill(2);
1333 		create_enemy1c(VIEWPORT_W+200.0*I*afrand(0), 500, Swirl, stage5_miner, -3+2.0*I*afrand(1));
1334 	}
1335 
1336 	FROM_TO(1500, 2400, 80)
1337 		create_enemy1c(VIEWPORT_W*(_i&1)+100.0*I, 300, Fairy, stage5_greeter, 3-6*(_i&1));
1338 
1339 	AT(2500) {
1340 		create_enemy1c(VIEWPORT_W/2, 2000, BigFairy, stage5_lightburst, 2.0*I);
1341 	}
1342 
1343 	FROM_TO(2200, 2600, 60-8*global.diff)
1344 		create_enemy1c(VIEWPORT_W/(6+global.diff)*_i, 200, Swirl, stage5_miner, 3.0*I);
1345 
1346 	AT(2700) {
1347 		if(global.diff > D_Easy) {
1348 			create_enemy1c(VIEWPORT_W-20+120*I, 2000, BigFairy, stage5_lightburst, -2.0);
1349 		}
1350 	}
1351 
1352 	AT(2900)
1353 		global.boss = create_iku_mid();
1354 
1355 	AT(2920) {
1356 		global.dialog = stage5_dialog_post_midboss();
1357 
1358 		// XXX: this shitty hack is needed to force the dialog to not reopen immediately when it's closed with the shot button
1359 		global.timer++;
1360 	}
1361 
1362 	FROM_TO(3000, 3200, 100) {
1363 		create_enemy1c(VIEWPORT_W/2 + VIEWPORT_W/6*(1-2*(_i&1)), 2000, BigFairy, stage5_lightburst2, -1+2*(_i&1) + 2.0*I);
1364 	}
1365 
1366 	FROM_TO(3300, 4000, 90-10*global.diff)
1367 		create_enemy1c(200.0*I+VIEWPORT_W*(_i&1), 1500, Fairy, stage5_superbullet, 3-6*(_i&1));
1368 
1369 	AT(3400) {
1370 		create_enemy2c(VIEWPORT_W/4, 6000, BigFairy, stage5_laserfairy, 2.0*I, 1);
1371 		create_enemy2c(VIEWPORT_W/4*3, 6000, BigFairy, stage5_laserfairy, 2.0*I, 1);
1372 	}
1373 
1374 	FROM_TO(4200, 5000, 20-3*global.diff) {
1375 		float f = frand();
1376 		create_enemy3c(
1377 			VIEWPORT_W/2+300*sin(global.frames)*cos(2*global.frames),
1378 			400,
1379 			Swirl,
1380 			stage5_swirl,
1381 			2*cexp(I*M_PI*f)+I,
1382 			60 + 100.0*I,
1383 			cexp(0.01*I*(1-2*(f<0.5)))
1384 		);
1385 	}
1386 
1387 	FROM_TO(4320, 4400, 60) {
1388 		double ofs = 32;
1389 		create_enemy1c(ofs + (VIEWPORT_W-2*ofs)*(_i&1) + VIEWPORT_H*I, 200, Swirl, stage5_miner, -I);
1390 	}
1391 
1392 	FROM_TO(4260, 5000, 60) {
1393 		create_enemy1c(VIEWPORT_W*(_i&1)+120*I, 400, Fairy, stage5_greeter, 6 * (1-2*(_i&1)) + I);
1394 	}
1395 
1396 	AT(5000) {
1397 		create_enemy1c(VIEWPORT_W/2, 2000, BigFairy, stage5_lightburst, 2.0*I);
1398 	}
1399 
1400 	FROM_TO(5030, 5060, 30) {
1401 		create_enemy1c(30.0*I+VIEWPORT_W*(_i&1), 1500, Fairy, stage5_superbullet, 2-4*(_i&1) + I);
1402 	}
1403 
1404 	AT(5180) {
1405 		create_enemy1c(VIEWPORT_W/2+100, 2000, BigFairy, stage5_lightburst2, 2.0*I - 0.25);
1406 	}
1407 
1408 	FROM_TO(5060, 5300, 60-10*global.diff) {
1409 		tsrand_fill(2);
1410 		create_enemy1c(VIEWPORT_W+200.0*I*afrand(0), 500, Swirl, stage5_miner, -3+2.0*I*afrand(1));
1411 	}
1412 
1413 	AT(5360) {
1414 		create_enemy1c(30.0*I+VIEWPORT_W, 1500, Fairy, stage5_superbullet, -2 + I);
1415 
1416 	}
1417 
1418 	AT(5390) {
1419 		create_enemy1c(30.0*I, 1500, Fairy, stage5_superbullet, 2 + I);
1420 	}
1421 
1422 	AT(5500) {
1423 		create_enemy1c(VIEWPORT_W+20 + VIEWPORT_H*0.6*I, 2000, BigFairy, stage5_lightburst, -2*I - 2);
1424 		create_enemy1c(          -20 + VIEWPORT_H*0.6*I, 2000, BigFairy, stage5_lightburst, -2*I + 2);
1425 	}
1426 
1427 	AT(5600) {
1428 		enemy_kill_all(&global.enemies);
1429 	}
1430 
1431 	{
1432 		int cnt = 5;
1433 		int step = 10;
1434 		double ofs = 42*2;
1435 
1436 		FROM_TO(5620, 5620 + step*cnt-1, step) {
1437 			cmplx src1 = -ofs/4               + (-ofs/4 +      _i    * (VIEWPORT_H-2*ofs)/(cnt-1))*I;
1438 			cmplx src2 = (VIEWPORT_W + ofs/4) + (-ofs/4 + (cnt-_i-1) * (VIEWPORT_H-2*ofs)/(cnt-1))*I;
1439 			cmplx dst1 = ofs                  + ( ofs   +      _i    * (VIEWPORT_H-2*ofs)/(cnt-1))*I;
1440 			cmplx dst2 = (VIEWPORT_W - ofs)   + ( ofs   + (cnt-_i-1) * (VIEWPORT_H-2*ofs)/(cnt-1))*I;
1441 
1442 			create_enemy2c(src1, 2000, Swirl, stage5_magnetto, dst1, dst2);
1443 			create_enemy2c(src2, 2000, Swirl, stage5_magnetto, dst2, dst1);
1444 		}
1445 	}
1446 
1447 	FROM_TO(6100, 6350, 60-12*global.diff) {
1448 		tsrand_fill(2);
1449 		create_enemy1c(VIEWPORT_W+200.0*I*afrand(0), 500, Swirl, stage5_miner, -3+2.0*I*afrand(1));
1450 	}
1451 
1452 	FROM_TO(6300, 6350, 50) {
1453 		create_enemy1c(VIEWPORT_W/4+VIEWPORT_W/2*!(_i&1), 2000, BigFairy, stage5_limiter, 2*I);
1454 	}
1455 
1456 	AT(6960) {
1457 		stage_unlock_bgm("stage5");
1458 		global.boss = create_iku();
1459 	}
1460 
1461 	AT(6980) {
1462 		stage_unlock_bgm("stage5boss");
1463 		global.dialog = stage5_dialog_post_boss();
1464 	}
1465 
1466 	AT(6985) {
1467 		stage_finish(GAMEOVER_SCORESCREEN);
1468 	}
1469 }
1470