1
2 #include "../stdai.h"
3 #include "sidekicks.fdh"
4
5 #define SUE_BASE 20
6 #define SUE_PREPARE_ATTACK 30
7 #define SUE_SOMERSAULT 40
8 #define SUE_DASH 50
9 #define SUE_SOMERSAULT_HIT 60
10
11 // both sue and misery
12 #define SIDEKICK_CORE_DEFEATED 99 // core defeated (script-triggered)
13 #define SIDEKICK_DEFEATED 100 // sidekick defeated
14 #define SIDEKICK_CORE_DEFEATED_2 110
15
INITFUNC(AIRoutines)16 INITFUNC(AIRoutines)
17 {
18 ONTICK(OBJ_SUE_FRENZIED, ai_sue_frenzied);
19 ONTICK(OBJ_MISERY_FRENZIED, ai_misery_frenzied);
20
21 ONTICK(OBJ_MISERY_CRITTER, ai_misery_critter);
22 ONTICK(OBJ_MISERY_BAT, ai_misery_bat);
23 ONTICK(OBJ_MISERY_MISSILE, ai_misery_missile);
24 }
25
26 bool sue_being_hurt;
27 bool sue_was_killed;
28
29 /*
30 void c------------------------------() {}
31 */
32
ai_misery_frenzied(Object * o)33 void ai_misery_frenzied(Object *o)
34 {
35 //AIDEBUG;
36 sidekick_run_defeated(o, 600);
37
38 switch(o->state)
39 {
40 case 0:
41 {
42 o->state = 1;
43 sue_being_hurt = sue_was_killed = false;
44
45 o->savedhp = o->hp;
46 o->nxflags |= NXFLAG_SLOW_X_WHEN_HURT;
47
48 sound(SND_TELEPORT);
49 o->timer = 1;
50 }
51 case 1: // transforming
52 {
53 o->timer++;
54
55 if (o->timer == 2)
56 { // frenzied
57 o->sprite = SPR_MISERY_FRENZIED;
58 o->frame = 9;
59 o->x -= 0x1000;
60 o->y -= 0x2000;
61 }
62
63 if (o->timer == 4)
64 { // normal
65 o->timer = 0;
66
67 o->sprite = SPR_MISERY;
68 o->frame = 2;
69 o->x += 0x1000;
70 o->y += 0x2000;
71 }
72
73 if (++o->timer2 >= 50)
74 {
75 o->timer2 = 0;
76 o->state = 2;
77 }
78 }
79 break;
80
81 case 10: // hold at "being transformed" frame
82 {
83 o->state = 11;
84 o->frame = 9;
85 }
86 break;
87
88 case 20: // fight begin / base state
89 {
90 o->state = 21;
91 o->timer = 0;
92 o->frame = 0;
93 o->animtimer = 0;
94 }
95 case 21:
96 {
97 o->xinertia *= 7; o->xinertia /= 8;
98 o->yinertia *= 7; o->yinertia /= 8;
99
100 ANIMATE(20, 0, 1);
101
102 if (++o->timer > 100)
103 o->state = 30;
104
105 FACEPLAYER;
106 }
107 break;
108
109 case 30:
110 {
111 o->state = 31;
112 o->timer = 0;
113 o->frame = 2;
114 o->savedhp = o->hp;
115 }
116 case 31:
117 {
118 ANIMATE(1, 2, 3);
119
120 if (o->blockd) o->yinertia = -0x200;
121
122 int core_x = game.stageboss.object ? game.stageboss.object->x : 0;
123
124 o->xinertia += (o->x > core_x) ? -0x20 : 0x20;
125 o->yinertia += (o->y > player->y) ? -0x10 : 0x10;
126 LIMITX(0x200);
127 LIMITY(0x200);
128
129 FACEPLAYER;
130
131 if (++o->timer > 150)
132 {
133 // she attacks with normal critters if you attack either her or Sue.
134 if ((o->savedhp - o->hp) > 20 || sue_being_hurt)
135 {
136 sue_being_hurt = false;
137 o->state = 40;
138 }
139 }
140
141 // she attacks periodically with fishy missiles if you killed Sue.
142 if (o->timer > 250 && sue_was_killed)
143 o->state = 50;
144 }
145 break;
146
147 case 40: // spawn bats/critters
148 {
149 o->state = 41;
150 o->timer = 0;
151 o->xinertia = 0;
152 o->yinertia = 0;
153 FACEPLAYER;
154 sound(SND_CHARGE_GUN);
155
156 // if you are below the 2nd little platform on the left,
157 // she spawns critters, else bats.
158 o->timer3 = (player->y >= MAPY(10)) ? OBJ_MISERY_CRITTER : OBJ_MISERY_BAT;
159 }
160 case 41:
161 {
162 o->timer++;
163 o->frame = (o->timer & 2) ? 4 : 5;
164
165 if ((o->timer % 6) == 1)
166 {
167 int x, y;
168
169 if (o->timer3 == OBJ_MISERY_CRITTER)
170 {
171 x = o->x + (random(-64, 64) << CSF);
172 y = o->y + (random(-32, 32) << CSF);
173 }
174 else
175 {
176 x = o->x + (random(-32, 32) << CSF);
177 y = o->y + (random(-64, 64) << CSF);
178 }
179
180 if (x < MAPX(2)) x = MAPX(2);
181 if (x > MAPX(map.xsize - 3)) x = MAPX(map.xsize - 3);
182
183 if (y < MAPY(2)) y = MAPY(2);
184 if (y > MAPY(map.ysize - 3)) y = MAPY(map.ysize - 3);
185
186 sound(SND_EM_FIRE);
187 CreateObject(x, y, o->timer3)->invisible = true;
188 }
189
190 if (o->timer > 50)
191 {
192 o->state = 42;
193 o->timer = 0;
194 FACEPLAYER;
195 }
196 }
197 break;
198
199 case 42:
200 {
201 o->frame = 6;
202
203 if (++o->timer > 50)
204 {
205 o->yinertia = -0x200;
206 XMOVE(-0x200);
207
208 o->state = 30;
209 }
210 }
211 break;
212
213 case 50: // spawn fishy missiles
214 {
215 o->state = 51;
216 o->timer = 0;
217 o->xinertia = 0;
218 o->yinertia = 0;
219 FACEPLAYER;
220 sound(SND_CHARGE_GUN);
221 }
222 case 51:
223 {
224 o->timer++;
225 o->frame = (o->timer & 2) ? 4 : 5;
226
227 int rate = (player->equipmask & EQUIP_BOOSTER20) ? 10 : 24;
228
229 if ((o->timer % rate) == 1)
230 {
231 // pattern: booster=[0,1,3,1,2,0], no-booster=[0,0,0]:
232 int angindex = (o->timer / 6) & 3;
233 fm_spawn_missile(o, angindex);
234 }
235
236 if (++o->timer > 50)
237 {
238 o->state = 42;
239 o->timer = 0;
240 FACEPLAYER;
241 }
242 }
243 break;
244 }
245 }
246
247
248 // spawn a fishy missile in the given direction
fm_spawn_missile(Object * o,int angindex)249 static Object *fm_spawn_missile(Object *o, int angindex)
250 {
251 static const int ang_table_left[] = { 0xD8, 0xEC, 0x14, 0x28 };
252 static const int ang_table_right[] = { 0x58, 0x6C, 0x94, 0xA8 };
253
254 Object *shot = CreateObject(o->x, o->y, OBJ_MISERY_MISSILE);
255 sound(SND_EM_FIRE);
256
257 if (o->dir == LEFT)
258 {
259 shot->x += (10 << CSF);
260 shot->angle = ang_table_left[angindex];
261 }
262 else
263 {
264 shot->x -= (10 << CSF);
265 shot->angle = ang_table_right[angindex];
266 }
267
268 return shot;
269 }
270
271 /*
272 void c------------------------------() {}
273 */
274
ai_misery_critter(Object * o)275 void ai_misery_critter(Object *o)
276 {
277 switch(o->state)
278 {
279 case 0:
280 {
281 if (++o->timer > 16)
282 {
283 o->frame = 2;
284 o->invisible = false;
285 FACEPLAYER;
286
287 o->state = 10;
288 o->damage = 2;
289 o->flags |= FLAG_SHOOTABLE;
290 }
291 }
292 break;
293
294 case 10:
295 {
296 if (o->blockd && o->yinertia >= 0)
297 {
298 o->state = 11;
299 o->frame = 0;
300 o->timer = 0;
301 o->xinertia = 0;
302
303 FACEPLAYER;
304 }
305 }
306 break;
307
308 case 11:
309 {
310 if (++o->timer > 10)
311 {
312 if (++o->timer2 > 4)
313 o->state = 12;
314 else
315 o->state = 10;
316
317 sound(SND_ENEMY_JUMP);
318
319 o->yinertia = -0x600;
320 XMOVE(0x200);
321
322 o->frame = 2;
323 }
324 }
325 break;
326
327 case 12:
328 {
329 o->flags |= FLAG_IGNORE_SOLID;
330 if (o->y > MAPY(map.ysize))
331 {
332 o->Delete();
333 }
334 }
335 break;
336 }
337
338 if (o->state >= 10)
339 {
340 o->yinertia += 0x40;
341 if (o->yinertia > 0x5ff) o->yinertia = 0x5ff;
342 }
343 }
344
ai_misery_bat(Object * o)345 void ai_misery_bat(Object *o)
346 {
347 switch(o->state)
348 {
349 case 0:
350 {
351 if (++o->timer > 16)
352 {
353 o->frame = 2;
354 o->invisible = false;
355 FACEPLAYER;
356
357 o->state = 1;
358 o->damage = 2;
359 o->flags |= (FLAG_SHOOTABLE | FLAG_IGNORE_SOLID);
360
361 o->ymark = o->y;
362 o->yinertia = 0x400;
363 }
364 }
365 break;
366
367 case 1:
368 {
369 ANIMATE(2, 0, 2);
370
371 o->yinertia += (o->y < o->ymark) ? 0x40 : -0x40;
372 XACCEL(0x10);
373
374 if (o->x < 0 || o->x > MAPX(map.xsize) || \
375 o->y < 0 || o->y > MAPY(map.ysize))
376 {
377 o->Delete();
378 }
379 }
380 break;
381 }
382 }
383
ai_misery_missile(Object * o)384 void ai_misery_missile(Object *o)
385 {
386 // cut & pasted from ai_x_fishy_missile
387 vector_from_angle(o->angle, 0x400, &o->xinertia, &o->yinertia);
388 int desired_angle = GetAngle(o->x, o->y, player->x, player->y);
389
390 if (o->angle >= desired_angle)
391 {
392 if ((o->angle - desired_angle) < 128)
393 {
394 o->angle--;
395 }
396 else
397 {
398 o->angle++;
399 }
400 }
401 else
402 {
403 if ((o->angle - desired_angle) < 128)
404 {
405 o->angle++;
406 }
407 else
408 {
409 o->angle--;
410 }
411 }
412
413 // smoke trails
414 if (++o->timer2 > 2)
415 {
416 o->timer2 = 0;
417 Caret *c = effect(o->ActionPointX(), o->ActionPointY(), EFFECT_SMOKETRAIL_SLOW);
418 c->xinertia = -o->xinertia >> 2;
419 c->yinertia = -o->yinertia >> 2;
420 }
421
422 o->frame = (o->angle + 16) / 32;
423 if (o->frame > 7) o->frame = 7;
424 }
425
426 /*
427 void c------------------------------() {}
428 */
429
ai_sue_frenzied(Object * o)430 void ai_sue_frenzied(Object *o)
431 {
432 //AIDEBUG;
433 sidekick_run_defeated(o, 500);
434
435 switch(o->state)
436 {
437 case 0:
438 {
439 o->state = 1;
440 sue_being_hurt = sue_was_killed = false;
441
442 o->savedhp = o->hp;
443 o->nxflags |= NXFLAG_SLOW_X_WHEN_HURT;
444
445 sound(SND_TELEPORT);
446 o->timer = 1;
447 }
448 case 1: // transforming
449 {
450 o->timer++;
451
452 if (o->timer == 2)
453 { // frenzied sue
454 o->sprite = SPR_SUE_FRENZIED;
455 o->frame = 11;
456 o->x -= 0x1000;
457 o->y -= 0x1800;
458 }
459
460 if (o->timer == 4)
461 { // normal sue
462 o->timer = 0;
463
464 o->sprite = SPR_SUE;
465 o->frame = 12;
466 o->x += 0x1000;
467 o->y += 0x1800;
468 }
469
470 if (++o->timer2 >= 50)
471 {
472 KillObjectsOfType(OBJ_RED_CRYSTAL);
473 o->timer2 = 0;
474 o->state = 2;
475 }
476 }
477 break;
478
479 // fight begin/base state (script-triggered)
480 case SUE_BASE:
481 {
482 o->state++;
483 o->timer = 0;
484 o->frame = 0;
485 o->animtimer = 0;
486 o->damage = 0;
487
488 o->flags |= FLAG_SHOOTABLE;
489 o->flags &= ~FLAG_IGNORE_SOLID;
490 }
491 case SUE_BASE+1:
492 {
493 ANIMATE(20, 0, 1);
494 FACEPLAYER;
495
496 o->xinertia *= 7; o->xinertia /= 8;
497 o->yinertia *= 7; o->yinertia /= 8;
498
499 if ((o->savedhp - o->hp) > 50)
500 {
501 o->savedhp = o->hp;
502 sue_being_hurt = true; // trigger Misery to spawn monsters
503 }
504
505 if (++o->timer > 80)
506 o->state = SUE_PREPARE_ATTACK;
507 }
508 break;
509
510 // prepare to attack
511 case SUE_PREPARE_ATTACK:
512 {
513 o->state++;
514 o->timer = 0;
515 o->frame = 2;
516
517 o->xinertia = 0;
518 o->yinertia = 0;
519 }
520 case SUE_PREPARE_ATTACK+1:
521 {
522 if (++o->timer > 16)
523 {
524 o->state = (o->timer2 ^= 1) ? SUE_SOMERSAULT : SUE_DASH;
525 o->timer = 0;
526 }
527 }
528 break;
529 }
530
531 sue_somersault(o);
532 sue_dash(o);
533 }
534
535 // somersault attack. this is the only time she can actually hurt you.
sue_somersault(Object * o)536 static void sue_somersault(Object *o)
537 {
538 switch(o->state)
539 {
540 case SUE_SOMERSAULT:
541 {
542 o->state++;
543 o->timer = 0;
544 o->damage = 4;
545 o->frame = 2;
546
547 FACEPLAYER;
548 ThrowObjectAtPlayer(o, 0, 0x600);
549 set_ignore_solid(o);
550 }
551 case SUE_SOMERSAULT+1:
552 {
553 // passes through frame 3 (prepare/dash) before entering anim loop
554 ANIMATE(1, 4, 7);
555 o->timer++;
556
557 if (o->shaketime && o->timer > 20)
558 { // hurt fall
559 o->state = SUE_SOMERSAULT_HIT;
560 break;
561 }
562
563 // hit wall or timeout?
564 if (o->timer > 50 || \
565 (o->blockr && o->xinertia > 0) || \
566 (o->blockl && o->xinertia < 0))
567 { // back to base state
568 o->state = SUE_BASE;
569 }
570
571 if ((o->timer % 5) == 1)
572 sound(SND_CRITTER_FLY);
573 }
574 break;
575
576 // hit during somersault
577 case SUE_SOMERSAULT_HIT:
578 {
579 o->state++;
580 o->timer = 0;
581 o->frame = 2; // stop somersault; back to normal stand frame
582 o->damage = 0;
583 o->flags &= ~FLAG_IGNORE_SOLID;
584 }
585 case SUE_SOMERSAULT_HIT+1: // slowing down
586 {
587 o->xinertia *= 7; o->xinertia /= 8;
588 o->yinertia *= 7; o->yinertia /= 8;
589
590 if (++o->timer > 6)
591 {
592 o->state++;
593 o->timer = 0;
594 o->yinertia = -0x200;
595 XMOVE(-0x200);
596 }
597 }
598 break;
599
600 // falling/egads
601 case SUE_SOMERSAULT_HIT+2:
602 {
603 o->frame = 9; // egads!
604
605 if (o->blockd && o->yinertia > 0)
606 {
607 o->state++;
608 o->timer = 0;
609 o->frame = 2; // standing
610
611 FACEPLAYER;
612 }
613
614 o->yinertia += 0x20;
615 LIMITY(0x5ff);
616 }
617 break;
618
619 // hit ground: slide a bit then recover
620 case SUE_SOMERSAULT_HIT+3:
621 {
622 if (++o->timer > 16)
623 o->state = 20;
624 }
625 break;
626 }
627 }
628
629 // non-harmful dash. she cannot be hurt, but cannot hurt you, either.
sue_dash(Object * o)630 static void sue_dash(Object *o)
631 {
632 int x;
633
634 switch(o->state)
635 {
636 case SUE_DASH:
637 {
638 o->state++;
639 o->timer = 0;
640
641 FACEPLAYER;
642 o->flags &= ~FLAG_SHOOTABLE;
643
644 if (player->x < o->x) x = player->x - (160<<CSF);
645 else x = player->x + (160<<CSF);
646
647 ThrowObject(o, x, player->y, 0, 0x600);
648 set_ignore_solid(o);
649 }
650 case SUE_DASH+1:
651 {
652 // flash
653 o->frame = (++o->timer & 2) ? 8 : 3; // frame 8 is invisible
654
655 if (o->shaketime < 8)
656 o->nxflags &= ~NXFLAG_SLOW_X_WHEN_HURT;
657
658 if (o->timer > 50 || \
659 (o->blockr && o->xinertia > 0) || \
660 (o->blockl && o->xinertia < 0))
661 {
662 o->invisible = false;
663 o->state = SUE_BASE;
664 o->nxflags |= NXFLAG_SLOW_X_WHEN_HURT;
665 }
666 }
667 break;
668 }
669 }
670
671
672 // sets FLAG_IGNORE_SOLID if the object is heading towards the center
673 // of the room, clears it otherwise.
set_ignore_solid(Object * o)674 static void set_ignore_solid(Object *o)
675 {
676 int map_right_half = ((map.xsize * TILE_W) << CSF) / 2;
677 int map_bottom_half = ((map.ysize * TILE_H) << CSF) / 2;
678
679 o->flags &= ~FLAG_IGNORE_SOLID;
680
681 if ((o->x < map_right_half && o->xinertia > 0) || \
682 (o->x > map_right_half && o->xinertia < 0))
683 {
684 if ((o->y < map_bottom_half && o->yinertia > 0) || \
685 (o->y > map_bottom_half && o->yinertia < 0))
686 {
687 o->flags |= FLAG_IGNORE_SOLID;
688 }
689 }
690 }
691
692 /*
693 void c------------------------------() {}
694 */
695
696 // shared between both Sue and Misery.
sidekick_run_defeated(Object * o,int health)697 static void sidekick_run_defeated(Object *o, int health)
698 {
699 // die if still around when core explodes
700 if (o->state == SIDEKICK_CORE_DEFEATED_2)
701 {
702 if (!game.stageboss.object)
703 o->hp = 0;
704 }
705
706 // trigger die
707 if (o->hp < (1000 - health))
708 {
709 o->flags &= ~FLAG_SHOOTABLE;
710 o->hp = 9999; // don't re-trigger
711 o->state = SIDEKICK_DEFEATED;
712 }
713
714 switch(o->state)
715 {
716 // the script triggers this if you defeat the core
717 // without killing one or both sidekicks.
718 //
719 // once the core explodes and game.stageboss.object becomes NULL,
720 // the sidekicks then enter the full defeated state and collapse.
721 case SIDEKICK_CORE_DEFEATED:
722 {
723 if (o->hp == 9999)
724 { // we were already dead when core was killed--ignore.
725 o->state = SIDEKICK_DEFEATED+1;
726 }
727 else
728 {
729 o->flags &= ~FLAG_SHOOTABLE;
730 o->hp = 9999;
731
732 o->xinertia = 0;
733 o->yinertia = 0;
734 o->frame = 9;
735
736 o->state = SIDEKICK_CORE_DEFEATED_2; // cannot "state++"; that is SIDEKICK_DEFEATED
737 }
738 }
739 break;
740
741 case SIDEKICK_DEFEATED:
742 {
743 o->state++;
744 o->frame = 9;
745 o->damage = 0;
746 o->flags &= ~FLAG_SHOOTABLE;
747 o->flags |= FLAG_IGNORE_SOLID;
748
749 o->yinertia = -0x200;
750 o->shaketime += 50;
751
752 if (o->type == OBJ_SUE_FRENZIED)
753 sue_was_killed = true; // trigger Misery to start spawning missiles
754 }
755 case SIDEKICK_DEFEATED+1:
756 {
757 o->yinertia += 0x20;
758
759 #define FLOOR (((13 * TILE_H) - 13) << CSF)
760 if (o->yinertia > 0 && o->y > FLOOR)
761 {
762 o->y = FLOOR;
763 o->state++;
764 o->frame = 10;
765 o->xinertia = 0;
766 o->yinertia = 0;
767 }
768 }
769 break;
770
771 case SIDEKICK_CORE_DEFEATED_2: break;
772 }
773 }
774
775
776
777