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