1 
2 #include "../stdai.h"
3 #include "x.h"
4 #include "x.fdh"
5 
6 #define STATE_X_APPEAR				1		// script-triggered: must stay constant
7 #define STATE_X_FIGHT_BEGIN			10		// script-triggered: must stay constant
8 #define STATE_X_TRAVEL				20
9 #define STATE_X_BRAKE				30
10 #define STATE_X_OPEN_DOORS			40
11 #define STATE_X_FIRE_TARGETS		50
12 #define STATE_X_FIRE_FISHIES		60
13 #define STATE_X_CLOSE_DOORS			70
14 #define STATE_X_EXPLODING			80
15 
16 #define STATE_DOOR_OPENING			10		// makes the doors open
17 #define STATE_DOOR_OPENING_PARTIAL	20		// makes the doors open part-way
18 #define STATE_DOOR_CLOSING			30		// closes the doors
19 #define STATE_DOOR_FINISHED			40		// doors are finished moving
20 
21 #define STATE_TREAD_STOPPED			20
22 #define STATE_TREAD_RUN				30
23 #define STATE_TREAD_BRAKE			40
24 
25 #define STATE_FISHSPAWNER_FIRE		10
26 #define STATE_TARGET_FIRE			10
27 
28 #define DOORS_OPEN_DIST			(32 << CSF)		// how far the doors open
29 #define DOORS_OPEN_FISHY_DIST	(20 << CSF)		// how far the doors open during fish-missile phase
30 
31 // the treads start moving at slightly different times
32 // which we change direction, etc.
33 static const int tread_turnon_times[] = { 4, 8, 10, 12 };
34 
35 
INITFUNC(AIRoutines)36 INITFUNC(AIRoutines)
37 {
38 	ONTICK(OBJ_X_FISHY_MISSILE, ai_x_fishy_missile);
39 	ONTICK(OBJ_X_DEFEATED, ai_x_defeated);
40 
41 	ONDEATH(OBJ_X_TARGET, ondeath_x_target);
42 	ONDEATH(OBJ_X_MAINOBJECT, ondeath_x_mainobject);
43 }
44 
OnMapEntry(void)45 void XBoss::OnMapEntry(void)
46 {
47 	NX_LOG("XBoss::OnMapEntry()\n");
48 
49 	memset(&X, 0, sizeof(X));
50 	memset(&body, 0, sizeof(body));
51 	memset(&treads, 0, sizeof(treads));
52 	memset(&internals, 0, sizeof(internals));
53 	memset(&doors, 0, sizeof(doors));
54 	memset(&targets, 0, sizeof(targets));
55 	memset(&fishspawners, 0, sizeof(fishspawners));
56 	npieces = 0;
57 
58 	mainobject = CreateObject(0, 0, OBJ_X_MAINOBJECT);
59 	mainobject->sprite = SPR_NULL;
60 
61 
62 	game.stageboss.object = mainobject;
63 }
64 
OnMapExit()65 void XBoss::OnMapExit()
66 {
67 	// we'll let the map loader code handle deleting all our pieces.
68 	// here's just a good-form failsafe to ensure XBoss::Run() runs no more.
69 	mainobject = NULL;
70 	game.stageboss.object = NULL;
71 }
72 
73 /*
74 void c------------------------------() {}
75 */
76 
Run()77 void XBoss::Run()
78 {
79 Object *o = mainobject;
80 int i;
81 
82 	if (!mainobject) return;
83 	if (o->state == 0 || (!X.initilized && o->state != STATE_X_APPEAR))
84 	{
85 		o->hp = 1;
86 		o->x = -(SCREEN_WIDTH << CSF);
87 		return;
88 	}
89 
90 	switch(o->state)
91 	{
92 		// script triggered us to initilize/appear
93 		// (there is a hvtrigger, right before player first walks by us
94 		// and sees us inactive, which sends us this ANP).
95 		case STATE_X_APPEAR:
96 		{
97 			if (!X.initilized)
98 			{
99 				Init();
100 				X.initilized = true;
101 			}
102 		}
103 		break;
104 
105 		// script has triggered the fight to begin
106 		case STATE_X_FIGHT_BEGIN:
107 		{
108 			o->timer = 0;
109 			o->state++;
110 		}
111 		case STATE_X_FIGHT_BEGIN+1:
112 		{
113 			if (++o->timer > 100)
114 			{
115 				FACEPLAYER;
116 				o->timer = 0;
117 				o->state = STATE_X_TRAVEL;
118 			}
119 		}
120 		break;
121 
122 		// starts the treads and moves us in the currently-facing direction
123 		case STATE_X_TRAVEL:
124 		{
125 			// count number of times we've traveled, we brake
126 			// and attack every third time.
127 			o->timer2++;
128 
129 			o->timer = 0;
130 			o->state++;
131 		}
132 		case STATE_X_TRAVEL+1:
133 		{
134 			o->timer++;
135 
136 			// trigger the treads to start moving,
137 			// and put them slightly out of sync with each-other.
138 			for(int i=0;i<4;i++)
139 			{
140 				if (o->timer == tread_turnon_times[i])
141 				{
142 					treads[i]->state = STATE_TREAD_RUN;
143 					treads[i]->dir = o->dir;
144 				}
145 			}
146 
147 			if (o->timer > 120)
148 			{
149 				// time to attack? we attack every 3rd travel
150 				// if so skid to a stop, that's the first step.
151 				if (o->timer2 >= 3)
152 				{
153 					o->timer2 = 0;
154 
155 					o->dir ^= 1;
156 					o->state = STATE_X_BRAKE;
157 					o->timer = 0;
158 				}
159 				else
160 				{
161 					// passed player? skid and turn around.
162 					if ((o->dir == RIGHT && o->x > player->x) || \
163 					 	(o->dir == LEFT  && o->x < player->x))
164 					{
165 						o->dir ^= 1;
166 						o->state = STATE_X_TRAVEL;
167 					}
168 				}
169 			}
170 		}
171 		break;
172 
173 		// skidding to a stop in preparation to attack
174 		case STATE_X_BRAKE:
175 		{
176 			o->timer = 0;
177 			o->state++;
178 		}
179 		case STATE_X_BRAKE+1:
180 		{
181 			o->timer++;
182 
183 			// trigger the treads to start braking,
184 			// and put them slightly out of sync with each-other.
185 			for(int i=0;i<4;i++)
186 			{
187 				if (o->timer == tread_turnon_times[i])
188 				{
189 					treads[i]->state = STATE_TREAD_BRAKE;
190 					treads[i]->dir = o->dir;
191 				}
192 			}
193 
194 			if (o->timer > 50)
195 			{
196 				o->state = STATE_X_OPEN_DOORS;
197 				o->timer = 0;
198 			}
199 		}
200 		break;
201 
202 		// doors opening to attack
203 		case STATE_X_OPEN_DOORS:
204 		{
205 			o->timer = 0;
206 			o->savedhp = o->hp;
207 
208 			// select type of attack depending on where we are in the battle
209 			if (!AllTargetsDestroyed())
210 			{
211 				SetStates(doors, 2, STATE_DOOR_OPENING);
212 				o->state = STATE_X_FIRE_TARGETS;
213 			}
214 			else
215 			{
216 				SetStates(doors, 2, STATE_DOOR_OPENING_PARTIAL);
217 				o->state = STATE_X_FIRE_FISHIES;
218 			}
219 		}
220 		break;
221 
222 		// firing targets (early battle)
223 		case STATE_X_FIRE_TARGETS:
224 		{
225 			if (doors[0]->state == STATE_DOOR_FINISHED)
226 			{
227 				doors[0]->state = 0;
228 				SetStates(targets, 4, STATE_TARGET_FIRE);
229 			}
230 
231 			if (++o->timer > 300 || AllTargetsDestroyed())
232 			{
233 				o->state = STATE_X_CLOSE_DOORS;
234 				o->timer = 0;
235 			}
236 		}
237 		break;
238 
239 		// firing fishy missiles (late battle)
240 		case STATE_X_FIRE_FISHIES:
241 		{
242 			if (doors[0]->state == STATE_DOOR_FINISHED)
243 			{
244 				doors[0]->state = 0;
245 
246 				SetStates(fishspawners, 4, STATE_FISHSPAWNER_FIRE);
247 				internals->flags |= FLAG_SHOOTABLE;
248 			}
249 
250 			if (++o->timer > 300 || (o->savedhp - o->hp) > 200)
251 			{
252 				o->state = STATE_X_CLOSE_DOORS;
253 				o->timer = 0;
254 			}
255 		}
256 		break;
257 
258 		// doors closing after attack
259 		case STATE_X_CLOSE_DOORS:
260 		{
261 			o->timer = 0;
262 			o->state++;
263 
264 			SetStates(doors, 2, STATE_DOOR_CLOSING);
265 		}
266 		case STATE_X_CLOSE_DOORS+1:
267 		{
268 			if (doors[0]->state == STATE_DOOR_FINISHED)
269 			{
270 				doors[0]->state = 0;
271 
272 				// just turn off everything for both types of attacks;
273 				// turning off the attack type that wasn't enabled isn't harmful.
274 				SetStates(targets, 4, 0);
275 				SetStates(fishspawners, 4, 0);
276 				internals->flags &= ~FLAG_SHOOTABLE;
277 			}
278 
279 			if (++o->timer > 50)
280 			{
281 				FACEPLAYER;
282 				o->state = STATE_X_TRAVEL;
283 				o->timer = 0;
284 			}
285 		}
286 		break;
287 
288 		// exploding
289 		case STATE_X_EXPLODING:
290 		{
291 			SetStates(fishspawners, 4, 0);
292 			KillObjectsOfType(OBJ_X_FISHY_MISSILE);
293 
294 			StartScript(1000);
295 			o->timer = 0;
296 			o->state++;
297 		}
298 		case STATE_X_EXPLODING+1:
299 		{
300 			game.quaketime = 2;
301 			o->timer++;
302 
303 			if ((o->timer % 8) == 0)
304 				sound(SND_ENEMY_HURT_BIG);
305 
306 			SmokePuff(o->CenterX() + (random(-72, 72) << CSF),
307 					  o->CenterY() + (random(-64, 64) << CSF));
308 
309 			if (o->timer > 100)
310 			{
311 				starflash.Start(o->CenterX(), o->CenterY());
312 				sound(SND_EXPLOSION1);
313 				o->timer = 0;
314 				o->state++;
315 			}
316 		}
317 		break;
318 		case STATE_X_EXPLODING+2:
319 		{
320 			game.quaketime = 40;
321 			if (++o->timer > 50)
322 			{
323 				CreateObject(o->x, o->y - (24 << CSF), OBJ_X_DEFEATED);
324 				DeleteMonster();
325 				return;
326 			}
327 		}
328 		break;
329 	}
330 
331 	// call AI for all tread pieces
332 	for(i=0;i<4;i++)
333 	{
334 		run_tread(i);
335 		run_fishy_spawner(i);
336 	}
337 }
338 
339 // moved this to aftermove so xinertia on treads is already applied
340 // when we calculate the main object position.
RunAftermove()341 void XBoss::RunAftermove()
342 {
343 Object *o = mainobject;
344 int i;
345 
346 	if (!mainobject || mainobject->state == 0 || !X.initilized)
347 		return;
348 
349 	// main object pulled along as treads move
350 	int tread_center = (treads[UL]->x + treads[UR]->x + \
351 					 	treads[LL]->x + treads[LR]->x) / 4;
352 	o->x += (tread_center - o->x) / 16;
353 
354 	run_internals();
355 
356 	for(i=0;i<4;i++)
357 	{
358 		run_body(i);
359 		run_target(i);
360 	}
361 
362 	for(i=0;i<2;i++)
363 	{
364 		run_door(i);
365 	}
366 }
367 
ondeath_x_mainobject(Object * internals)368 void ondeath_x_mainobject(Object *internals)
369 {
370 	// do nothing really, this function is just there to override
371 	// the default so we are not destroyed--our 0 HP level will
372 	// be noticed in run_internals() and trigger the defeat sequence.
373 	internals->flags &= ~FLAG_SHOOTABLE;
374 }
375 
376 /*
377 void c------------------------------() {}
378 */
379 
run_tread(int index)380 void XBoss::run_tread(int index)
381 {
382 	Object *o = treads[index];
383 
384 	switch(o->state)
385 	{
386 		case 0:
387 		{
388 			o->flags |= (FLAG_SOLID_BRICK | FLAG_INVULNERABLE | FLAG_NOREARTOPATTACK);
389 			o->state = STATE_TREAD_STOPPED;
390 		}
391 		case STATE_TREAD_STOPPED:
392 		{
393 			o->frame = 0;
394 			o->damage = 0;
395 			o->flags &= ~FLAG_BOUNCY;
396 		}
397 		break;
398 
399 		case STATE_TREAD_RUN:
400 		{
401 			o->flags |= FLAG_BOUNCY;
402 			o->timer = 0;
403 			o->frame = 2;
404 			o->animtimer = 0;
405 
406 			o->state++;
407 		}
408 		case STATE_TREAD_RUN+1:
409 		{
410 			ANIMATE(0, 2, 3);
411 			XACCEL(0x20);
412 
413 			if (++o->timer > 30)
414 			{
415 				o->flags &= ~FLAG_BOUNCY;
416 				o->frame = 0;
417 				o->animtimer = 0;
418 				o->state++;
419 			}
420 		}
421 		break;
422 		case STATE_TREAD_RUN+2:
423 		{
424 			ANIMATE(1, 0, 1);
425 			XACCEL(0x20);
426 
427 			o->timer++;
428 		}
429 		break;
430 
431 		case STATE_TREAD_BRAKE:
432 		{
433 			o->frame = 2;
434 			o->animtimer = 0;
435 
436 			o->flags |= FLAG_BOUNCY;
437 			o->state++;
438 		}
439 		case STATE_TREAD_BRAKE+1:
440 		{
441 			ANIMATE(0, 2, 3);
442 			XACCEL(0x20);
443 
444 			if ((o->dir == RIGHT && o->xinertia > 0) || \
445 				(o->dir == LEFT && o->xinertia < 0))
446 			{
447 				o->xinertia = 0;
448 				o->state = STATE_TREAD_STOPPED;
449 			}
450 		}
451 		break;
452 	}
453 
454 	// make motor noise
455 	switch(o->state)
456 	{
457 		case STATE_TREAD_RUN+1:
458 		case STATE_TREAD_BRAKE+1:
459 		{
460 			if (o->timer & 1)
461 				sound(SND_MOTOR_SKIP);
462 		}
463 		break;
464 
465 		case STATE_TREAD_RUN+2:
466 		{
467 			if ((o->timer % 4) == 1)
468 				sound(SND_MOTOR_RUN);
469 		}
470 		break;
471 	}
472 
473 	// determine if player is in a position where he could get run over.
474 	if (o->state > STATE_TREAD_STOPPED && o->xinertia != 0)
475 	{
476 		if (abs(player->y - o->CenterY()) <= (5 << CSF))
477 			o->damage = 10;
478 		else
479 			o->damage = 0;
480 	}
481 	else
482 	{
483 		o->damage = 0;
484 	}
485 
486 	LIMITX(0x400);
487 }
488 
run_body(int i)489 void XBoss::run_body(int i)
490 {
491 	// set body position based on main object position and
492 	// our linked tread position. first get the center point we should be at...
493 	body[i]->x = (mainobject->x + treads[i]->x) / 2;
494 	body[i]->y = (mainobject->y + treads[i]->y) / 2;
495 
496 	// ...and place our center pixel at those coordinates.
497 	int dx = (sprites[body[i]->sprite].w / 2) - 8;
498 	int dy = (sprites[body[i]->sprite].h / 2) - 8;
499 	body[i]->x -= dx << CSF;
500 	body[i]->y -= dy << CSF;
501 
502 	// tweaks
503 	if (i == UL || i == LL)
504 	{
505 		body[i]->x -= (6 << CSF);
506 	}
507 	else
508 	{
509 		body[i]->x += (7 << CSF);
510 	}
511 
512 	if (i == LL || i == LR)
513 	{
514 		body[i]->y += (8 << CSF);
515 	}
516 
517 }
518 
run_internals()519 void XBoss::run_internals()
520 {
521 	internals->x = mainobject->x;
522 	internals->y = mainobject->y;
523 
524 	// select frame
525 	if (internals->shaketime & 2)
526 	{
527 		internals->frame = 1;
528 	}
529 	else
530 	{
531 		internals->frame = (mainobject->state < 10) ? 2 : 0;
532 	}
533 
534 	// link damage to main object
535 	if (internals->hp < 1000)
536 	{
537 		mainobject->DealDamage(1000 - internals->hp);
538 		internals->hp = 1000;
539 	}
540 
541 	// trigger explosion sequence when monster defeated
542 	if (mainobject->hp <= 0 && mainobject->state < STATE_X_EXPLODING)
543 	{
544 		mainobject->shaketime = 150;
545 		mainobject->state = STATE_X_EXPLODING;
546 	}
547 }
548 
run_door(int index)549 void XBoss::run_door(int index)
550 {
551 	Object *o = doors[index];
552 
553 	switch(o->state)
554 	{
555 		// doors opening all the way
556 		case STATE_DOOR_OPENING:
557 		{
558 			o->xmark += (1 << CSF);
559 
560 			if (o->xmark >= DOORS_OPEN_DIST)
561 			{
562 				o->xmark = DOORS_OPEN_DIST;
563 				o->state = STATE_DOOR_FINISHED;
564 			}
565 		}
566 		break;
567 
568 		// doors opening partially for fish-missile launchers to fire
569 		case STATE_DOOR_OPENING_PARTIAL:
570 		{
571 			o->xmark += (1 << CSF);
572 
573 			if (o->xmark >= DOORS_OPEN_FISHY_DIST)
574 			{
575 				o->xmark = DOORS_OPEN_FISHY_DIST;
576 				o->state = STATE_DOOR_FINISHED;
577 			}
578 		}
579 		break;
580 
581 		// doors closing
582 		case STATE_DOOR_CLOSING:
583 		{
584 			o->xmark -= (1 << CSF);
585 			if (o->xmark <= 0)
586 			{
587 				o->xmark = 0;
588 				o->state = STATE_DOOR_FINISHED;
589 			}
590 		}
591 		break;
592 
593 		// this is a signal to the main object that the doors
594 		// are finished with the last command.
595 		case STATE_DOOR_FINISHED:
596 		break;
597 	}
598 
599 	// set position relative to main object.
600 	// doors open in opposite directions.
601 	if (o->dir == LEFT) o->x = (mainobject->x - o->xmark);
602 				   else o->x = (mainobject->x + o->xmark);
603 
604 	o->y = mainobject->y;
605 }
606 
run_fishy_spawner(int index)607 void XBoss::run_fishy_spawner(int index)
608 {
609 	Object *o = fishspawners[index];
610 
611 	switch(o->state)
612 	{
613 		case STATE_FISHSPAWNER_FIRE:
614 		{
615 			o->timer = 20 + (index * 20);
616 			o->state++;
617 		}
618 		case STATE_FISHSPAWNER_FIRE+1:
619 		{
620 			if (o->timer)
621 			{
622 				o->timer--;
623 				break;
624 			}
625 
626 			// keep appropriate position relative to main object
627 			//                               UL          UR         LL         LR
628 			static const int xoffs[]   = { -64 <<CSF,  76 <<CSF, -64 <<CSF,  76 <<CSF };
629 			static const int yoffs[]   = {  27 <<CSF,  27 <<CSF, -16 <<CSF, -16 <<CSF };
630 			o->x = (mainobject->x + xoffs[index]);
631 			o->y = (mainobject->y + yoffs[index]);
632 
633 			Object *missile = CreateObject(o->x, o->y, OBJ_X_FISHY_MISSILE);
634 			missile->dir = index;
635 
636 			sound(SND_EM_FIRE);
637 			o->timer = 120;
638 		}
639 		break;
640 	}
641 }
642 
run_target(int index)643 void XBoss::run_target(int index)
644 {
645 	Object *o = targets[index];
646 
647 	// has this target been destroyed?
648 	// (we don't really kill the object until the battle is over,
649 	// to avoid having to deal with dangling pointers).
650 	if (o->invisible)
651 		return;
652 
653 	switch(o->state)
654 	{
655 		case 0:
656 			o->flags &= ~FLAG_SHOOTABLE;
657 			o->frame &= 3;
658 			o->state = 1;
659 		break;
660 
661 		case STATE_TARGET_FIRE:
662 		{
663 			o->timer = 40 + (index * 10);
664 			o->flags |= FLAG_SHOOTABLE;
665 			o->state++;
666 		}
667 		case STATE_TARGET_FIRE+1:
668 		{
669 			if (--o->timer <= 16)
670 			{
671 				// flash shortly before firing
672 				if (o->timer & 2) o->frame |= 4;
673 							 else o->frame &= 3;
674 
675 				if (o->timer <= 0)
676 				{
677 					o->timer = 40;
678 					EmFireAngledShot(o, OBJ_GAUDI_FLYING_SHOT, 2, 0x500);
679 					sound(SND_EM_FIRE);
680 				}
681 			}
682 		}
683 		break;
684 	}
685 
686 	// keep appropriate position on internals
687 	//                               UL          UR         LL         LR
688 	static const int xoffs[] = { -22 <<CSF,  28 <<CSF, -15 <<CSF,  17 <<CSF };
689 	static const int yoffs[] = { -16 <<CSF, -16 <<CSF,  14 <<CSF,  14 <<CSF };
690 
691 	o->x = internals->x + xoffs[index];
692 	o->y = internals->y + yoffs[index];
693 }
694 
ondeath_x_target(Object * o)695 void ondeath_x_target(Object *o)
696 {
697 	SmokeClouds(o, 8, 8, 8);
698 	sound(SND_LITTLE_CRASH);
699 
700 	o->flags &= ~FLAG_SHOOTABLE;
701 	o->invisible = true;
702 }
703 
704 /*
705 void c------------------------------() {}
706 */
707 
Init()708 void XBoss::Init()
709 {
710 int i;
711 
712 	mainobject->hp = 700;
713 	mainobject->state = 1;
714 	mainobject->x = (128 * TILE_W) << CSF;
715 	mainobject->y = (200 << CSF);
716 	mainobject->flags = FLAG_IGNORE_SOLID;
717 
718 	// put X behind the flying gaudis
719 	mainobject->PushBehind(lowestobject);
720 
721 	// create body pieces
722 	for(i=3;i>=0;i--)
723 	{
724 		body[i] = CreatePiece(0, 0, OBJ_X_BODY);
725 		body[i]->dir   = (i == UL || i == LL) ? LEFT : RIGHT;
726 		body[i]->frame = (i == LL || i == LR) ? 1 : 0;
727 	}
728 
729 	// create treads
730 	for(i=0;i<4;i++)
731 	{
732 		int x = (i == UL || i == LL) ? 0xf8000 : 0x108000;
733 		int y = (i == UL || i == UR) ? 0x12000 : (0x20000 - (16 << CSF));
734 		int sprite = (i == UL || i == UR) ? SPR_X_TREAD_UPPER : SPR_X_TREAD_LOWER;
735 
736 		treads[i] = CreateTread(x, y, sprite);
737 		treads[i]->smushdamage = 10;
738 	}
739 
740 	// create internals
741 	internals = CreatePiece(0, 0, OBJ_X_INTERNALS);
742 	internals->hp = 1000;
743 	internals->flags &= ~FLAG_SHOW_FLOATTEXT;
744 
745 	// create targets
746 	for(i=0;i<4;i++)
747 	{
748 		targets[i] = CreatePiece(0, 0, OBJ_X_TARGET);
749 		targets[i]->sprite = SPR_X_TARGETS;
750 		targets[i]->frame = i;
751 		targets[i]->hp = 60;
752 		targets[i]->flags &= ~FLAG_SHOW_FLOATTEXT;
753 	}
754 
755 	// create fishy-missile shooters
756 	for(i=0;i<4;i++)
757 	{
758 		fishspawners[i] = CreatePiece(0, 0, OBJ_X_FISHY_SPAWNER);
759 		fishspawners[i]->sprite = SPR_NULL;
760 		fishspawners[i]->invisible = true;
761 		fishspawners[i]->flags = 0;
762 	}
763 
764 	// create doors
765 	for(i=0;i<2;i++)
766 	{
767 		doors[i] = CreatePiece(0, 0, OBJ_X_DOOR);
768 		doors[i]->sprite = SPR_X_DOOR;
769 		doors[i]->dir = i;
770 	}
771 
772 	sprites[SPR_X_DOOR].frame[0].dir[LEFT].drawpoint.x = 40;
773 	sprites[SPR_X_DOOR].frame[0].dir[LEFT].drawpoint.y = 16;
774 	sprites[SPR_X_DOOR].frame[0].dir[RIGHT].drawpoint.x = -9;
775 	sprites[SPR_X_DOOR].frame[0].dir[RIGHT].drawpoint.y = 16;
776 }
777 
778 // create an object and record it as a piece of the monster
779 // so we can delete all the pieces later via DeleteMonster().
CreatePiece(int x,int y,int object)780 Object *XBoss::CreatePiece(int x, int y, int object)
781 {
782 	Object *piece = CreateObject(x, y, object);
783 	piecelist[npieces++] = piece;
784 	piece->PushBehind(mainobject);
785 	return piece;
786 }
787 
788 // create an object of type OBJ_X_TREAD and give it the specified sprite.
CreateTread(int x,int y,int sprite)789 Object *XBoss::CreateTread(int x, int y, int sprite)
790 {
791 	Object *tread = CreatePiece(x, y, OBJ_X_TREAD);
792 	tread->sprite = sprite;
793 	return tread;
794 }
795 
796 // delete all pieces of the monster
DeleteMonster()797 void XBoss::DeleteMonster()
798 {
799 	for(int i=0;i<npieces;i++)
800 		piecelist[i]->Delete();
801 
802 	mainobject->Delete();
803 	mainobject = NULL;
804 	game.stageboss.object = NULL;
805 }
806 
807 // return true if all the targets behind the doors have been destroyed.
AllTargetsDestroyed()808 bool XBoss::AllTargetsDestroyed()
809 {
810 	for(int i=0;i<4;i++)
811 	{
812 		if (!targets[i]->invisible)
813 			return false;
814 	}
815 
816 	return true;
817 }
818 
819 /*
820 void c------------------------------() {}
821 */
822 
823 // sets state on an array on objects
SetStates(Object * objects[],int nobjects,int state)824 void XBoss::SetStates(Object *objects[], int nobjects, int state)
825 {
826 	for(int i=0;i<nobjects;i++)
827 		objects[i]->state = state;
828 }
829 
830 // sets direction on an array on objects
SetDirs(Object * objects[],int nobjects,int dir)831 void XBoss::SetDirs(Object *objects[], int nobjects, int dir)
832 {
833 	for(int i=0;i<nobjects;i++)
834 		objects[i]->dir = dir;
835 }
836 
837 /*
838 void c------------------------------() {}
839 */
840 
ai_x_fishy_missile(Object * o)841 void ai_x_fishy_missile(Object *o)
842 {
843 	if (o->state == 0)
844 	{
845 		static const int angle_for_dirs[] = { 160, 224, 96, 32 };
846 
847 		o->angle = angle_for_dirs[o->dir];
848 		o->dir = RIGHT;
849 
850 		o->state = 1;
851 	}
852 
853 	vector_from_angle(o->angle, 0x400, &o->xinertia, &o->yinertia);
854 	int desired_angle = GetAngle(o->x, o->y, player->x, player->y);
855 
856 	if (o->angle >= desired_angle)
857 	{
858 		if ((o->angle - desired_angle) < 128)
859 		{
860 			o->angle--;
861 		}
862 		else
863 		{
864 			o->angle++;
865 		}
866 	}
867 	else
868 	{
869 		if ((o->angle - desired_angle) < 128)
870 		{
871 			o->angle++;
872 		}
873 		else
874 		{
875 			o->angle--;
876 		}
877 	}
878 
879 	// smoke trails
880 	if (++o->timer2 > 2)
881 	{
882 		o->timer2 = 0;
883 		Caret *c = effect(o->ActionPointX(), o->ActionPointY(), EFFECT_SMOKETRAIL_SLOW);
884 		c->xinertia = -o->xinertia >> 2;
885 		c->yinertia = -o->yinertia >> 2;
886 	}
887 
888 	o->frame = (o->angle + 16) / 32;
889 	if (o->frame > 7) o->frame = 7;
890 }
891 
892 
893 // this is the cat that falls out after you defeat him
ai_x_defeated(Object * o)894 void ai_x_defeated(Object *o)
895 {
896 	o->timer++;
897 	if ((o->timer % 4) == 0)
898 	{
899 		SmokeClouds(o, 1, 16, 16);
900 	}
901 
902 	switch(o->state)
903 	{
904 		case 0:
905 		{
906 			SmokeClouds(o, 8, 16, 16);
907 			o->state = 1;
908 		}
909 		case 1:
910 		{
911 			if (o->timer > 50)
912 			{
913 				o->state = 2;
914 				o->xinertia = -0x100;
915 			}
916 
917 			// three-position shake
918 			o->x += (o->timer & 2) ? (1 << CSF) : -(1 << CSF);
919 		}
920 		break;
921 
922 		case 2:
923 		{
924 			o->yinertia += 0x40;
925 			if (o->y > (map.ysize * TILE_H) << CSF) o->Delete();
926 		}
927 		break;
928 	}
929 }
930 
931 
932 
933 
934