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