1 
2 #include "../stdai.h"
3 #include "doctor.h"
4 #include "doctor_frenzied.fdh"
5 
6 enum STATES
7 {
8 	// 0-9 are used by in-game scripts during intro
9 	// 500+ is triggered for defeat sequence
10 
11 	STATE_BASE				= 10,		// fall; once on ground for a moment pick next attack
12 
13 	STATE_JUMP				= 20,		// jumps at player
14 	STATE_JUMP_WITH_GP		= 30,		// taller jump and enable "ground pound"
15 	STATE_IN_AIR			= 40,		// in air
16 	STATE_IN_AIR_WITH_GP	= 50,		// in air and will "ground pound" if passes over player
17 	STATE_LANDED			= 60,		// landed from a jump
18 
19 	STATE_RED_DASH			= 70,		// red dash; try to hit player with elbow
20 	STATE_MEGA_BATS			= 80,		// arms out & spawn mega bats
21 
22 	STATE_TELEPORT			= 90,		// teleport away and reappear over players head
23 
24 	STATE_DEFEATED			= 500,		// script constant
25 	STATE_DISSOLVE			= 510		// also scripted
26 };
27 
28 #define DAMAGE_NORMAL		5
29 #define DAMAGE_RED_DASH		10
30 
31 // the Doctor repeats this series of attacks over and over,
32 // when he reaches the -1, it starts over at the beginning.
33 static const int attack_pattern[] =
34 {
35 	STATE_JUMP_WITH_GP,
36 	STATE_TELEPORT,
37 	STATE_RED_DASH,
38 	STATE_JUMP,
39 	STATE_JUMP,
40 	STATE_RED_DASH,
41 	STATE_TELEPORT,
42 	STATE_MEGA_BATS,
43 	STATE_JUMP_WITH_GP,
44 	STATE_JUMP,
45 	STATE_JUMP,
46 	-1
47 };
48 
INITFUNC(AIRoutines)49 INITFUNC(AIRoutines)
50 {
51 	ONTICK(OBJ_BOSS_DOCTOR_FRENZIED, ai_boss_doctor_frenzied);
52 	ONTICK(OBJ_DOCTOR_BAT, ai_doctor_bat);
53 }
54 
55 /*
56 void c------------------------------() {}
57 */
58 
ai_boss_doctor_frenzied(Object * o)59 void ai_boss_doctor_frenzied(Object *o)
60 {
61 	//AIDEBUG;
62 
63 	switch(o->state)
64 	{
65 		// fight begin/base state
66 		case STATE_BASE:
67 		{
68 			o->flags |= FLAG_SHOOTABLE;
69 			o->flags &= ~FLAG_INVULNERABLE;
70 
71 			o->xinertia = 0;
72 			o->frame = 1;
73 
74 			o->timer = 0;
75 			o->animtimer = 0;
76 
77 			o->savedhp = o->hp;
78 			o->state++;
79 		}
80 		case STATE_BASE+1:
81 		{
82 			o->yinertia += 0x80;
83 			FACEPLAYER;
84 
85 			// select frame
86 			if (o->frame == 6)	// arms out
87 			{
88 				// must have done "redsplode" attack; leave alone for the duration
89 			}
90 			else if (!o->blockd)
91 			{	// falling
92 				o->frame = 4;
93 			}
94 			else
95 			{	// panting animation
96 				ANIMATE(10, 1, 2);
97 
98 				// "redsplode" attack if possible
99 				if ((o->savedhp - o->hp) > 20)
100 				{
101 					if (pdistlx(48<<CSF) && player->blockd)
102 						do_redsplode(o);
103 				}
104 			}
105 
106 			// after a moment select next attack in the loop
107 			if (++o->timer > 30 || (o->savedhp - o->hp) > 20)
108 			{
109 				o->state = attack_pattern[o->timer2];
110 				o->timer = 0;
111 
112 				// move to next state
113 				if (attack_pattern[++o->timer2] == -1)
114 					o->timer2 = 0;
115 			}
116 		}
117 		break;
118 	}
119 
120 	run_jumps(o);
121 
122 	run_red_dash(o);
123 	run_mega_bats(o);
124 	run_teleport(o);
125 	run_init(o);
126 	run_defeat(o);
127 
128 	if (o->state < STATE_DISSOLVE)
129 		run_red_drip(o);
130 
131 	// set crystal follow position
132 	// still set it on first 2 DEFEATED states (falling to ground)
133 	// but not after that (alert: this seems pretty damn bug prone, fixme)
134 	if (o->state >= STATE_BASE && o->state <= STATE_DEFEATED+1)
135 	{
136 		if (o->invisible)	// in middle of teleport: after tp out, before tp in
137 		{
138 			crystal_xmark = o->xmark;
139 			crystal_ymark = o->ymark;
140 		}
141 		else
142 		{
143 			crystal_xmark = o->x;
144 			crystal_ymark = o->y;
145 		}
146 	}
147 
148 	if (o->yinertia > 0x5ff)
149 		o->yinertia = 0x5ff;
150 }
151 
152 
run_jumps(Object * o)153 static void run_jumps(Object *o)
154 {
155 
156 	switch(o->state)
157 	{
158 		// jump at player
159 		case STATE_JUMP:
160 		{
161 			o->frame = 3;
162 			FACEPLAYER;
163 
164 			if (++o->timer > 20)
165 			{
166 				o->state = STATE_IN_AIR;
167 				o->frame = 4;
168 				o->animtimer = 0;
169 
170 				o->yinertia = -0x600;
171 				XMOVE(0x400);
172 			}
173 		}
174 		break;
175 
176 		// slight taller jump with no delay, and can "ground pound"
177 		case STATE_JUMP_WITH_GP:
178 		{
179 			FACEPLAYER;
180 
181 			o->state = STATE_IN_AIR_WITH_GP;
182 			o->frame = 4;
183 			o->animtimer = 0;
184 
185 			o->yinertia = -0x800;
186 			XMOVE(0x400);
187 		}
188 		break;
189 
190 
191 		case STATE_IN_AIR:			// in air (normal)
192 		case STATE_IN_AIR_WITH_GP:	// in air; can "ground pound" if we pass over player
193 		{
194 			ANIMATE(1, 4, 5);
195 			o->yinertia += 0x40;
196 
197 			if (o->state == STATE_IN_AIR_WITH_GP)
198 			{
199 				if (pdistlx(8<<CSF) && player->y >= o->y)
200 				{
201 					o->xinertia = 0;
202 					o->yinertia = 0x5ff;
203 					o->state = STATE_IN_AIR;
204 				}
205 			}
206 			else
207 			{
208 				FACEPLAYER;
209 			}
210 
211 			if (o->blockd && o->yinertia > 0)
212 				o->state = STATE_LANDED;
213 		}
214 		break;
215 
216 
217 		// landed from jump
218 		case STATE_LANDED:
219 		{
220 			o->frame = 3;
221 			quake(10);
222 
223 			o->timer = 0;
224 			o->state++;
225 		}
226 		case STATE_LANDED+1:
227 		{
228 			o->yinertia += 0x80;
229 
230 			o->xinertia *= 7;
231 			o->xinertia /= 8;
232 
233 			if (++o->timer > 10)
234 				o->state = STATE_BASE;
235 		}
236 		break;
237 	}
238 }
239 
240 /*
241 void c------------------------------() {}
242 */
243 
244 // flashing red elbow dash
run_red_dash(Object * o)245 static void run_red_dash(Object *o)
246 {
247 	switch(o->state)
248 	{
249 		// prepare to dash
250 		case STATE_RED_DASH:
251 		{
252 			o->frame = 3;	// crouch down; look mean
253 			o->flags |= (FLAG_SOLID_MUSHY | FLAG_INVULNERABLE);
254 
255 			o->timer = 0;
256 			o->state++;
257 		}
258 		case STATE_RED_DASH+1:
259 		{
260 			if (++o->timer > 20)
261 			{
262 				o->frame = 7;		// elbow-out dash frame
263 				o->timer = 0;
264 				o->state++;
265 
266 				sound(SND_FUNNY_EXPLODE);
267 				XMOVE(0x5ff);
268 
269 				o->damage = DAMAGE_RED_DASH;
270 				o->flags |= FLAG_NOREARTOPATTACK;
271 				o->yinertia = 0;	// he does not fall during dash
272 			}
273 		}
274 		break;
275 
276 		// doing red dash
277 		case STATE_RED_DASH+2:
278 		{
279 			// flash red
280 			o->timer++;
281 			o->frame = (o->timer & 2) ? 7 : 8;
282 
283 			// time to stop?
284 			if ((o->blockl && o->xinertia < 0) || \
285 				(o->blockr && o->xinertia > 0) || \
286 				o->timer > 30)
287 			{
288 				if (o->timer > 30)		// stopped because timeout
289 					o->state++;
290 				else					// stopped because hit a wall
291 					o->state = STATE_JUMP;
292 
293 				o->flags &= ~(FLAG_SOLID_MUSHY | FLAG_NOREARTOPATTACK | FLAG_INVULNERABLE);
294 				o->damage = DAMAGE_NORMAL;
295 				o->timer = 0;
296 			}
297 		}
298 		break;
299 
300 		// dash ending due to timeout
301 		case STATE_RED_DASH+3:
302 		{
303 			o->yinertia += 0x80;
304 			o->frame = 3;
305 
306 			o->xinertia *= 7;
307 			o->xinertia /= 8;
308 
309 			if (++o->timer > 10)
310 				o->state = STATE_BASE;
311 		}
312 		break;
313 	}
314 }
315 
316 
317 // arms thrust out, spawn oodles of bouncing bats
run_mega_bats(Object * o)318 static void run_mega_bats(Object *o)
319 {
320 
321 	switch(o->state)
322 	{
323 		case STATE_MEGA_BATS:
324 		{
325 			o->state++;
326 			o->timer = 0;
327 		}
328 		case STATE_MEGA_BATS+1:
329 		{
330 			o->frame = 6;
331 			o->timer++;
332 
333 			if (o->timer > 20 && (o->timer % 3) == 1)
334 			{
335 				Object *bat = CreateObject(o->x + (8<<CSF), \
336 										   o->y - (4<<CSF), OBJ_DOCTOR_BAT);
337 
338 				bat->xinertia = random(0x400, 0x800);
339 				bat->yinertia = random(-0x200, 0x200);
340 				bat->dir = o->dir;
341 
342 				if (o->dir == LEFT)
343 				{
344 					bat->x -= (16 << CSF);
345 					bat->xinertia = -bat->xinertia;
346 				}
347 
348 				sound(SND_EM_FIRE);
349 			}
350 
351 			if (o->timer > 90)
352 				o->state = STATE_BASE;
353 		}
354 		break;
355 	}
356 }
357 
358 
359 // teleport away and return
run_teleport(Object * o)360 static void run_teleport(Object *o)
361 {
362 	switch(o->state)
363 	{
364 		case STATE_TELEPORT:
365 		{
366 			o->flags &= ~FLAG_SHOOTABLE;
367 			o->damage = 0;
368 
369 			o->state++;
370 			dr_tp_out_init(o);
371 		}
372 		case STATE_TELEPORT+1:
373 		{
374 			if (dr_tp_out(o))
375 			{
376 				o->state++;
377 				o->timer = 0;
378 
379 				// mark the location above player's head where we'll reappear
380 				o->xmark = player->x;
381 				o->ymark = player->y - (32<<CSF);
382 
383 				// don't be fooled into going off bounds of map
384 				#define TP_X_MIN	((4 * TILE_W) << CSF)
385 				#define TP_X_MAX	((36 * TILE_W) << CSF)
386 				#define TP_Y_MIN	((4 * TILE_W) << CSF)
387 
388 				if (o->xmark < TP_X_MIN) o->xmark = TP_X_MIN;
389 				if (o->xmark > TP_X_MAX) o->xmark = TP_X_MAX;
390 				if (o->ymark < TP_Y_MIN) o->ymark = TP_Y_MIN;
391 			}
392 		}
393 		break;
394 
395 		// invisible...waiting to reappear
396 		case STATE_TELEPORT+2:
397 		{
398 			if (++o->timer > 40)
399 			{
400 				o->x = o->xmark;
401 				o->y = o->ymark;
402 				o->frame = 4;
403 
404 				FACEPLAYER;
405 
406 				o->state++;
407 			}
408 		}
409 		break;
410 
411 		// reappear
412 		case STATE_TELEPORT+3:
413 		{
414 			dr_tp_in_init(o);
415 			o->yinertia = 0;
416 			o->state++;
417 		}
418 		case STATE_TELEPORT+4:
419 		{
420 			if (dr_tp_in(o))
421 			{
422 				o->flags |= FLAG_SHOOTABLE;
423 				o->damage = DAMAGE_NORMAL;
424 
425 				o->xinertia = 0;
426 				o->yinertia = -0x200;
427 				o->state = STATE_IN_AIR;
428 			}
429 		}
430 		break;
431 	}
432 
433 }
434 
435 
436 /*
437 void c------------------------------() {}
438 */
439 
440 // initilization/transformation animation and "prepare to fight" states.
441 // this are of course all script-triggered and need to stay constant.
run_init(Object * o)442 static void run_init(Object *o)
443 {
444 	switch(o->state)
445 	{
446 		case 0:
447 		{
448 			// put ourselves at correct position over the other doctor
449 			o->dir = (crystal_xmark > player->x) ? LEFT : RIGHT;
450 
451 			o->x = crystal_xmark + ((o->dir == RIGHT) ? (6<<CSF) : -(6<<CSF));
452 			o->y = crystal_ymark;
453 
454 			// make sure we're front of other doctor
455 			o->BringToFront();
456 			// make sure front crystal is in front of us
457 			crystal_tofront = true;
458 
459 			// because we moved our x/y directly
460 			o->UpdateBlockStates(ALLDIRMASK);
461 			o->state = 1;
462 		}
463 		case 1:		// appearing/transforming
464 		{
465 			o->yinertia += 0x80;
466 
467 			o->timer++;
468 			o->frame = (o->timer & 2) ? 0 : 3;
469 		}
470 		break;
471 
472 		// standing idle & panting
473 		// this state doesn't seem to ever be used;
474 		// AFAIK can only be triggered by modifying the script.
475 		case 5:
476 		{
477 			o->frame = 1;
478 			o->animtimer = 0;
479 			o->state = 6;
480 		}
481 		case 6:
482 		{
483 			o->yinertia += 0x80;
484 			ANIMATE(30, 1, 2);
485 		}
486 		break;
487 
488 		case 7: 	// prepare-to-fight pause
489 		{
490 			o->state = 8;
491 			o->timer = 0;
492 			o->frame = 3;
493 		}
494 		case 8:
495 		{
496 			o->yinertia += 0x40;
497 
498 			if (++o->timer > 40)
499 				o->state = STATE_BASE;
500 		}
501 		break;
502 	}
503 }
504 
505 
506 // defeated states and animation
run_defeat(Object * o)507 static void run_defeat(Object *o)
508 {
509 	switch(o->state)
510 	{
511 		// Defeated!
512 		case STATE_DEFEATED:
513 		{
514 			KillObjectsOfType(OBJ_DOCTOR_BAT);
515 			o->flags &= ~FLAG_SHOOTABLE;
516 			o->damage = 0;
517 			o->frame = 4;
518 			o->xinertia = 0;
519 			o->state++;
520 		}
521 		case STATE_DEFEATED+1:		// wait till we hit ground
522 		{
523 			o->yinertia += 0x20;
524 
525 			if (o->blockd && o->yinertia > 0)
526 			{
527 				o->state++;
528 				o->timer = 0;
529 				o->xmark = o->x;
530 				FACEPLAYER;
531 			}
532 		}
533 		break;
534 
535 		// shaking (script tells us when to stop)
536 		case STATE_DEFEATED+2:
537 		{
538 			o->frame = 9;
539 			o->timer++;
540 
541 			o->x = o->xmark;
542 			if (!(o->timer & 2)) o->x += (1 << CSF);
543 		}
544 		break;
545 
546 
547 		// dissolve into red energy
548 		// we already did this once before; think Pooh Black.
549 		case STATE_DISSOLVE:
550 		{
551 			o->frame = 9;
552 			o->x = o->xmark;
553 
554 			o->ResetClip();
555 			o->clip_enable = true;
556 
557 			o->state++;
558 			o->timer = 0;
559 		}
560 		case STATE_DISSOLVE+1:
561 		{
562 			o->timer++;
563 
564 			// shaking
565 			o->x = o->xmark;
566 			if (!(o->timer & 2)) o->x += (1 << CSF);
567 
568 			game.quaketime = 2;
569 
570 			// sound
571 			if ((o->timer % 6) == 3)
572 				sound(SND_FUNNY_EXPLODE);
573 
574 			// move energy spawn point
575 			if (++o->timer2 >= 8)
576 			{
577 				o->timer2 = 0;
578 
579 				o->clipy1++;
580 				if (o->clipy1 >= sprites[o->sprite].h)
581 					o->invisible = true;
582 			}
583 
584 			// spawn copious amount of energy
585 			for(int i=0;i<3;i++)
586 			{
587 				int x, y;
588 
589 				x = o->x + (random(-16, 16) << CSF);
590 				y = (o->y - o->DrawPointY()) + (o->clipy1 << CSF);
591 
592 				Object *drip = CreateObject(x, y, OBJ_RED_ENERGY);
593 
594 				drip->xinertia = random(-0x200, 0x200);
595 				drip->yinertia = random(-0x400, 0);
596 				drip->angle = DOWN;
597 				// otherwise during the last few frames they'll get stuck in the floor
598 				// (they still delete themselves once they hit the floor, just are
599 				// able to come up out of it then back down during last few moments).
600 				drip->flags |= FLAG_IGNORE_SOLID;
601 			}
602 
603 			// he doesn't take up the entire height of the sprite,
604 			// so we stop a little bit early.
605 			if (o->clipy1 >= 44)
606 			{
607 				o->invisible = true;
608 				o->frame = 0;
609 				o->state++;
610 			}
611 		}
612 		break;
613 
614 		// script: crystal up and away
615 		case 520:
616 		{
617 			crystal_ymark = -(32 << CSF);
618 		}
619 		break;
620 	}
621 }
622 
623 
624 
625 
626 /*
627 void c------------------------------() {}
628 */
629 
630 // this is a lesser-seen attack in which he pushes you away amongst
631 // a shower of red sparks. To trigger it, immediately after he lands
632 // you must walk directly up to him and deal more than 20 damage.
do_redsplode(Object * o)633 static void do_redsplode(Object *o)
634 {
635 	// arms out full
636 	o->frame = 6;
637 	FACEPLAYER;
638 
639 	player->yinertia = -0x400;
640 	player->xinertia = (o->x > player->x) ? -0x5ff : 0x5ff;
641 
642 	hurtplayer(5);
643 	quake(10);
644 
645 	// big shower of red energy
646 	for(int i=0;i<100;i++)
647 	{
648 		int x = o->x + (random(-16, 16) << CSF);
649 		int y = o->y + (random(-16, 16) << CSF);
650 
651 		Object *spark = CreateObject(x, y, OBJ_RED_ENERGY);
652 
653 		spark->xinertia = random(-0x600, 0x600);
654 		spark->yinertia = random(-0x600, 0x600);
655 		spark->angle = DOWN;
656 	}
657 }
658 
659 /*
660 void c------------------------------() {}
661 */
662 
663 // the red energy that oozes off of him during most of the battle
run_red_drip(Object * o)664 static void run_red_drip(Object *o)
665 {
666 	if (random(0, 3) == 2)
667 	{
668 		int x = o->x + (random(-16, 16) << CSF);
669 		int y = o->y + (random(-8, 4) << CSF);
670 
671 		Object *drip = CreateObject(x, y, OBJ_RED_ENERGY);
672 		drip->xinertia = o->xinertia;
673 		drip->angle = DOWN;
674 	}
675 }
676 
677 
678 /*
679 void c------------------------------() {}
680 */
681 
ai_doctor_bat(Object * o)682 void ai_doctor_bat(Object *o)
683 {
684 	ANIMATE(2, 0, 2);
685 
686 	if ((o->blockl && o->xinertia < 0) || \
687 		(o->blockr && o->xinertia > 0))
688 	{
689 		o->dir ^= 1;
690 		o->xinertia = -o->xinertia;
691 	}
692 	else if ((o->blocku && o->yinertia < 0) || \
693 		(o->blockd && o->yinertia > 0))
694 	{
695 		o->yinertia = -o->yinertia;
696 	}
697 }
698 
699 
700 
701 
702 
703 
704 
705 
706 
707 
708 
709 
710 
711 
712 
713 
714 
715 
716 
717 
718 
719 
720 
721 
722 
723