1
2 #include "../stdai.h"
3 #include "balfrog.h"
4 #include "balfrog.fdh"
5
6 enum Frames
7 {
8 FRAME_STAND = 0,
9 FRAME_CROUCH = 1,
10 FRAME_MOUTH_OPEN = 2,
11 FRAME_MOUTH_OPEN_HURT = 3,
12
13 FRAME_JUMPING = 0 // on other sprite (SPR_BALFROG_JUMP)
14 };
15
16 enum States
17 {
18 STATE_TRANSFORM = 20, // script-triggered: must stay at this value
19 STATE_READY = 10, // script-triggered: must stay at this value
20 STATE_DEATH = 130, // script-triggered: must stay at this value
21 STATE_FIGHTING = 100, // script-triggered: must stay at this value
22
23 STATE_JUMPING = 50,
24
25 STATE_OPEN_MOUTH = 60,
26 STATE_SHOOTING = 70,
27 STATE_CLOSE_MOUTH = 80,
28
29 STATE_BIG_JUMP = 90
30 };
31
32 enum BBox_States
33 {
34 BM_STAND,
35 BM_JUMPING,
36 BM_MOUTH_OPEN,
37 BM_DISABLED
38 };
39
40 #define FROG_START_X ((5 * TILE_W) << CSF)
41 #define FROG_START_Y ((10 * TILE_H) << CSF)
42
43 #define LANDING_SMOKE_COUNT 8
44 #define LANDING_SMOKE_YTOP -4
45
46 #define DEATH_SMOKE_COUNT 8
47 #define DEATH_SMOKE_YTOP -24
48
49 // when he lands he spawns frogs from ceiling--
50 // this is the range of where they should spawn at
51 #define SPAWN_RANGE_LEFT 4
52 #define SPAWN_RANGE_RIGHT 16
53 #define SPAWN_RANGE_TOP 0
54 #define SPAWN_RANGE_BOTTOM 4
55
56 // offset from top and from left or right (depending on direction facing)
57 // to spawn the balrog 'puppet' when we return to balrog form after being defeated.
58 #define BALDEATH_X (12 << CSF)
59 #define BALDEATH_Y (44 << CSF)
60
61 // twiddle adjustment to get the proper Y coordinate when switching
62 // between normal and jumping sprites.
63 #define JUMP_SPRITE_ADJ (16 << CSF)
64
65
INITFUNC(AIRoutines)66 INITFUNC(AIRoutines)
67 {
68 ONDEATH(OBJ_BALFROG, ondeath_balfrog);
69 ONTICK(OBJ_BALFROG_SHOT, ai_generic_angled_shot);
70 }
71
OnMapEntry(void)72 void BalfrogBoss::OnMapEntry(void)
73 {
74 memset(&frog, 0, sizeof(frog));
75
76 o = CreateObject(FROG_START_X, FROG_START_Y, OBJ_BALFROG);
77 game.stageboss.object = o;
78
79 o->hp = 300;
80 o->damage = 0; // damage comes from our bbox puppets, not our own bbox
81 o->flags |= FLAG_SHOW_FLOATTEXT;
82
83 o->sprite = SPR_BALFROG;
84 o->dir = RIGHT;
85 o->invisible = true;
86
87 // setup the bounding box objects--this boss has an irregular bounding box
88 // and so we simulate that by having three invisible objects which are wired
89 // to transmit hits to the real Balfrog boss object.
90 frog.bboxes.init(o, 3);
91 frog.bboxes.set_damage(5);
92 frog.bbox_mode = BM_DISABLED;
93
94 // now disable being able to hit the Balfrog boss object itself.
95 o->flags &= ~FLAG_SHOOTABLE;
96
97 objprop[OBJ_BALFROG].xponkill = 1;
98 objprop[OBJ_BALFROG].shaketime = 9;
99 }
100
101 /*
102 void c------------------------------() {}
103 */
104
Run()105 void BalfrogBoss::Run()
106 {
107 if (!o) return;
108
109 // each subroutine handles a subset of the frog's finite state machine
110 RunFighting();
111 RunJumping();
112 RunShooting();
113
114 RunEntryAnim();
115 RunDeathAnim();
116
117 if (o) // because RunDeathAnim destroys o at end of fight
118 {
119 o->yinertia += 0x40;
120 // don't limit upwards inertia or Big Jump will fail
121 if (o->yinertia > 0x5FF) o->yinertia = 0x5FF;
122
123 // link our "irregular" bbox (actually composed of multiple "puppet" bboxes)
124 // to our real object.
125 frog.bboxes.transmit_hits();
126 frog.bboxes.place(&call_place_bboxes, this);
127 }
128 }
129
130 /*
131 void c------------------------------() {}
132 */
133
call_place_bboxes(void * balfrog)134 static void call_place_bboxes(void *balfrog)
135 {
136 ((BalfrogBoss *)balfrog)->place_bboxes();
137 }
138
place_bboxes()139 void BalfrogBoss::place_bboxes()
140 {
141 #define set_bbox frog.bboxes.set_bbox
142
143 // I got these coordinates by drawing rectangles over the
144 // sprites in Photoshop. These are for the right-facing frame
145 // and are automatically flipped if the object is facing left.
146 switch(frog.bbox_mode)
147 {
148 case BM_STAND:
149 set_bbox(0, 5, 28, 50, 36, FLAG_INVULNERABLE); // body
150 set_bbox(1, 37, 4, 38, 36, FLAG_INVULNERABLE); // head
151 break;
152
153 case BM_JUMPING:
154 set_bbox(0, 12, 29, 41, 47, FLAG_INVULNERABLE); // body
155 set_bbox(1, 30, 3, 43, 35, FLAG_INVULNERABLE); // head
156 break;
157
158 case BM_MOUTH_OPEN:
159 set_bbox(0, 8, 22, 38, 42, FLAG_INVULNERABLE); // backside
160 set_bbox(1, 46, 54, 13, 10, FLAG_INVULNERABLE); // feet below mouth
161 set_bbox(2, 46, 15, 21, 39, FLAG_SHOOTABLE); // mouth target
162 break;
163 }
164 }
165
166 /*
167 void c------------------------------() {}
168 */
169
170 // the "master" start state, we can always return here eventually from
171 // all the other states. The script also sets this state to start the fight.
RunFighting()172 void BalfrogBoss::RunFighting()
173 {
174 switch(o->state)
175 {
176 case STATE_FIGHTING:
177 {
178 o->frame = FRAME_STAND;
179 frog.bbox_mode = BM_STAND;
180
181 o->state++;
182 o->timer = 0;
183 o->xinertia = 0;
184 }
185 case STATE_FIGHTING+1:
186 {
187 o->timer++;
188
189 // prepare to jump
190 if (o->timer < 50) o->frame = FRAME_STAND;
191 if (o->timer == 50) o->frame = FRAME_CROUCH;
192 if (o->timer == 60) o->frame = FRAME_STAND;
193
194 // jump
195 if (o->timer > 64)
196 o->state = STATE_JUMPING;
197 }
198 break;
199 }
200 }
201
202 // handles the hopping and the "big jump" attack
203 // (a straight up/down jump after every 3rd attack that spawns tons of frogs).
RunJumping()204 void BalfrogBoss::RunJumping()
205 {
206 switch(o->state)
207 {
208 case STATE_JUMPING:
209 {
210 sound(SND_FUNNY_EXPLODE);
211
212 SetJumpingSprite(true);
213 o->yinertia = -0x400;
214
215 XMOVE(0x200);
216 o->timer = 0;
217 o->state++;
218 }
219 case STATE_JUMPING+1:
220 {
221 // turn around at walls
222 if (o->dir == LEFT && o->blockl) o->dir = RIGHT;
223 if (o->dir == RIGHT && o->blockr) o->dir = LEFT;
224
225 // landed?
226 if (++o->timer > 3 && o->blockd)
227 {
228 quake(30);
229 SetJumpingSprite(false);
230
231 // passed player? turn around and fire!
232 if ((o->dir == RIGHT && o->x >= player->x) || \
233 (o->dir == LEFT && o->x <= player->x))
234 {
235 o->dir ^= 1;
236 o->state = STATE_OPEN_MOUTH;
237 }
238 else
239 {
240 o->state = STATE_FIGHTING;
241 }
242
243 // shake a small frog loose from the ceiling on every landing
244 SpawnFrogs(OBJ_MINIFROG, 1);
245 SpawnSmoke(LANDING_SMOKE_COUNT, LANDING_SMOKE_YTOP);
246 }
247 }
248 break;
249
250 case STATE_BIG_JUMP:
251 {
252 o->state++;
253 o->timer = 0;
254 o->xinertia = 0;
255 }
256 case STATE_BIG_JUMP+1: // animation of preparing to jump
257 {
258 o->timer++;
259
260 if (o->timer < 50) o->frame = FRAME_STAND;
261 if (o->timer == 50) o->frame = FRAME_CROUCH;
262 if (o->timer == 70) o->frame = FRAME_STAND;
263
264 if (o->timer > 74)
265 {
266 o->state++;
267
268 SetJumpingSprite(true);
269 o->yinertia = -0xA00;
270 }
271 }
272 break;
273
274 case STATE_BIG_JUMP+2: // in air, waiting to hit ground
275 {
276 // pass through ceiling at edges
277 if (o->y <= MAPY(8))
278 o->flags |= FLAG_IGNORE_SOLID;
279 else
280 o->flags &= ~FLAG_IGNORE_SOLID;
281
282 if (++o->timer > 3 && o->blockd)
283 {
284 o->flags &= ~FLAG_IGNORE_SOLID;
285 SetJumpingSprite(false);
286 quake(60);
287
288 SpawnFrogs(OBJ_MINIFROG, 6);
289 SpawnFrogs(OBJ_FROG, 2);
290 SpawnSmoke(LANDING_SMOKE_COUNT, LANDING_SMOKE_YTOP);
291
292 // player ran under us? turn around and fire!
293 if ((o->dir == RIGHT && o->x >= player->x) || \
294 (o->dir == LEFT && o->x <= player->x))
295 {
296 o->state = STATE_OPEN_MOUTH;
297 }
298 else
299 {
300 o->state = STATE_FIGHTING;
301 }
302
303 FACEPLAYER;
304 }
305 }
306 break;
307 }
308 }
309
310
RunShooting()311 void BalfrogBoss::RunShooting()
312 {
313 switch(o->state)
314 {
315 case STATE_OPEN_MOUTH: // open mouth and fire shots
316 {
317 o->frame = FRAME_STAND;
318 o->timer = 0;
319 o->state++;
320 }
321 case STATE_OPEN_MOUTH+1:
322 {
323 o->xinertia *= 8;
324 o->xinertia /= 9;
325
326 o->timer++;
327
328 if (o->timer == 50)
329 o->frame = FRAME_CROUCH;
330
331 if (o->timer > 54)
332 {
333 o->state = STATE_SHOOTING;
334 o->timer = 0;
335
336 o->frame = FRAME_MOUTH_OPEN;
337 frog.bbox_mode = BM_MOUTH_OPEN;
338
339 frog.orighp = o->hp;
340 frog.shots_fired = 0;
341 }
342 }
343 break;
344
345 case STATE_SHOOTING:
346 {
347 frog.bbox_mode = BM_MOUTH_OPEN;
348 o->frame = FRAME_MOUTH_OPEN;
349 o->xinertia *= 10;
350 o->xinertia /= 11;
351
352 if (o->shaketime)
353 {
354 if (++frog.shakeflash & 2)
355 o->frame = FRAME_MOUTH_OPEN_HURT;
356 }
357 else
358 {
359 frog.shakeflash = 0;
360 }
361
362 if (++o->timer > 16)
363 {
364 o->timer = 0;
365
366 EmFireAngledShot(o, OBJ_BALFROG_SHOT, 16, 0x200);
367 sound(SND_EM_FIRE);
368
369 if (++frog.shots_fired > 10 || o->hp < (frog.orighp - 90))
370 {
371 o->frame = FRAME_CROUCH;
372 o->state = STATE_CLOSE_MOUTH;
373 frog.bbox_mode = BM_STAND;
374 o->timer = 0;
375 }
376 }
377 }
378 break;
379
380 case STATE_CLOSE_MOUTH:
381 {
382 o->frame = FRAME_CROUCH;
383
384 if (++o->timer > 10)
385 {
386 o->timer = 0;
387 o->frame = FRAME_STAND;
388
389 if (++frog.attackcounter >= 3)
390 { // big jump after every 3rd attack
391 frog.attackcounter = 0;
392 o->state = STATE_BIG_JUMP;
393 }
394 else
395 {
396 o->state = STATE_FIGHTING;
397 }
398 }
399 }
400 break;
401 }
402 }
403
404 /*
405 void c------------------------------() {}
406 */
407
408 // the animation where we first appear
409 // both these states are triggered by the script
RunEntryAnim()410 void BalfrogBoss::RunEntryAnim()
411 {
412 switch(o->state)
413 {
414 // transforming from Balrog
415 // the flicker is calibrated to be interlaced exactly out-of-phase
416 // with Balrog's flicker, which is entirely separate.
417 case STATE_TRANSFORM:
418 {
419 o->timer = 0;
420 o->frame = FRAME_MOUTH_OPEN;
421 o->state++;
422 }
423 case STATE_TRANSFORM+1:
424 {
425 o->timer++;
426 o->invisible = (o->timer & 2) ? true : false;
427 }
428 break;
429
430 // transformation complete: puff away balrog, and appear solid now
431 case STATE_READY:
432 {
433 SmokeXY(o->x + ((sprites[SPR_BALROG_GREEN].w / 2) << CSF), \
434 o->y + (48 << CSF),
435 8, 16, 16);
436
437 o->state++;
438 o->frame = FRAME_MOUTH_OPEN;
439 }
440 break;
441 }
442 }
443
444
RunDeathAnim()445 void BalfrogBoss::RunDeathAnim()
446 {
447 switch(o->state)
448 {
449 case STATE_DEATH: // BOOM!
450 {
451 SetJumpingSprite(false);
452 o->frame = FRAME_MOUTH_OPEN;
453
454 sound(SND_BIG_CRASH);
455 o->xinertia = 0;
456 o->timer = 0;
457 o->state++;
458
459 SpawnSmoke(DEATH_SMOKE_COUNT, DEATH_SMOKE_YTOP);
460 }
461 case STATE_DEATH+1: // shaking with mouth open
462 {
463 o->timer++;
464 if ((o->timer % 5) == 0)
465 {
466 SpawnSmoke(1, DEATH_SMOKE_YTOP);
467 }
468
469 // at a glance it might seem like this has it alternate
470 // slowly between 2 X coordinates, but in fact, it
471 // alternates quickly between 3.
472 o->x += (o->timer & 2) ? (1 << CSF) : (-1 << CSF);
473
474 if (o->timer > 100)
475 {
476 o->timer = 0;
477 o->state++;
478 }
479 }
480 break;
481
482 case STATE_DEATH+2: // begin flashing back and forth between frog and balrog
483 {
484 // spawn balrog puppet
485 frog.balrog = CreateObject(0, o->y+BALDEATH_Y, OBJ_BALROG);
486 frog.balrog->state = 500; // tell him to give us complete control
487 frog.balrog->dir = o->dir;
488 frog.balrog->frame = 5;
489
490 if (o->dir == RIGHT)
491 {
492 frog.balrog->x = (o->x + BALDEATH_X);
493 }
494 else
495 {
496 frog.balrog->x = o->x + o->Width(); // not the same as o->Right()
497 frog.balrog->x -= frog.balrog->Width();
498 frog.balrog->x -= BALDEATH_X;
499 }
500
501 o->state++;
502 }
503 case STATE_DEATH+3: // flashing
504 {
505 o->timer++;
506
507 if ((o->timer % 9) == 0)
508 SpawnSmoke(1, DEATH_SMOKE_YTOP);
509
510 if (o->timer <= 150)
511 {
512 o->invisible = (o->timer & 2);
513 frog.balrog->invisible = !(o->timer & 2);
514 }
515
516 if (o->timer > 156)
517 {
518 o->timer = 0;
519 o->state++;
520 }
521 }
522 break;
523
524 case STATE_DEATH+4: // balrog falling to ground
525 {
526 // should start to move exactly when timer hits 160
527 //
528 // 10 frames until starts to fall
529 // 14 frames until changes to landed frame
530 frog.balrog->yinertia += 0x40;
531
532 if (frog.balrog->blockd)
533 {
534 frog.balrog->frame = 2;
535 if (++o->timer > 30)
536 {
537 frog.balrog->frame = 3;
538 o->state++;
539 }
540 }
541 }
542 break;
543
544 case STATE_DEATH+5: // balrog flying away
545 {
546 if (++o->timer > 30)
547 {
548 // it's all over, destroy ourselves and clean up
549 frog.balrog->yinertia = -0xA00;
550 frog.balrog->flags |= FLAG_IGNORE_SOLID;
551
552 if (frog.balrog->y < -(100 << CSF))
553 {
554 frog.balrog->Delete();
555 frog.bboxes.destroy();
556
557 o->Delete();
558 o = game.stageboss.object = NULL;
559 return;
560 }
561 }
562 }
563 break;
564 }
565 }
566
567
568 /*
569 void c------------------------------() {}
570 */
571
ondeath_balfrog(Object * o)572 void ondeath_balfrog(Object *o)
573 {
574 o->flags &= ~FLAG_SHOOTABLE;
575 StartScript(1000);
576 }
577
578 /*
579 void c------------------------------() {}
580 */
581
582 // shake loose frogs from the ceiling
SpawnFrogs(int objtype,int count)583 void BalfrogBoss::SpawnFrogs(int objtype, int count)
584 {
585 Object *child;
586
587 for(int i=0;i<count;i++)
588 {
589 int x = random(SPAWN_RANGE_LEFT, SPAWN_RANGE_RIGHT);
590 int y = random(SPAWN_RANGE_TOP, SPAWN_RANGE_BOTTOM);
591
592 child = CreateObject((x*TILE_W)<<CSF, (y*TILE_H)<<CSF, objtype);
593 child->dir = DOWN; // allow fall through ceiling
594 }
595 }
596
597 // spawn the smoke clouds from landing after a jump
598 // or during the death sequence.
SpawnSmoke(int count,int ytop)599 void BalfrogBoss::SpawnSmoke(int count, int ytop)
600 {
601 Object *smoke;
602
603 for(int i=0;i<count;i++)
604 {
605 int x = random(o->Left() + (4 << CSF), o->Right() - (4<<CSF));
606 int y = o->Bottom() + random(ytop<<CSF, 4<<CSF);
607
608 smoke = CreateObject(x, y, OBJ_SMOKE_CLOUD);
609 smoke->xinertia = random(-0x155, 0x155);
610 smoke->yinertia = random(-0x600, 0);
611 }
612 }
613
614
615 // switches on and off the jumping frame/sprite
SetJumpingSprite(bool enable)616 void BalfrogBoss::SetJumpingSprite(bool enable)
617 {
618 if (enable != (o->sprite == SPR_BALFROG_JUMP))
619 {
620 if (enable)
621 {
622 o->sprite = SPR_BALFROG_JUMP;
623 o->frame = FRAME_JUMPING;
624 o->y -= JUMP_SPRITE_ADJ;
625
626 frog.bbox_mode = BM_JUMPING;
627 }
628 else
629 {
630 o->sprite = SPR_BALFROG;
631 o->frame = FRAME_STAND;
632 o->y += JUMP_SPRITE_ADJ;
633
634 frog.bbox_mode = BM_STAND;
635 }
636 }
637 }
638
639
640