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