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