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