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