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 "stage4_events.h"
12 #include "stage6_events.h"
13 #include "global.h"
14 #include "stage.h"
15 #include "enemy.h"
16 #include "laser.h"
17 
18 void kurumi_spell_bg(Boss*, int);
19 void kurumi_slaveburst(Boss*, int);
20 void kurumi_redspike(Boss*, int);
21 void kurumi_aniwall(Boss*, int);
22 void kurumi_blowwall(Boss*, int);
23 void kurumi_danmaku(Boss*, int);
24 void kurumi_extra(Boss*, int);
25 
stage4_dialog_pre_boss(void)26 static Dialog *stage4_dialog_pre_boss(void) {
27 	PlayerMode *pm = global.plr.mode;
28 	Dialog *d = dialog_create();
29 	dialog_set_image(d, DIALOG_LEFT, pm->character->dialog_base_sprite_name);
30 	dialog_set_image(d, DIALOG_RIGHT, "dialog/kurumi");
31 	pm->dialog->stage4_pre_boss(d);
32 	dialog_add_action(d, DIALOG_SET_BGM, "stage4boss");
33 	return d;
34 }
35 
stage4_dialog_post_boss(void)36 static Dialog *stage4_dialog_post_boss(void) {
37 	PlayerMode *pm = global.plr.mode;
38 	Dialog *d = dialog_create();
39 	dialog_set_image(d, DIALOG_LEFT, pm->character->dialog_base_sprite_name);
40 	dialog_set_image(d, DIALOG_RIGHT, "dialog/kurumi");
41 	pm->dialog->stage4_post_boss(d);
42 	return d;
43 }
44 
stage4_splasher(Enemy * e,int t)45 static int stage4_splasher(Enemy *e, int t) {
46 	TIMER(&t);
47 	AT(EVENT_KILLED) {
48 		spawn_items(e->pos, ITEM_POINTS, 3, ITEM_POWER, 1, ITEM_BOMB, 1);
49 		return 1;
50 	}
51 
52 	e->moving = true;
53 	e->dir = creal(e->args[0]) < 0;
54 
55 	FROM_TO(0, 50, 1)
56 		e->pos += e->args[0]*(1-t/50.0);
57 
58 	FROM_TO_SND("shot1_loop", 66-6*global.diff, 150, 5-global.diff) {
59 		tsrand_fill(4);
60 		PROJECTILE(
61 			.proto = afrand(0) > 0.5 ? pp_rice : pp_thickrice,
62 			.pos = e->pos,
63 			.color = RGB(0.8,0.3-0.1*afrand(1),0.5),
64 			.rule = accelerated,
65 			.args = {
66 				e->args[0]/2+(1-2*afrand(2))+(1-2*afrand(3))*I,
67 				0.02*I
68 			}
69 		);
70 	}
71 
72 	FROM_TO(200, 300, 1)
73 		e->pos -= creal(e->args[0])*(t-200)/100.0;
74 
75 	return 1;
76 }
77 
stage4_fodder(Enemy * e,int t)78 static int stage4_fodder(Enemy *e, int t) {
79 	TIMER(&t);
80 	AT(EVENT_KILLED) {
81 		spawn_items(e->pos, ITEM_POWER, 1);
82 		return 1;
83 	}
84 
85 	if(creal(e->args[0]) != 0)
86 		e->moving = true;
87 	e->dir = creal(e->args[0]) < 0;
88 	e->pos += e->args[0];
89 
90 	FROM_TO(10, 200, 120) {
91 		cmplx fairy_halfsize = 21 * (1 + I);
92 
93 		if(!rect_rect_intersect(
94 			(Rect) { e->pos - fairy_halfsize, e->pos + fairy_halfsize },
95 			(Rect) { 0, CMPLX(VIEWPORT_W, VIEWPORT_H) },
96 			true, true)
97 		) {
98 			return 1;
99 		}
100 
101 		play_sound_ex("shot3", 5, false);
102 		cmplx aim = global.plr.pos - e->pos;
103 		aim /= cabs(aim);
104 
105 		float speed = 3;
106 		float boost_factor = 1.2;
107 		float boost_base = 1;
108 		int chain_len = global.diff + 2;
109 
110 		PROJECTILE(
111 			.proto = pp_wave,
112 			.pos = e->pos,
113 			.color = RGB(1, 0.3, 0.5),
114 			.rule = asymptotic,
115 			.args = {
116 				speed * aim,
117 				boost_base + (chain_len + 1) * boost_factor,
118 			},
119 			.max_viewport_dist = 32,
120 		);
121 
122 		for(int i = chain_len; i; --i) {
123 			PROJECTILE(
124 				.proto = pp_crystal,
125 				.pos = e->pos,
126 				.color = RGB(i / (float)(chain_len+1), 0.3, 0.5),
127 				.rule = asymptotic,
128 				.args = {
129 					speed * aim,
130 					boost_base + i * boost_factor,
131 				},
132 				.max_viewport_dist = 32,
133 			);
134 		}
135 
136 		PROJECTILE(
137 			.proto = pp_ball,
138 			.pos = e->pos,
139 			.color = RGB(0, 0.3, 0.5),
140 			.rule = asymptotic,
141 			.args = {
142 				speed * aim,
143 				boost_base,
144 			},
145 			.max_viewport_dist = 32,
146 		);
147 	}
148 
149 	return 1;
150 }
151 
stage4_partcircle(Enemy * e,int t)152 static int stage4_partcircle(Enemy *e, int t) {
153 	TIMER(&t);
154 	AT(EVENT_KILLED) {
155 		spawn_items(e->pos, ITEM_POINTS, 3);
156 		return 1;
157 	}
158 
159 	e->pos += e->args[0];
160 
161 	FROM_TO(30,60,1) {
162 		e->args[0] *= 0.9;
163 	}
164 
165 	FROM_TO(60,76,1) {
166 		int i;
167 		for(i = 0; i < global.diff; i++) {
168 			play_sound("shot2");
169 			cmplx n = cexp(I*M_PI/16.0*_i + I*carg(e->args[0])-I*M_PI/4.0 + 0.01*I*i*(1-2*(creal(e->args[0]) > 0)));
170 			PROJECTILE(
171 				.proto = pp_wave,
172 				.pos = e->pos + (30)*n,
173 				.color = RGB(1-0.2*i,0.5,0.7),
174 				.rule = asymptotic,
175 				.args = { 2*n, 2+2*i }
176 			);
177 		}
178 	}
179 
180 	FROM_TO(160, 200, 1)
181 		e->args[0] += 0.05*I;
182 
183 	return 1;
184 }
185 
stage4_cardbuster(Enemy * e,int t)186 static int stage4_cardbuster(Enemy *e, int t) {
187 	TIMER(&t);
188 	AT(EVENT_KILLED) {
189 		spawn_items(e->pos, ITEM_POINTS, 3, ITEM_POWER, 1);
190 		return 1;
191 	}
192 
193 	FROM_TO(0, 120, 1)
194 		e->pos += (e->args[0]-e->pos0)/120.0;
195 
196 	FROM_TO(200, 300, 1)
197 		e->pos += (e->args[1]-e->args[0])/100.0;
198 
199 	FROM_TO(400, 600, 1)
200 		e->pos += (e->args[2]-e->args[1])/200.0;
201 
202 	int c = 40;
203 	cmplx n = cexp(I*carg(global.plr.pos - e->pos) + 4*M_PI/(c+1)*I*_i);
204 
205 	FROM_TO_SND("shot1_loop", 60, 60+c*global.diff, 1) {
206 		for(int i = 0; i < 3; ++i) {
207 			PROJECTILE(
208 				.proto = pp_card,
209 				.pos = e->pos + 30*n,
210 				.color = RGB(0, 0.2, 0.4 + 0.2 * i),
211 				.rule = accelerated,
212 				.args = { (0.8+0.2*global.diff)*(1.0 + 0.1 * i)*n, 0.01*(1+0.01*_i)*n }
213 			);
214 		}
215 	}
216 
217 	FROM_TO_SND("shot1_loop", 240, 260+20*global.diff, 1) {
218 		PROJECTILE(
219 			.proto = pp_card,
220 			.pos = e->pos + 30*n,
221 			.color = RGB(0, 0.7, 0.5),
222 			.rule = asymptotic,
223 			.args = { (0.8+0.2*global.diff)*n, 0.4*I }
224 		);
225 	}
226 
227 	return 1;
228 }
229 
stage4_backfire(Enemy * e,int t)230 static int stage4_backfire(Enemy *e, int t) {
231 	TIMER(&t);
232 	AT(EVENT_KILLED) {
233 		spawn_items(e->pos, ITEM_POINTS, 5);
234 		return 1;
235 	}
236 
237 	FROM_TO(0,20,1)
238 		e->args[0] -= 0.05*I;
239 
240 	FROM_TO(60,100,1)
241 		e->args[0] += 0.05*I;
242 
243 	if(t > 100)
244 		e->args[0] -= 0.02*I;
245 
246 
247 	e->pos += e->args[0];
248 
249 	FROM_TO(20,180+global.diff*20,2) {
250 		play_sound("shot2");
251 		cmplx n = cexp(I*M_PI*frand()-I*copysign(M_PI/2.0, creal(e->args[0])));
252 		for(int i = 0; i < global.diff; i++) {
253 			PROJECTILE(
254 				.proto = pp_wave,
255 				.pos = e->pos,
256 				.color = RGB(0.2, 0.2, 1-0.2*i),
257 				.rule = asymptotic,
258 				.args = { 2*n, 2+2*i }
259 			);
260 		}
261 	}
262 
263 	return 1;
264 }
265 
stage4_bigcircle(Enemy * e,int t)266 static int stage4_bigcircle(Enemy *e, int t) {
267 	TIMER(&t);
268 	AT(EVENT_KILLED) {
269 		spawn_items(e->pos, ITEM_POINTS, 3, ITEM_POWER, 1);
270 
271 		return 1;
272 	}
273 
274 	FROM_TO(0, 70, 1)
275 		e->pos += e->args[0];
276 
277 	FROM_TO(200, 300, 1)
278 		e->pos -= e->args[0];
279 
280 
281 	FROM_TO(80,100+30*global.diff,20) {
282 		play_sound("shot_special1");
283 		int i;
284 		int n = 10+3*global.diff;
285 		for(i = 0; i < n; i++) {
286 			PROJECTILE(
287 				.proto = pp_bigball,
288 				.pos = e->pos,
289 				.color = RGBA(0, 0.8 - 0.4 * _i, 0, 0),
290 				.rule = asymptotic,
291 				.args = {
292 					2*cexp(2.0*I*M_PI/n*i+I*3*_i),
293 					3*sin(6*M_PI/n*i)
294 				},
295 			);
296 
297 			if(global.diff > D_Easy) {
298 				PROJECTILE(
299 					.proto = pp_ball,
300 					.pos = e->pos,
301 					.color = RGBA(0, 0.3 * _i, 0.4, 0),
302 					.rule = asymptotic,
303 					.args = {
304 						(1.5+global.diff*0.2)*cexp(I*3*(i+frand())),
305 						I*5*sin(6*M_PI/n*i)
306 					},
307 				);
308 			}
309 		}
310 	}
311 	return 1;
312 }
313 
stage4_explosive(Enemy * e,int t)314 static int stage4_explosive(Enemy *e, int t) {
315 	if(t == EVENT_KILLED || (t >= 100 && global.diff >= D_Normal)) {
316 		int i;
317 
318 		if(t == EVENT_KILLED)
319 			spawn_items(e->pos, ITEM_POWER, 1);
320 
321 		int n = 10*global.diff;
322 		cmplx phase = global.plr.pos-e->pos;
323 		phase /= cabs(phase);
324 
325 		for(i = 0; i < n; i++) {
326 			double angle = 2*M_PI*i/n+carg(phase);
327 			PROJECTILE(
328 				.proto = pp_ball,
329 				.pos = e->pos,
330 				.color = RGB(0.1+0.6*(i&1), 0.2, 1-0.6*(i&1)),
331 				.rule = accelerated,
332 				.args = {
333 					1.5*(1.1+0.3*global.diff)*cexp(I*angle),
334 					0.001*cexp(I*angle)
335 				}
336 			);
337 		}
338 
339 		play_sound("shot1");
340 		return ACTION_DESTROY;
341 	}
342 
343 	e->pos += e->args[0];
344 
345 	return 1;
346 }
347 
KurumiSlave(Enemy * e,int t,bool render)348 static void KurumiSlave(Enemy *e, int t, bool render) {
349 	if(render) {
350 		return;
351 	}
352 
353 	if(!(t%2)) {
354 		cmplx offset = (frand()-0.5)*30;
355 		offset += (frand()-0.5)*20.0*I;
356 		PARTICLE(
357 			.sprite = "smoothdot",
358 			.pos = offset,
359 			.color = RGBA(0.3, 0.0, 0.0, 0.0),
360 			.draw_rule = Shrink,
361 			.rule = enemy_flare,
362 			.timeout = 50,
363 			.args = { (-50.0*I-offset)/50.0, add_ref(e) },
364 			.flags = PFLAG_REQUIREDPARTICLE,
365 		);
366 	}
367 }
368 
kurumi_intro(Boss * b,int t)369 static void kurumi_intro(Boss *b, int t) {
370 	GO_TO(b, BOSS_DEFAULT_GO_POS, 0.03);
371 }
372 
kurumi_burstslave(Enemy * e,int t)373 static int kurumi_burstslave(Enemy *e, int t) {
374 	TIMER(&t);
375 	AT(EVENT_BIRTH)
376 		e->args[1] = e->args[0];
377 	AT(EVENT_DEATH) {
378 		free_ref(e->args[2]);
379 		return 1;
380 	}
381 
382 
383 	if(t == 600 || REF(e->args[2]) == NULL)
384 		return ACTION_DESTROY;
385 
386 	e->pos += 2*e->args[1]*(sin(t/10.0)+1.5);
387 
388 	FROM_TO(0, 600, 18-2*global.diff) {
389 		float r = cimag(e->pos)/VIEWPORT_H;
390 
391 		for(int i = 1; i >= -1; i -= 2) {
392 			PROJECTILE(
393 				.proto = pp_wave,
394 				.pos = e->pos + i*10.0*I*e->args[0],
395 				.color = RGB(r,0,0),
396 				.rule = accelerated,
397 				.args = { i*2.0*I*e->args[0], -0.01*e->args[1] }
398 			);
399 		}
400 
401 		play_sound("shot1");
402 	}
403 
404 	FROM_TO(40, 100,1) {
405 		e->args[1] -= e->args[0]*0.02;
406 		e->args[1] *= cexp(0.02*I);
407 	}
408 
409 	return 1;
410 }
411 
kurumi_slaveburst(Boss * b,int time)412 void kurumi_slaveburst(Boss *b, int time) {
413 	int t = time % 400;
414 	TIMER(&t);
415 
416 	if(time == EVENT_DEATH)
417 		enemy_kill_all(&global.enemies);
418 
419 	if(time < 0)
420 		return;
421 
422 	AT(0) {
423 		int i;
424 		int n = 3+2*global.diff;
425 		for(i = 0; i < n; i++) {
426 			create_enemy3c(b->pos, ENEMY_IMMUNE, KurumiSlave, kurumi_burstslave, cexp(I*2*M_PI/n*i+0.2*I*time/500), 0, add_ref(b));
427 		}
428 	}
429 }
430 
kurumi_spikeslave(Enemy * e,int t)431 static int kurumi_spikeslave(Enemy *e, int t) {
432 	TIMER(&t);
433 	AT(EVENT_BIRTH)
434 		e->args[1] = e->args[0];
435 	AT(EVENT_DEATH) {
436 		free_ref(e->args[2]);
437 		return 1;
438 	}
439 
440 
441 	if(t == 300+50*global.diff || REF(e->args[2]) == NULL)
442 		return ACTION_DESTROY;
443 
444 	e->pos += e->args[1];
445 	e->args[1] *= cexp(0.01*I*e->args[0]);
446 
447 	FROM_TO(0, 600, 18-2*global.diff) {
448 		float r = cimag(e->pos)/VIEWPORT_H;
449 
450 		for(int i = 1; i >= -1; i -= 2) {
451 			PROJECTILE(
452 				.proto = pp_wave,
453 				.pos = e->pos + 10.0*I*e->args[0],
454 				.color = RGB(r,0,0),
455 				.rule = linear,
456 				.args = { i*1.5*I*e->args[1] }
457 			);
458 		}
459 
460 		play_sound("shot1");
461 	}
462 
463 	return 1;
464 }
465 
kurumi_redspike(Boss * b,int time)466 void kurumi_redspike(Boss *b, int time) {
467 	int t = time % 500;
468 
469 	if(time == EVENT_DEATH)
470 		enemy_kill_all(&global.enemies);
471 
472 	if(time < 0)
473 		return;
474 
475 	TIMER(&t);
476 
477 	FROM_TO(0, 500, 60) {
478 		create_enemy3c(b->pos, ENEMY_IMMUNE, KurumiSlave, kurumi_spikeslave, 1-2*(_i&1), 0, add_ref(b));
479 	}
480 
481 	if(global.diff < D_Hard) {
482 		FROM_TO(0, 500, 150-50*global.diff) {
483 			int i;
484 			int n = global.diff*8;
485 			for(i = 0; i < n; i++) {
486 				PROJECTILE(
487 					.proto = pp_bigball,
488 					.pos = b->pos,
489 					.color = RGBA(1.0, 0.0, 0.0, 0.0),
490 					.rule = asymptotic,
491 					.args = {
492 						(1+0.1*(global.diff == D_Normal))*3*cexp(2.0*I*M_PI/n*i+I*carg(global.plr.pos-b->pos)),
493 						3
494 					},
495 				);
496 			}
497 
498 			play_sound("shot_special1");
499 		}
500 	} else {
501 		AT(80) {
502 			aniplayer_queue(&b->ani, "muda", 0);
503 		}
504 
505 		AT(499) {
506 			aniplayer_queue(&b->ani, "main", 0);
507 		}
508 
509 		FROM_TO_INT(80, 500, 40,200,2+2*(global.diff == D_Hard)) {
510 			tsrand_fill(2);
511 			cmplx offset = 100*afrand(0)*cexp(2.0*I*M_PI*afrand(1));
512 			cmplx n = cexp(I*carg(global.plr.pos-b->pos-offset));
513 			PROJECTILE(
514 				.proto = pp_rice,
515 				.pos = b->pos+offset,
516 				.color = RGBA(1, 0, 0, 0),
517 				.rule = accelerated,
518 				.args = { -1*n, 0.05*n },
519 			);
520 			play_sound_ex("warp",0,false);
521 		}
522 	}
523 }
524 
kurumi_spell_bg(Boss * b,int time)525 void kurumi_spell_bg(Boss *b, int time) {
526 	float f = 0.5+0.5*sin(time/80.0);
527 
528 	r_mat_mv_push();
529 	r_mat_mv_translate(VIEWPORT_W/2, VIEWPORT_H/2,0);
530 	r_mat_mv_scale(0.6, 0.6, 1);
531 	r_color3(f, 1 - f, 1 - f);
532 	draw_sprite(0, 0, "stage4/kurumibg1");
533 	r_mat_mv_pop();
534 	r_color4(1, 1, 1, 0);
535 	fill_viewport(time/300.0, time/300.0, 0.5, "stage4/kurumibg2");
536 	r_color4(1, 1, 1, 1);
537 }
538 
kurumi_outro(Boss * b,int time)539 static void kurumi_outro(Boss *b, int time) {
540 	b->pos += -5-I;
541 }
542 
kurumi_global_rule(Boss * b,int time)543 static void kurumi_global_rule(Boss *b, int time) {
544 	// FIXME: avoid running this every frame!
545 
546 	if(b->current && ATTACK_IS_SPELL(b->current->type)) {
547 		b->shadowcolor = *RGBA_MUL_ALPHA(0.0, 0.4, 0.5, 0.5);
548 	} else {
549 		b->shadowcolor = *RGBA_MUL_ALPHA(1.0, 0.1, 0.0, 0.5);
550 	}
551 }
552 
stage4_spawn_kurumi(cmplx pos)553 Boss* stage4_spawn_kurumi(cmplx pos) {
554 	Boss* b = create_boss("Kurumi", "kurumi", "dialog/kurumi", pos);
555 	b->glowcolor = *RGB(0.5, 0.1, 0.0);
556 	b->global_rule = kurumi_global_rule;
557 	return b;
558 }
559 
create_kurumi_mid(void)560 static Boss* create_kurumi_mid(void) {
561 	Boss *b = stage4_spawn_kurumi(VIEWPORT_W/2-400.0*I);
562 
563 	boss_add_attack(b, AT_Move, "Introduction", 2, 0, kurumi_intro, NULL);
564 	boss_add_attack_from_info(b, &stage4_spells.mid.gate_of_walachia, false);
565 	if(global.diff < D_Hard) {
566 		boss_add_attack_from_info(b, &stage4_spells.mid.dry_fountain, false);
567 	} else {
568 		boss_add_attack_from_info(b, &stage4_spells.mid.red_spike, false);
569 	}
570 	boss_add_attack(b, AT_Move, "Outro", 2, 1, kurumi_outro, NULL);
571 	boss_start_attack(b, b->attacks);
572 	return b;
573 }
574 
splitcard(Projectile * p,int t)575 static int splitcard(Projectile *p, int t) {
576 	if(t < 0) {
577 		return ACTION_ACK;
578 	}
579 
580 	if(t == creal(p->args[2])) {
581 		// projectile_set_prototype(p, pp_bigball);
582 		p->color = *RGB(p->color.b, 0.2, p->color.g);
583 		play_sound_ex("redirect", 10, false);
584 		spawn_projectile_highlight_effect(p);
585 	}
586 
587 	if(t > creal(p->args[2])) {
588 		p->args[0] += 0.01*p->args[3];
589 		asymptotic(p, t);
590 	} else {
591 		p->angle = carg(p->args[0]);
592 	}
593 
594 	return ACTION_NONE; // asymptotic(p, t);
595 }
596 
stage4_supercard(Enemy * e,int t)597 static int stage4_supercard(Enemy *e, int t) {
598 	int time = t % 150;
599 
600 	TIMER(&t);
601 	AT(EVENT_KILLED) {
602 		spawn_items(e->pos, ITEM_POINTS, 5);
603 		return 1;
604 	}
605 
606 	e->pos += e->args[0];
607 
608 	FROM_TO(50, 70, 1) {
609 		e->args[0] *= 0.7;
610 	}
611 
612 	if(t > 450) {
613 		e->pos -= I;
614 		return 1;
615 	}
616 
617 	__timep = &time;
618 
619 	FROM_TO(70, 70+20*global.diff, 1) {
620 		play_sound_ex("shot1",5,false);
621 
622 		cmplx n = cexp(I*(2*M_PI/20.0*_i + (t / 150) * M_PI/4));
623 		for(int i = -1; i <= 1 && t; i++) {
624 			PROJECTILE(
625 				.proto = pp_card,
626 				.pos = e->pos + 30*n,
627 				.color = RGB(0,0.4,1-_i/40.0),
628 				.rule = splitcard,
629 				.args = {1*n, 0.1*_i, 100-time+70, 2*I*i*n}
630 			);
631 		}
632 	}
633 
634 	return 1;
635 }
636 
kurumi_boss_intro(Boss * b,int t)637 static void kurumi_boss_intro(Boss *b, int t) {
638 	TIMER(&t);
639 	GO_TO(b, BOSS_DEFAULT_GO_POS, 0.015);
640 
641 	AT(120)
642 		global.dialog = stage4_dialog_pre_boss();
643 }
644 
splitcard_elly(Projectile * p,int t)645 static int splitcard_elly(Projectile *p, int t) {
646 	if(t < 0) {
647 		return ACTION_ACK;
648 	}
649 
650 	if(t == creal(p->args[2])) {
651 		p->args[0]+=p->args[3];
652 		// p->color = derive_color(p->color, CLRMASK_B, RGB(0,0,-color_component(p->color,CLR_B)));
653 		// FIXME: what was even the intention here?
654 		p->color.b *= -1;
655 		play_sound_ex("redirect", 10, false);
656 		spawn_projectile_highlight_effect(p);
657 	}
658 
659 	return asymptotic(p, t);
660 }
661 
kurumi_breaker(Boss * b,int time)662 static void kurumi_breaker(Boss *b, int time) {
663 	int t = time % 400;
664 	int i;
665 
666 	int c = 10+global.diff*2;
667 	int kt = 20;
668 
669 	if(time < 0)
670 		return;
671 
672 	GO_TO(b, VIEWPORT_W/2 + VIEWPORT_W/3*sin(time/220) + I*cimag(b->pos), 0.02);
673 
674 	TIMER(&t);
675 
676 	FROM_TO_SND("shot1_loop", 50, 400, 50-7*global.diff) {
677 		cmplx p = b->pos + 150*sin(_i) + 100.0*I*cos(_i);
678 
679 		for(i = 0; i < c; i++) {
680 			cmplx n = cexp(2.0*I*M_PI/c*i);
681 			PROJECTILE(
682 				.proto = pp_rice,
683 				.pos = p,
684 				.color = RGB(1,0,0.5),
685 				.rule = splitcard_elly,
686 				.args = {
687 					3*n,
688 					0,
689 					kt,
690 					1.5*cexp(I*carg(global.plr.pos - p - 2*kt*n))-2.6*n
691 			});
692 		}
693 	}
694 
695 	FROM_TO(60, 400, 100) {
696 		play_sound("shot_special1");
697 		aniplayer_queue(&b->ani,"muda",1);
698 		aniplayer_queue(&b->ani,"main",0);
699 		for(i = 0; i < 20; i++) {
700 			PROJECTILE(
701 				.proto = pp_bigball,
702 				.pos = b->pos,
703 				.color = RGBA(0.5, 0.0, 0.5, 0.0),
704 				.rule = asymptotic,
705 				.args = { cexp(2.0*I*M_PI/20.0*i), 3 },
706 			);
707 		}
708 	}
709 }
710 
aniwall_bullet(Projectile * p,int t)711 static int aniwall_bullet(Projectile *p, int t) {
712 	if(t < 0) {
713 		return ACTION_ACK;
714 	}
715 
716 	if(t > creal(p->args[1])) {
717 		if(global.diff > D_Normal) {
718 			tsrand_fill(2);
719 			p->args[0] += 0.1*(0.1-0.2*afrand(0) + 0.1*I-0.2*I*afrand(1))*(global.diff-2);
720 			p->args[0] += 0.002*cexp(I*carg(global.plr.pos - p->pos));
721 		}
722 
723 		p->pos += p->args[0];
724 	}
725 
726 	p->color.r = cimag(p->pos)/VIEWPORT_H;
727 	return ACTION_NONE;
728 }
729 
aniwall_slave(Enemy * e,int t)730 static int aniwall_slave(Enemy *e, int t) {
731 	float re, im;
732 
733 	if(t < 0)
734 		return 1;
735 
736 	if(creal(e->pos) <= 0)
737 		e->pos = I*cimag(e->pos);
738 	if(creal(e->pos) >= VIEWPORT_W)
739 		e->pos = VIEWPORT_W + I*cimag(e->pos);
740 	if(cimag(e->pos) <= 0)
741 		e->pos = creal(e->pos);
742 	if(cimag(e->pos) >= VIEWPORT_H)
743 		e->pos = creal(e->pos) + I*VIEWPORT_H;
744 
745 	re = creal(e->pos);
746 	im = cimag(e->pos);
747 
748 	if(cabs(e->args[1]) <= 0.1) {
749 		if(re == 0 || re == VIEWPORT_W) {
750 
751 			e->args[1] = 1;
752 			e->args[2] = 10.0*I;
753 		}
754 
755 		e->pos += e->args[0]*t;
756 	} else {
757 		if((re <= 0) + (im <= 0) + (re >= VIEWPORT_W) + (im >= VIEWPORT_H) == 2) {
758 			float sign = 1;
759 			sign *= 1-2*(re > 0);
760 			sign *= 1-2*(im > 0);
761 			sign *= 1-2*(cimag(e->args[2]) == 0);
762 			e->args[2] *= sign*I;
763 		}
764 
765 		e->pos += e->args[2];
766 
767 		if(!(t % 7-global.diff-2*(global.diff > D_Normal))) {
768 			cmplx v = e->args[2]/cabs(e->args[2])*I*sign(creal(e->args[0]));
769 			if(cimag(v) > -0.1 || global.diff >= D_Normal) {
770 				play_sound("shot1");
771 				PROJECTILE(
772 					.proto = pp_ball,
773 					.pos = e->pos+I*v*20*nfrand(),
774 					.color = RGB(1,0,0),
775 					.rule = aniwall_bullet,
776 					.args = { 1*v, 40 }
777 				);
778 			}
779 		}
780 	}
781 
782 	return 1;
783 }
784 
KurumiAniWallSlave(Enemy * e,int t,bool render)785 static void KurumiAniWallSlave(Enemy *e, int t, bool render) {
786 	if(render) {
787 		return;
788 	}
789 
790 	if(e->args[1]) {
791 		PARTICLE(
792 			.sprite = "smoothdot",
793 			.pos = e->pos,
794 			.color = RGBA(1, 1, 1, 0),
795 			.draw_rule = Fade,
796 			.timeout = 30,
797 		);
798 	}
799 }
800 
kurumi_aniwall(Boss * b,int time)801 void kurumi_aniwall(Boss *b, int time) {
802 	TIMER(&time);
803 
804 	AT(EVENT_DEATH) {
805 		enemy_kill_all(&global.enemies);
806 	}
807 
808 	GO_TO(b, VIEWPORT_W/2 + VIEWPORT_W/3*sin(time/200) + I*cimag(b->pos),0.03)
809 
810 	if(time < 0)
811 		return;
812 
813 	AT(0) {
814 		aniplayer_queue(&b->ani, "muda", 0);
815 		play_sound("laser1");
816 		create_lasercurve2c(b->pos, 50, 80, RGBA(1.0, 0.8, 0.8, 0.0), las_accel, 0, 0.2*cexp(0.4*I));
817 		create_enemy1c(b->pos, ENEMY_IMMUNE, KurumiAniWallSlave, aniwall_slave, 0.2*cexp(0.4*I));
818 		create_lasercurve2c(b->pos, 50, 80, RGBA(1.0, 0.8, 0.8, 0.0), las_accel, 0, 0.2*cexp(I*M_PI - 0.4*I));
819 		create_enemy1c(b->pos, ENEMY_IMMUNE, KurumiAniWallSlave, aniwall_slave, 0.2*cexp(I*M_PI - 0.4*I));
820 	}
821 }
822 
kurumi_sbreaker(Boss * b,int time)823 static void kurumi_sbreaker(Boss *b, int time) {
824 	if(time < 0)
825 		return;
826 
827 	int dur = 300+50*global.diff;
828 	int t = time % dur;
829 	int i;
830 	TIMER(&t);
831 
832 	int c = 10+global.diff*2;
833 	int kt = 40;
834 
835 	FROM_TO_SND("shot1_loop", 50, dur, 2+(global.diff < D_Hard)) {
836 		cmplx p = b->pos + 150*sin(_i/8.0)+100.0*I*cos(_i/15.0);
837 
838 		cmplx n = cexp(2.0*I*M_PI/c*_i);
839 		PROJECTILE(
840 			.proto = pp_rice,
841 			.pos = p,
842 			.color = RGB(1.0, 0.0, 0.5),
843 			.rule = splitcard_elly,
844 			.args = {
845 				2*n,
846 				0,
847 				kt,
848 				1.5*cexp(I*carg(global.plr.pos - p - 2*kt*n))-1.7*n
849 			}
850 		);
851 	}
852 
853 	FROM_TO(60, dur, 100) {
854 		play_sound("shot_special1");
855 		aniplayer_queue(&b->ani, "muda", 4);
856 		aniplayer_queue(&b->ani, "main", 0);
857 
858 		for(i = 0; i < 20; i++) {
859 			PROJECTILE(
860 				.proto = pp_bigball,
861 				.pos = b->pos,
862 				.color = RGBA(0.5, 0.0, 0.5, 0.0),
863 				.rule = asymptotic,
864 				.args = { cexp(2.0*I*M_PI/20.0*i), 3 },
865 			);
866 		}
867 	}
868 }
869 
blowwall_slave(Enemy * e,int t)870 static int blowwall_slave(Enemy *e, int t) {
871 	float re, im;
872 
873 	if(t < 0)
874 		return 1;
875 
876 	e->pos += e->args[0]*t;
877 
878 	if(creal(e->pos) <= 0)
879 		e->pos = I*cimag(e->pos);
880 	if(creal(e->pos) >= VIEWPORT_W)
881 		e->pos = VIEWPORT_W + I*cimag(e->pos);
882 	if(cimag(e->pos) <= 0)
883 		e->pos = creal(e->pos);
884 	if(cimag(e->pos) >= VIEWPORT_H)
885 		e->pos = creal(e->pos) + I*VIEWPORT_H;
886 
887 	re = creal(e->pos);
888 	im = cimag(e->pos);
889 
890 	if(re <= 0 || im <= 0 || re >= VIEWPORT_W || im >= VIEWPORT_H) {
891 		int i, c;
892 		float f;
893 		ProjPrototype *type;
894 
895 		c = 20 + global.diff*40;
896 
897 		for(i = 0; i < c; i++) {
898 			f = frand();
899 
900 			if(f < 0.3) {
901 				type = pp_soul;
902 			} else if(f < 0.6) {
903 				type = pp_bigball;
904 			} else {
905 				type = pp_plainball;
906 			}
907 
908 			PROJECTILE(
909 				.proto = type,
910 				.pos = e->pos,
911 				.color = RGBA(1.0, 0.1, 0.1, 0.0),
912 				.rule = asymptotic,
913 				.args = { (1+3*f)*cexp(2.0*I*M_PI*frand()), 4 },
914 			);
915 		}
916 
917 		play_sound("shot_special1");
918 		return ACTION_DESTROY;
919 	}
920 
921 	return 1;
922 }
923 
bwlaser(Boss * b,float arg,int slave)924 static void bwlaser(Boss *b, float arg, int slave) {
925 	create_lasercurve2c(b->pos, 50, 100, RGBA(1.0, 0.5+0.3*slave, 0.5+0.3*slave, 0.0), las_accel, 0, (0.1+0.1*slave)*cexp(I*arg));
926 
927 	if(slave) {
928 		play_sound("laser1");
929 		create_enemy1c(b->pos, ENEMY_IMMUNE, NULL, blowwall_slave, 0.2*cexp(I*arg));
930 	} else {
931 		// FIXME: needs a better sound
932 		play_sound("shot2");
933 		play_sound("shot_special1");
934 	}
935 }
936 
kurumi_blowwall(Boss * b,int time)937 void kurumi_blowwall(Boss *b, int time) {
938 	int t = time % 600;
939 	TIMER(&t);
940 
941 	if(time == EVENT_DEATH)
942 		enemy_kill_all(&global.enemies);
943 
944 	if(time < 0) {
945 		return;
946 	}
947 
948 	GO_TO(b, BOSS_DEFAULT_GO_POS, 0.04)
949 
950 	AT(0) {
951 		aniplayer_queue(&b->ani,"muda",0);
952 	}
953 
954 	AT(50)
955 		bwlaser(b, 0.4, 1);
956 
957 	AT(100)
958 		bwlaser(b, M_PI-0.4, 1);
959 
960 	FROM_TO(200, 300, 50)
961 		bwlaser(b, -M_PI*frand(), 1);
962 
963 	FROM_TO(300, 500, 10)
964 		bwlaser(b, M_PI/10*_i, 0);
965 
966 }
967 
vapor_particle(cmplx pos,const Color * clr)968 static Projectile* vapor_particle(cmplx pos, const Color *clr) {
969 	return PARTICLE(
970 		.sprite = "stain",
971 		.color = clr,
972 		.timeout = 60,
973 		.draw_rule = ScaleFade,
974 		.args = { 0, 0, 0.2 + 2.0*I },
975 		.pos = pos,
976 		.angle = M_PI*2*frand(),
977 	);
978 }
979 
kdanmaku_proj(Projectile * p,int t)980 static int kdanmaku_proj(Projectile *p, int t) {
981 	if(t < 0) {
982 		return ACTION_ACK;
983 	}
984 
985 	int time = creal(p->args[0]);
986 
987 	if(t == time) {
988 		p->color = *RGBA(0.6, 0.3, 1.0, 0.0);
989 		projectile_set_prototype(p, pp_bullet);
990 		p->args[1] = (global.plr.pos - p->pos) * 0.001;
991 
992 		if(frand() < 0.5) {
993 			Projectile *v = vapor_particle(p->pos, color_mul_scalar(COLOR_COPY(&p->color), 0.5));
994 
995 			if(frand() < 0.5) {
996 				v->flags |= PFLAG_REQUIREDPARTICLE;
997 			}
998 		}
999 
1000 		PARTICLE(
1001 			.sprite = "flare",
1002 			.color = RGB(1, 1, 1),
1003 			.timeout = 30,
1004 			.draw_rule = ScaleFade,
1005 			.args = { 0, 0, 3.0 },
1006 			.pos = p->pos,
1007 		);
1008 
1009 		play_sound("shot3");
1010 	}
1011 
1012 	if(t > time && cabs(p->args[1]) < 2) {
1013 		p->args[1] *= 1.02;
1014 		fapproach_p(&p->color.a, 1, 0.025);
1015 	}
1016 
1017 	p->pos += p->args[1];
1018 	p->angle = carg(p->args[1]);
1019 
1020 	return ACTION_NONE;
1021 }
1022 
kdanmaku_slave(Enemy * e,int t)1023 static int kdanmaku_slave(Enemy *e, int t) {
1024 	float re;
1025 
1026 	if(t < 0)
1027 		return 1;
1028 
1029 	if(!e->args[1])
1030 		e->pos += e->args[0]*t;
1031 	else
1032 		e->pos += 5.0*I;
1033 
1034 	if(creal(e->pos) <= 0)
1035 		e->pos = I*cimag(e->pos);
1036 	if(creal(e->pos) >= VIEWPORT_W)
1037 		e->pos = VIEWPORT_W + I*cimag(e->pos);
1038 
1039 	re = creal(e->pos);
1040 
1041 	if(re <= 0 || re >= VIEWPORT_W)
1042 		e->args[1] = 1;
1043 
1044 	if(cimag(e->pos) >= VIEWPORT_H)
1045 		return ACTION_DESTROY;
1046 
1047 	if(e->args[2] && e->args[1]) {
1048 		int i, n = 3+max(D_Normal,global.diff);
1049 		float speed = 1.5+0.1*global.diff;
1050 
1051 		for(i = 0; i < n; i++) {
1052 			cmplx p = VIEWPORT_W/(float)n*(i+psin(t*t*i*i+t*t)) + I*cimag(e->pos);
1053 			if(cabs(p-global.plr.pos) > 60) {
1054 				PROJECTILE(
1055 					.proto = pp_thickrice,
1056 					.pos = p,
1057 					.color = RGBA(1.0, 0.5, 0.5, 0.0),
1058 					.rule = kdanmaku_proj,
1059 					.args = { 160, speed*0.5*cexp(2.0*I*M_PI*sin(245*t+i*i*3501)) },
1060 					.flags = PFLAG_NOSPAWNFLARE,
1061 				);
1062 
1063 				if(frand() < 0.5) {
1064 					vapor_particle(p, RGBA(0.5, 0.125 * frand(), 0.125 * frand(), 0.1));
1065 				}
1066 			}
1067 		}
1068 		play_sound_ex("redirect", 3, false);
1069 	}
1070 
1071 	return 1;
1072 }
1073 
kurumi_danmaku(Boss * b,int time)1074 void kurumi_danmaku(Boss *b, int time) {
1075 	int t = time % 400;
1076 	TIMER(&t);
1077 
1078 	if(time == EVENT_DEATH)
1079 		enemy_kill_all(&global.enemies);
1080 	if(time < 0)
1081 		return;
1082 
1083 	GO_TO(b, BOSS_DEFAULT_GO_POS, 0.04)
1084 
1085 	AT(260) {
1086 		aniplayer_queue(&b->ani,"muda",4);
1087 		aniplayer_queue(&b->ani,"main",0);
1088 	}
1089 
1090 	AT(50) {
1091 		play_sound("laser1");
1092 		create_lasercurve2c(b->pos, 50, 100, RGBA(1.0, 0.8, 0.8, 0.0), las_accel, 0, 0.2*cexp(I*carg(-b->pos)));
1093 		create_lasercurve2c(b->pos, 50, 100, RGBA(1.0, 0.8, 0.8, 0.0), las_accel, 0, 0.2*cexp(I*carg(VIEWPORT_W-b->pos)));
1094 		create_enemy3c(b->pos, ENEMY_IMMUNE, KurumiAniWallSlave, kdanmaku_slave, 0.2*cexp(I*carg(-b->pos)), 0, 1);
1095 		create_enemy3c(b->pos, ENEMY_IMMUNE, KurumiAniWallSlave, kdanmaku_slave, 0.2*cexp(I*carg(VIEWPORT_W-b->pos)), 0, 0);
1096 	}
1097 }
1098 
kurumi_extra_shield_pos(Enemy * e,int time)1099 static void kurumi_extra_shield_pos(Enemy *e, int time) {
1100 	double dst = 75 + 100 * max((60 - time) / 60.0, 0);
1101 	double spd = cimag(e->args[0]) * min(time / 120.0, 1);
1102 	e->args[0] += spd;
1103 	e->pos = global.boss->pos + dst * cexp(I*creal(e->args[0]));
1104 }
1105 
kurumi_extra_shield_expire(Enemy * e,int time)1106 static bool kurumi_extra_shield_expire(Enemy *e, int time) {
1107 	if(time > creal(e->args[1])) {
1108 		e->hp = 0;
1109 		return true;
1110 	}
1111 
1112 	return false;
1113 }
1114 
kurumi_extra_dead_shield_proj(Projectile * p,int time)1115 static int kurumi_extra_dead_shield_proj(Projectile *p, int time) {
1116 	if(time < 0) {
1117 		return ACTION_ACK;
1118 	}
1119 
1120 	p->color = *color_lerp(
1121 		RGBA(2.0, 0.0, 0.0, 0.0),
1122 		RGBA(0.2, 0.1, 0.5, 0.0),
1123 	min(time / 60.0f, 1.0f));
1124 
1125 	return asymptotic(p, time);
1126 }
1127 
kurumi_extra_dead_shield(Enemy * e,int time)1128 static int kurumi_extra_dead_shield(Enemy *e, int time) {
1129 	if(time < 0) {
1130 		return 1;
1131 	}
1132 
1133 	if(!(time % 6)) {
1134 		// cmplx dir = cexp(I*(M_PI * 0.5 * nfrand() + carg(global.plr.pos - e->pos)));
1135 		// cmplx dir = cexp(I*(carg(global.plr.pos - e->pos)));
1136 		cmplx dir = cexp(I*creal(e->args[0]));
1137 		PROJECTILE(
1138 			.proto = pp_rice,
1139 			.pos = e->pos,
1140 			.rule = kurumi_extra_dead_shield_proj,
1141 			.args = { 2*dir, 10 }
1142 		);
1143 		play_sound("shot1");
1144 	}
1145 
1146 	time += cimag(e->args[1]);
1147 
1148 	kurumi_extra_shield_pos(e, time);
1149 
1150 	if(kurumi_extra_shield_expire(e, time)) {
1151 		int cnt = 10;
1152 		for(int i = 0; i < cnt; ++i) {
1153 			cmplx dir = cexp(I*M_PI*2*i/(double)cnt);
1154 			tsrand_fill(2);
1155 			PROJECTILE(
1156 				.proto = pp_ball,
1157 				.pos = e->pos,
1158 				.rule = kurumi_extra_dead_shield_proj,
1159 				.args = { 1.5 * (1 + afrand(0)) * dir, 4 + anfrand(1) },
1160 			);
1161 		}
1162 
1163 		play_sound("boom");
1164 	}
1165 
1166 	return 1;
1167 }
1168 
kurumi_extra_shield(Enemy * e,int time)1169 static int kurumi_extra_shield(Enemy *e, int time) {
1170 	if(time == EVENT_DEATH) {
1171 		if(global.boss && !(global.gameover > 0) && !boss_is_dying(global.boss) && e->args[2] == 0) {
1172 			create_enemy2c(e->pos, ENEMY_IMMUNE, KurumiSlave, kurumi_extra_dead_shield, e->args[0], e->args[1]);
1173 		}
1174 		return 1;
1175 	}
1176 
1177 	if(time < 0) {
1178 		return 1;
1179 	}
1180 
1181 	e->args[1] = creal(e->args[1]) + time*I;
1182 
1183 	kurumi_extra_shield_pos(e, time);
1184 	kurumi_extra_shield_expire(e, time);
1185 
1186 	return 1;
1187 }
1188 
kurumi_extra_bigfairy1(Enemy * e,int time)1189 static int kurumi_extra_bigfairy1(Enemy *e, int time) {
1190 	if(time < 0) {
1191 		return 1;
1192 	}
1193 	TIMER(&time);
1194 
1195 	int escapetime = 400+4000*(global.diff == D_Lunatic);
1196 	if(time < escapetime) {
1197 		GO_TO(e, e->args[0], 0.02);
1198 	} else  {
1199 		GO_TO(e, e->args[0]-I*VIEWPORT_H,0.01)
1200 	}
1201 
1202 	FROM_TO(50,escapetime,60) {
1203 		int count = 5;
1204 		cmplx phase = cexp(I*2*M_PI*frand());
1205 		for(int i = 0; i < count; i++) {
1206 			cmplx arg = cexp(I*2*M_PI*i/count);
1207 			if(global.diff == D_Lunatic)
1208 				arg *= phase;
1209 			create_lasercurve2c(e->pos, 20, 200, RGBA(1.0, 0.3, 0.7, 0.0), las_accel, arg, 0.1*arg);
1210 			PROJECTILE(
1211 				.proto = pp_bullet,
1212 				.pos = e->pos,
1213 				.color = RGB(1.0, 0.3, 0.7),
1214 				.rule = accelerated,
1215 				.args = { arg, 0.1*arg },
1216 			);
1217 		}
1218 
1219 		play_sound("laser1");
1220 	}
1221 
1222 	return 1;
1223 }
1224 
kurumi_extra_drainer_draw(Projectile * p,int time)1225 static void kurumi_extra_drainer_draw(Projectile *p, int time) {
1226 	cmplx org = p->pos;
1227 	cmplx targ = p->args[1];
1228 	double a = 0.5 * creal(p->args[2]);
1229 	Texture *tex = r_texture_get("part/sinewave");
1230 
1231 	r_shader_standard();
1232 
1233 	r_color4(1.0 * a, 0.5 * a, 0.5 * a, 0);
1234 	loop_tex_line_p(org, targ, 16, time * 0.01, tex);
1235 
1236 	r_color4(0.4 * a, 0.2 * a, 0.2 * a, 0);
1237 	loop_tex_line_p(org, targ, 18, time * 0.0043, tex);
1238 
1239 	r_color4(0.5 * a, 0.2 * a, 0.5 * a, 0);
1240 	loop_tex_line_p(org, targ, 24, time * 0.003, tex);
1241 }
1242 
kurumi_extra_drainer(Projectile * p,int time)1243 static int kurumi_extra_drainer(Projectile *p, int time) {
1244 	if(time == EVENT_DEATH) {
1245 		free_ref(p->args[0]);
1246 		return ACTION_ACK;
1247 	}
1248 
1249 	if(time < 0) {
1250 		return ACTION_ACK;
1251 	}
1252 
1253 	Enemy *e = REF(p->args[0]);
1254 	p->pos = global.boss->pos;
1255 
1256 	if(e) {
1257 		p->args[1] = e->pos;
1258 		p->args[2] = approach(p->args[2], 1, 0.5);
1259 
1260 		if(time > 40 && e->hp > 0) {
1261 			// TODO: maybe add a special sound for this?
1262 
1263 			float drain = min(4, e->hp);
1264 			ent_damage(&e->ent, &(DamageInfo) { .amount = drain });
1265 			global.boss->current->hp = min(global.boss->current->maxhp, global.boss->current->hp + drain * 2);
1266 		}
1267 	} else {
1268 		p->args[2] = approach(p->args[2], 0, 0.5);
1269 		if(!creal(p->args[2])) {
1270 			return ACTION_DESTROY;
1271 		}
1272 	}
1273 
1274 	return ACTION_NONE;
1275 }
1276 
kurumi_extra_create_drainer(Enemy * e)1277 static void kurumi_extra_create_drainer(Enemy *e) {
1278 	PROJECTILE(
1279 		.size = 1+I,
1280 		.pos = e->pos,
1281 		.rule = kurumi_extra_drainer,
1282 		.draw_rule = kurumi_extra_drainer_draw,
1283 		.args = { add_ref(e) },
1284 		.shader = "sprite_default",
1285 		.flags = PFLAG_NOCLEAR | PFLAG_NOCOLLISION,
1286 	);
1287 }
1288 
kurumi_extra_swirl_visual(Enemy * e,int time,bool render)1289 static void kurumi_extra_swirl_visual(Enemy *e, int time, bool render) {
1290 	if(!render) {
1291 		// why the hell was this here?
1292 		// Swirl(e, time, render);
1293 		return;
1294 	}
1295 
1296 	// FIXME: blend
1297 	r_blend(BLEND_SUB);
1298 	Swirl(e, time, render);
1299 	r_blend(BLEND_PREMUL_ALPHA);
1300 }
1301 
kurumi_extra_fairy_visual(Enemy * e,int time,bool render)1302 static void kurumi_extra_fairy_visual(Enemy *e, int time, bool render) {
1303 	if(!render) {
1304 		Fairy(e, time, render);
1305 		return;
1306 	}
1307 
1308 	// r_blend(BLEND_ADD);
1309 	r_shader("sprite_negative");
1310 	Fairy(e, time, render);
1311 	r_shader("sprite_default");
1312 	// r_blend(BLEND_ALPHA);
1313 }
1314 
kurumi_extra_bigfairy_visual(Enemy * e,int time,bool render)1315 static void kurumi_extra_bigfairy_visual(Enemy *e, int time, bool render) {
1316 	if(!render) {
1317 		BigFairy(e, time, render);
1318 		return;
1319 	}
1320 
1321 	// r_blend(BLEND_ADD);
1322 	r_shader("sprite_negative");
1323 	BigFairy(e, time, render);
1324 	r_shader("sprite_default");
1325 	// r_blend(BLEND_ALPHA);
1326 }
1327 
kurumi_extra_fairy(Enemy * e,int t)1328 static int kurumi_extra_fairy(Enemy *e, int t) {
1329 	TIMER(&t);
1330 	AT(EVENT_KILLED) {
1331 		spawn_items(e->pos, ITEM_POINTS, 1);
1332 		return 1;
1333 	}
1334 
1335 	if(e->hp == ENEMY_IMMUNE && t > 50)
1336 		e->hp = 500;
1337 
1338 	if(creal(e->args[0]-e->pos) != 0)
1339 		e->moving = true;
1340 	e->dir = creal(e->args[0]-e->pos) < 0;
1341 
1342 	FROM_TO(0,90,1) {
1343 		GO_TO(e,e->args[0],0.1)
1344 	}
1345 
1346 	/*FROM_TO(100, 200, 22-global.diff*3) {
1347 		PROJECTILE("ball", e->pos, RGB(1, 0.3, 0.5), asymptotic, 2*cexp(I*M_PI*2*frand()), 3);
1348 	}*/
1349 
1350 	int attacktime = creal(e->args[1]);
1351 	int flytime = cimag(e->args[1]);
1352 	FROM_TO_SND("shot1_loop", attacktime-20,attacktime+20,20) {
1353 		cmplx vel = cexp(I*frand()*2*M_PI)*(2+0.1*(global.diff-D_Easy));
1354 		if(e->args[2] == 0) { // attack type
1355 			int corners = 5;
1356 			double len = 50;
1357 			int count = 5;
1358 			for(int i = 0; i < corners; i++) {
1359 				for(int j = 0; j < count; j++) {
1360 					cmplx pos = len/2/tan(2*M_PI/corners)*I+(j/(double)count-0.5)*len;
1361 					pos *= cexp(I*2*M_PI/corners*i);
1362 					PROJECTILE(
1363 						.proto = pp_flea,
1364 						.pos = e->pos+pos,
1365 						.color = RGB(1, 0.3, 0.5),
1366 						.rule = linear,
1367 						.args = { vel+0.1*I*pos/cabs(pos) }
1368 					);
1369 				}
1370 			}
1371 		} else {
1372 			int count = 30;
1373 			double rad = 20;
1374 			for(int j = 0; j < count; j++) {
1375 				double x = (j/(double)count-0.5)*2*M_PI;
1376 				cmplx pos = 0.5*cos(x)+sin(2*x) + (0.5*sin(x)+cos(2*x))*I;
1377 				pos*=vel/cabs(vel);
1378 				PROJECTILE(
1379 					.proto = pp_flea,
1380 					.pos = e->pos+rad*pos,
1381 					.color = RGB(0.5, 0.3, 1),
1382 					.rule = linear,
1383 					.args = { vel+0.1*pos },
1384 				);
1385 			}
1386 		}
1387 	}
1388 	AT(attacktime) {
1389 		e->args[0] = global.plr.pos-e->pos;
1390 		kurumi_extra_create_drainer(e);
1391 		play_sound("redirect");
1392 	}
1393 	FROM_TO(attacktime,attacktime+flytime,1) {
1394 		e->pos += e->args[0]/flytime;
1395 	}
1396 
1397 	FROM_TO(attacktime,attacktime+flytime,20-global.diff*3) {
1398 		if(global.diff>D_Easy) {
1399 			Color *clr = RGBA_MUL_ALPHA(0.1 + 0.07 * _i, 0.3, 1 - 0.05 * _i, 0.8);
1400 			clr->a = 0;
1401 
1402 			PROJECTILE(
1403 				.proto = pp_ball,
1404 				.pos = e->pos,
1405 				.color = clr,
1406 				.timeout = 50,
1407 			);
1408 		}
1409 	}
1410 	if(t > attacktime + flytime + 20 && global.boss) {
1411 		GO_TO(e,global.boss->pos,0.04)
1412 	}
1413 
1414 	return 1;
1415 }
1416 
kurumi_extra(Boss * b,int time)1417 void kurumi_extra(Boss *b, int time) {
1418 	int length = 400;
1419 	int t = time % length;
1420 	int direction = (time/length)%2;
1421 
1422 	int castlimit = b->current->maxhp * 0.05;
1423 	int shieldlimit = b->current->maxhp * 0.1;
1424 
1425 	TIMER(&t);
1426 
1427 	if(time == EVENT_DEATH) {
1428 		enemy_kill_all(&global.enemies);
1429 		return;
1430 	}
1431 
1432 	if(time < 120)
1433 		GO_TO(b, VIEWPORT_W * 0.5 + VIEWPORT_H * 0.28 * I, 0.01)
1434 
1435 	if(t == 0 && b->current->hp >= shieldlimit) {
1436 		int cnt = 12;
1437 		for(int i = 0; i < cnt; ++i) {
1438 			double a = M_PI * 2 * i / (double)cnt;
1439 			int hp = 500;
1440 			create_enemy2c(b->pos, hp, kurumi_extra_swirl_visual, kurumi_extra_shield, a + 0.05*I, 800);
1441 			create_enemy2c(b->pos, hp, kurumi_extra_swirl_visual, kurumi_extra_shield, a - 0.05*I, 800);
1442 		}
1443 	}
1444 
1445 	AT(90) {
1446 		int cnt = 20;
1447 		for(int i = 0; i < cnt; i++) {
1448 			b->current->hp -= 600;
1449 			if(b->current->hp < castlimit)
1450 				b->current->hp = castlimit;
1451 			tsrand_fill(2);
1452 			cmplx pos = VIEWPORT_W/2*afrand(0)+I*afrand(1)*VIEWPORT_H*2/3;
1453 			if(direction)
1454 				pos = VIEWPORT_W-creal(pos)+I*cimag(pos);
1455 			// immune so they don’t get killed while they are still offscreen.
1456 			create_enemy3c(pos-300*(1-2*direction),ENEMY_IMMUNE,kurumi_extra_fairy_visual,kurumi_extra_fairy,pos,100+20*i+100*(1.1-0.05*global.diff)*I,direction);
1457 		}
1458 
1459 		// XXX: maybe add a special sound for this?
1460 		play_sound("shot_special1");
1461 	}
1462 
1463 	cmplx sidepos = VIEWPORT_W * (0.5+0.3*(1-2*direction)) + VIEWPORT_H * 0.28 * I;
1464 	FROM_TO(90,120,1) {
1465 		GO_TO(b, sidepos,0.1)
1466 	}
1467 
1468 	FROM_TO(190,200,1) {
1469 		GO_TO(b, sidepos+30*I,0.1)
1470 	}
1471 
1472 	AT(190){
1473 		aniplayer_queue_frames(&b->ani, "muda", 110);
1474 		aniplayer_queue(&b->ani, "main", 0);
1475 	}
1476 
1477 	FROM_TO(300,400,1) {
1478 		GO_TO(b,VIEWPORT_W * 0.5 + VIEWPORT_H * 0.28 * I,0.1)
1479 	}
1480 
1481 	if(global.diff >= D_Hard) {
1482 		AT(300) {
1483 			double ofs = VIEWPORT_W * 0.5;
1484 			cmplx pos = 0.5 * VIEWPORT_W + I * (VIEWPORT_H - 100);
1485 			cmplx targ = 0.5 *VIEWPORT_W + VIEWPORT_H * 0.3 * I;
1486 			create_enemy1c(pos + ofs, 3300, kurumi_extra_bigfairy_visual, kurumi_extra_bigfairy1, targ + 0.8*ofs);
1487 			create_enemy1c(pos - ofs, 3300, kurumi_extra_bigfairy_visual, kurumi_extra_bigfairy1, targ - 0.8*ofs);
1488 		}
1489 	}
1490 	if((t == length-20 && global.diff == D_Easy)|| b->current->hp < shieldlimit) {
1491 		for(Enemy *e = global.enemies.first; e; e = e->next) {
1492 			if(e->logic_rule == kurumi_extra_shield) {
1493 				e->args[2] = 1; // discharge extra shield
1494 				e->hp = 0;
1495 				continue;
1496 			}
1497 		}
1498 	}
1499 }
1500 
1501 
create_kurumi(void)1502 static Boss *create_kurumi(void) {
1503 	Boss* b = stage4_spawn_kurumi(-400.0*I);
1504 
1505 	boss_add_attack(b, AT_Move, "Introduction", 4, 0, kurumi_boss_intro, NULL);
1506 	boss_add_attack(b, AT_Normal, "Sin Breaker", 25, 33000, kurumi_sbreaker, NULL);
1507 	if(global.diff < D_Hard) {
1508 		boss_add_attack_from_info(b, &stage4_spells.boss.animate_wall, false);
1509 	} else {
1510 		boss_add_attack_from_info(b, &stage4_spells.boss.demon_wall, false);
1511 	}
1512 	boss_add_attack(b, AT_Normal, "Cold Breaker", 25, 36000, kurumi_breaker, NULL);
1513 	boss_add_attack_from_info(b, &stage4_spells.boss.bloody_danmaku, false);
1514 	boss_add_attack_from_info(b, &stage4_spells.boss.blow_the_walls, false);
1515 	boss_add_attack_from_info(b, &stage4_spells.extra.vlads_army, false);
1516 	boss_start_attack(b, b->attacks);
1517 
1518 	return b;
1519 }
1520 
scythe_post_mid(Enemy * e,int t)1521 static int scythe_post_mid(Enemy *e, int t) {
1522 	TIMER(&t);
1523 
1524 	int fleetime = creal(e->args[3]);
1525 
1526 	if(t == EVENT_DEATH) {
1527 		if(fleetime >= 300) {
1528 			spawn_items(e->pos, ITEM_LIFE, 1);
1529 		}
1530 
1531 		return 1;
1532 	}
1533 
1534 	if(t < 0) {
1535 		return 1;
1536 	}
1537 
1538 	AT(fleetime) {
1539 		return ACTION_DESTROY;
1540 	}
1541 
1542 	double scale = min(1.0, t / 60.0) * (1.0 - clamp((t - (fleetime - 60)) / 60.0, 0.0, 1.0));
1543 	double alpha = scale * scale;
1544 	double spin = (0.2 + 0.2 * (1.0 - alpha)) * 1.5;
1545 
1546 	cmplx opos = VIEWPORT_W/2+160*I;
1547 	double targ = (t-300) * (0.5 + psin(t/300.0));
1548 	double w = min(0.15, 0.0001*targ);
1549 
1550 	cmplx pofs = 150*cos(w*targ+M_PI/2.0) + I*80*sin(2*w*targ);
1551 	pofs += ((VIEWPORT_W/2+VIEWPORT_H/2*I - opos) * (global.diff - D_Easy)) / (D_Lunatic - D_Easy);
1552 
1553 	e->pos = opos + pofs * (1.0 - clamp((t - (fleetime - 120)) / 60.0, 0.0, 1.0)) * smooth(smooth(scale));
1554 	e->args[2] = 0.5 * scale + (1.0 - alpha) * I;
1555 	e->args[1] = creal(e->args[1]) + spin * I;
1556 
1557 	FROM_TO(90, fleetime - 120, 1) {
1558 		cmplx shotorg = e->pos+80*cexp(I*creal(e->args[1]));
1559 		cmplx shotdir = cexp(I*creal(e->args[1]));
1560 
1561 		struct projentry { ProjPrototype *proj; char *snd; } projs[] = {
1562 			{ pp_ball,       "shot1"},
1563 			{ pp_bigball,    "shot1"},
1564 			{ pp_soul,       "shot_special1"},
1565 			{ pp_bigball,    "shot1"},
1566 		};
1567 
1568 		struct projentry *pe = &projs[_i % (sizeof(projs)/sizeof(struct projentry))];
1569 
1570 		double ca = creal(e->args[1]) + _i/60.0;
1571 		Color *c = RGB(cos(ca), sin(ca), cos(ca+2.1));
1572 
1573 		play_sound_ex(pe->snd, 3, true);
1574 
1575 		PROJECTILE(
1576 			.proto = pe->proj,
1577 			.pos = shotorg,
1578 			.color = c,
1579 			.rule = asymptotic,
1580 			.args = {
1581 				(1.2-0.1*global.diff)*shotdir,
1582 				5 * sin(t/150.0)
1583 			},
1584 		);
1585 	}
1586 
1587 	FROM_TO(fleetime - 120, fleetime, 1) {
1588 		stage_clear_hazards(CLEAR_HAZARDS_ALL);
1589 	}
1590 
1591 	scythe_common(e, t);
1592 	return 1;
1593 }
1594 
stage4_events(void)1595 void stage4_events(void) {
1596 	TIMER(&global.timer);
1597 
1598 	AT(0) {
1599 		stage_start_bgm("stage4");
1600 		stage4_skip(env_get("STAGE4_TEST", 0));
1601 		stage_set_voltage_thresholds(170, 340, 660, 1040);
1602 	}
1603 
1604 	AT(70) {
1605 		create_enemy1c(VIEWPORT_H/4*3*I, 3000, BigFairy, stage4_splasher, 3-4.0*I);
1606 		create_enemy1c(VIEWPORT_W + VIEWPORT_H/4*3*I, 3000, BigFairy, stage4_splasher, -3-4.0*I);
1607 	}
1608 
1609 	FROM_TO(300, 450, 10) {
1610 		float span = VIEWPORT_W * 0.5;
1611 		float phase = 2*_i/M_PI;
1612 
1613 		if(_i & 1) {
1614 			phase *= -1;
1615 		}
1616 
1617 		create_enemy1c((VIEWPORT_W + span * sin(phase)) * 0.5 - 32*I, 200, Fairy, stage4_fodder, 2.0*I*cexp(I*phase*0.1));
1618 	}
1619 
1620 	FROM_TO(500, 550, 10) {
1621 		int d = _i&1;
1622 		create_enemy1c(VIEWPORT_W*d, 1000, Fairy, stage4_partcircle, 2*cexp(I*M_PI/2.0*(0.2+0.6*frand()+d)));
1623 	}
1624 
1625 	FROM_TO(600, 1400, 100) {
1626 		create_enemy3c(VIEWPORT_W/4.0 + VIEWPORT_W/2.0*(_i&1), 3000, BigFairy, stage4_cardbuster, VIEWPORT_W/6.0 + VIEWPORT_W/3.0*2*(_i&1)+100.0*I,
1627 					VIEWPORT_W/4.0 + VIEWPORT_W/2.0*((_i+1)&1)+300.0*I, VIEWPORT_W/2.0+VIEWPORT_H*I+200.0*I);
1628 	}
1629 
1630 	AT(1800) {
1631 		create_enemy1c(VIEWPORT_H/2.0*I, 2000, Swirl, stage4_backfire, 0.3);
1632 		create_enemy1c(VIEWPORT_W+VIEWPORT_H/2.0*I, 2000, Swirl, stage4_backfire, -0.5);
1633 	}
1634 
1635 	FROM_TO(2060, 2600, 15) {
1636 		float span = 300;
1637 		float phase = 2*_i/M_PI;
1638 
1639 		if(_i & 1) {
1640 			phase *= -1;
1641 		}
1642 
1643 		create_enemy1c(I * span * psin(phase) - 24, 200, Fairy, stage4_fodder, 2.0*cexp(I*phase*0.005));
1644 	}
1645 
1646 	FROM_TO(2000, 2400, 200)
1647 		create_enemy1c(VIEWPORT_W/2+200-400*frand(), 1000, BigFairy, stage4_bigcircle, 2.0*I);
1648 
1649 	FROM_TO(2600, 3000, 10)
1650 		create_enemy1c(20.0*I+VIEWPORT_H/3*I*frand()+VIEWPORT_W, 100, Swirl, stage4_explosive, -3);
1651 
1652 	AT(3200)
1653 		global.boss = create_kurumi_mid();
1654 
1655 	int midboss_time = STAGE4_MIDBOSS_MUSIC_TIME;
1656 
1657 	AT(3201) {
1658 		if(global.boss) {
1659 			global.timer += min(midboss_time, global.frames - global.boss->birthtime) - 1;
1660 		}
1661 	}
1662 
1663 	FROM_TO(3201, 3201 + midboss_time - 1, 1) {
1664 		if(!global.enemies.first) {
1665 			create_enemy4c(VIEWPORT_W/2+160.0*I, ENEMY_IMMUNE, Scythe, scythe_post_mid, 0, 1+0.2*I, 0+1*I, 3201 + midboss_time - global.timer);
1666 		}
1667 	}
1668 
1669 	FROM_TO(3201 + midboss_time, 3501 + midboss_time, 10) {
1670 		float span = 120;
1671 		float phase = 2*_i/M_PI;
1672 		create_enemy1c(VIEWPORT_W*(_i&1)+span*I*psin(phase), 200, Fairy, stage4_fodder, 2-4*(_i&1)+1.0*I);
1673 	}
1674 
1675 	FROM_TO(3350 + midboss_time, 4000 + midboss_time, 60) {
1676 		int d = _i&1;
1677 		create_enemy1c(VIEWPORT_W*d, 1000, Fairy, stage4_partcircle, 2*cexp(I*M_PI/2.0*(0.2+0.6*frand()+d)));
1678 	}
1679 
1680 	FROM_TO(3550 + midboss_time, 4200 + midboss_time, 200) {
1681 		create_enemy3c(VIEWPORT_W/4.0 + VIEWPORT_W/2.0*(_i&1), 3000, BigFairy, stage4_cardbuster, VIEWPORT_W/2.+VIEWPORT_W*0.4*(1-2*(_i&1))+100.0*I,
1682 					VIEWPORT_W/4.0+VIEWPORT_W/2.0*((_i+1)&1)+300.0*I, VIEWPORT_W/2.0-200.0*I);
1683 	}
1684 
1685 	AT(3800 + midboss_time)
1686 		create_enemy1c(VIEWPORT_W/2, 9000, BigFairy, stage4_supercard, 4.0*I);
1687 
1688 	FROM_TO(4300 + midboss_time, 4600 + midboss_time, 95-10*global.diff)
1689 		create_enemy1c(VIEWPORT_W*(_i&1)+100*I, 200, Swirl, stage4_backfire, frand()*(1-2*(_i&1)));
1690 
1691 	FROM_TO(4800 + midboss_time, 5200 + midboss_time, 10)
1692 		create_enemy1c(20.0*I+I*VIEWPORT_H/3*frand()+VIEWPORT_W*(_i&1), 100, Swirl, stage4_explosive, (1-2*(_i&1))*3+I);
1693 
1694 	AT(5300 + midboss_time) {
1695 		stage_unlock_bgm("stage4");
1696 		global.boss = create_kurumi();
1697 	}
1698 
1699 	AT(5400 + midboss_time) {
1700 		stage_unlock_bgm("stage4boss");
1701 		global.dialog = stage4_dialog_post_boss();
1702 	}
1703 
1704 	AT(5405 + midboss_time) {
1705 		stage_finish(GAMEOVER_SCORESCREEN);
1706 	}
1707 }
1708