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