1 /*
2 Copyright (C) 2009-2021 Parallel Realities
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 */
19
20 #include "../headers.h"
21
22 #include "../audio/audio.h"
23 #include "../audio/music.h"
24 #include "../collisions.h"
25 #include "../credits.h"
26 #include "../custom_actions.h"
27 #include "../entity.h"
28 #include "../game.h"
29 #include "../geometry.h"
30 #include "../graphics/animation.h"
31 #include "../graphics/decoration.h"
32 #include "../hud.h"
33 #include "../item/key_items.h"
34 #include "../map.h"
35 #include "../player.h"
36 #include "../projectile.h"
37 #include "../system/error.h"
38 #include "../system/properties.h"
39 #include "../system/random.h"
40 #include "../world/target.h"
41
42 extern Entity *self, player;
43
44 static void bodyWait(void);
45 static void initialise(void);
46 static void headWait(void);
47 static void riseUp(void);
48 static void createBody(void);
49 static void setSnakePosition(int, int);
50 static void alignBodyToHead(void);
51 static void biteAttackInit(void);
52 static void biteAttackWindUp(void);
53 static void biteAttack(void);
54 static void returnToStart(void);
55 static void attackFinished(void);
56 static void die(void);
57 static void dieWait(void);
58 static void fallToGround(void);
59 static void changeSidesInit(void);
60 static void changeSides(void);
61 static void riseUpWait(void);
62 static void takeDamage(Entity *, int);
63 static void shotAttackInit(void);
64 static void shotAttackWindUp(void);
65 static void shotAttack(void);
66 static void specialShotMove(void);
67 static void specialShotWait(void);
68 static void specialShotBlock(Entity *);
69 static void specialShotTouch(Entity *);
70 static void biteReactToBlock(Entity *);
71 static void crushAttackInit(void);
72 static void crushAttackMoveToPosition(void);
73 static void crushAttack(void);
74 static void crushAttackFinish(void);
75 static void addSmokeAlongBody(void);
76 static void stunned(void);
77 static void crushAttackHit(Entity *);
78 static void bodyTakeDamage(Entity *, int);
79 static void starWait(void);
80 static void creditsMove(void);
81 static void creditsMoveOffScreen(void);
82
addSnakeBoss(int x,int y,char * name)83 Entity *addSnakeBoss(int x, int y, char *name)
84 {
85 Entity *head;
86
87 head = getFreeEntity();
88
89 if (head == NULL)
90 {
91 showErrorAndExit("No free slots to add the Snake Boss");
92 }
93
94 loadProperties(name, head);
95
96 head->x = x;
97 head->y = y;
98
99 head->action = &initialise;
100
101 head->draw = &drawLoopingAnimationToMap;
102 head->touch = &entityTouch;
103 head->die = ¨
104 head->takeDamage = &takeDamage;
105
106 head->creditsAction = &creditsMove;
107
108 head->type = ENEMY;
109
110 head->active = FALSE;
111
112 setEntityAnimation(head, "STAND");
113
114 return head;
115 }
116
bodyWait()117 static void bodyWait()
118 {
119 if (self->head->flags & FLASH)
120 {
121 self->flags |= FLASH;
122 }
123
124 else
125 {
126 self->flags &= ~FLASH;
127 }
128
129 checkToMap(self);
130
131 if (self->head->inUse == FALSE)
132 {
133 self->inUse = FALSE;
134 }
135 }
136
initialise()137 static void initialise()
138 {
139 Target *t;
140
141 self->flags |= NO_DRAW;
142
143 if (self->active == TRUE)
144 {
145 if (cameraAtMinimum())
146 {
147 createBody();
148
149 centerMapOnEntity(NULL);
150
151 t = getTargetByName("SNAKE_BOSS_TARGET_RIGHT");
152
153 if (t == NULL)
154 {
155 showErrorAndExit("Snake boss cannot find target");
156 }
157
158 setSnakePosition(t->x, t->y);
159
160 self->targetY = self->y - 64;
161
162 self->thinkTime = 60;
163
164 self->face = LEFT;
165
166 self->flags |= LIMIT_TO_SCREEN;
167
168 self->action = &riseUp;
169
170 playDefaultBossMusic();
171
172 initBossHealthBar();
173
174 setContinuePoint(FALSE, self->name, NULL);
175 }
176 }
177 }
178
headWait()179 static void headWait()
180 {
181 int x;
182
183 /* Sway back and forth */
184
185 self->dirX += 0.5;
186
187 if (self->dirX >= 360)
188 {
189 self->dirX = 0;
190 }
191
192 x = 24;
193
194 self->x = self->targetX + (sin(DEG_TO_RAD(self->dirX)) * x);
195
196 alignBodyToHead();
197
198 self->thinkTime--;
199
200 if (self->thinkTime <= 0 && player.health > 0)
201 {
202 self->thinkTime = 0;
203
204 x = prand() % 4;
205
206 switch (x)
207 {
208 case 0:
209 self->action = &biteAttackInit;
210 break;
211
212 case 1:
213 self->action = &changeSidesInit;
214 break;
215
216 case 2:
217 self->action = &shotAttackInit;
218 break;
219
220 default:
221 self->action = &crushAttackInit;
222 break;
223 }
224 }
225
226 if (prand() % 180 == 0)
227 {
228 playSoundToMap("sound/boss/snake_boss/hiss", BOSS_CHANNEL, self->x, self->y, 0);
229 }
230 }
231
riseUp()232 static void riseUp()
233 {
234 Entity *e, *smoke;
235
236 /*facePlayer();*/
237
238 self->thinkTime--;
239
240 if (self->thinkTime <= 0)
241 {
242 self->damage = 1;
243
244 self->y -= 2;
245
246 self->thinkTime = 0;
247
248 self->flags &= ~NO_DRAW;
249
250 self->takeDamage = &takeDamage;
251 }
252
253 alignBodyToHead();
254
255 e = self->target;
256
257 while (e != NULL)
258 {
259 if (e->target == NULL)
260 {
261 smoke = addSmoke(e->x + (prand() % self->w) * (prand() % 2 == 0 ? -1 : 1), e->y + prand() % e->h, "decoration/dust");
262
263 if (smoke != NULL)
264 {
265 smoke->dirY = 0;
266 }
267 }
268
269 e = e->target;
270 }
271
272 if (self->y <= self->targetY)
273 {
274 self->y = self->targetY;
275
276 self->targetX = self->x;
277
278 self->dirX = 0;
279
280 self->thinkTime = 120;
281
282 self->action = &headWait;
283 }
284 }
285
createBody()286 static void createBody()
287 {
288 char bodyName[MAX_VALUE_LENGTH];
289 int i;
290 Entity **body, *head;
291
292 body = malloc(self->mental * sizeof(Entity *));
293
294 if (body == NULL)
295 {
296 showErrorAndExit("Failed to allocate a whole %d bytes for Snake Boss body...", self->mental * (int)sizeof(Entity *));
297 }
298
299 SNPRINTF(bodyName, sizeof(bodyName), "%s_body", self->name);
300
301 for (i=self->mental-1;i>=0;i--)
302 {
303 body[i] = getFreeEntity();
304
305 if (body[i] == NULL)
306 {
307 showErrorAndExit("No free slots to add a Snake Boss body part");
308 }
309
310 loadProperties(bodyName, body[i]);
311
312 body[i]->x = self->x;
313 body[i]->y = self->y;
314
315 body[i]->action = &bodyWait;
316
317 body[i]->draw = &drawLoopingAnimationToMap;
318 body[i]->touch = &entityTouch;
319 body[i]->die = &entityDieNoDrop;
320 body[i]->takeDamage = &bodyTakeDamage;
321
322 body[i]->creditsAction = &bodyWait;
323
324 body[i]->type = ENEMY;
325
326 body[i]->active = FALSE;
327
328 body[i]->flags |= NO_DRAW;
329
330 setEntityAnimation(body[i], "STAND");
331 }
332
333 /* Recreate the head so that it's on top */
334
335 head = getFreeEntity();
336
337 if (head == NULL)
338 {
339 showErrorAndExit("No free slots to add a Snake Boss head");
340 }
341
342 *head = *self;
343
344 self->inUse = FALSE;
345
346 self = head;
347
348 /* Link the sections */
349
350 for (i=self->mental-1;i>=0;i--)
351 {
352 if (i == 0)
353 {
354 self->target = body[i];
355 }
356
357 else
358 {
359 body[i - 1]->target = body[i];
360 }
361
362 body[i]->head = self;
363 }
364
365 free(body);
366 }
367
biteAttackInit()368 static void biteAttackInit()
369 {
370 /*facePlayer();*/
371
372 setEntityAnimation(self, "ATTACK_1");
373
374 self->targetX = self->endX + (self->face == LEFT ? -32 : 32);
375 self->targetY = self->endY - 32;
376
377 calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
378
379 self->dirX *= 4;
380 self->dirY *= 4;
381
382 self->action = &biteAttackWindUp;
383
384 self->maxThinkTime = 1 + prand() % 3;
385 }
386
biteAttackWindUp()387 static void biteAttackWindUp()
388 {
389 checkToMap(self);
390
391 if (atTarget())
392 {
393 self->targetX = self->face == RIGHT ? self->x + 320 - self->w - 1 : self->x - 320;
394
395 self->action = &biteAttack;
396
397 self->reactToBlock = &biteReactToBlock;
398
399 self->dirX = (self->targetX < self->x ? -self->speed * 2 : self->speed * 2);
400 }
401
402 alignBodyToHead();
403 }
404
biteAttack()405 static void biteAttack()
406 {
407 checkToMap(self);
408
409 if (fabs(self->x - self->targetX) < self->speed)
410 {
411 self->x = self->targetX;
412
413 self->maxThinkTime--;
414
415 if (self->maxThinkTime <= 0)
416 {
417 self->action = &attackFinished;
418 }
419
420 else
421 {
422 self->targetX = self->endX + (self->face == LEFT ? -32 : 32);
423 self->targetY = self->endY - 32;
424
425 calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
426
427 self->dirX *= 4;
428 self->dirY *= 4;
429
430 self->action = &biteAttackWindUp;
431 }
432 }
433
434 alignBodyToHead();
435 }
436
shotAttackInit()437 static void shotAttackInit()
438 {
439 /*facePlayer();*/
440
441 setEntityAnimation(self, "ATTACK_1");
442
443 self->targetX = self->endX + (self->face == LEFT ? -50 : 50);
444 self->targetY = self->endY - 32;
445
446 calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
447
448 self->dirX *= 4;
449 self->dirY *= 4;
450
451 self->action = &shotAttackWindUp;
452
453 self->flags |= UNBLOCKABLE;
454
455 self->startX = 0;
456 }
457
shotAttackWindUp()458 static void shotAttackWindUp()
459 {
460 checkToMap(self);
461
462 if (atTarget())
463 {
464 self->maxThinkTime = 5;
465
466 self->thinkTime = 0;
467
468 self->action = &shotAttack;
469 }
470
471 alignBodyToHead();
472 }
473
shotAttack()474 static void shotAttack()
475 {
476 Entity *e;
477
478 self->thinkTime--;
479
480 if (self->thinkTime <= 0)
481 {
482 playSoundToMap("sound/boss/snake_boss/snake_boss_shot", BOSS_CHANNEL, self->x, self->y, 0);
483
484 if (prand() % 4 == 0 && self->startX == 0)
485 {
486 e = addProjectile("boss/snake_boss_special_shot", self, self->x + self->w / 2, self->y + self->h / 2, (self->face == RIGHT ? 7 : -7), 0);
487
488 e->action = &specialShotMove;
489
490 e->reactToBlock = &specialShotBlock;
491
492 self->startX = 1;
493 }
494
495 else
496 {
497 e = addProjectile("boss/snake_boss_normal_shot", self, self->x + self->w / 2, self->y + self->h / 2, (self->face == RIGHT ? 7 : -7), 0);
498
499 e->reactToBlock = &bounceOffShield;
500 }
501
502 e->y -= e->h / 2;
503
504 self->x += (self->face == LEFT ? 10 : -10);
505
506 self->maxThinkTime--;
507
508 if (self->maxThinkTime <= 0)
509 {
510 self->action = &attackFinished;
511 }
512
513 else
514 {
515 self->thinkTime = 10;
516 }
517 }
518
519 alignBodyToHead();
520 }
521
setSnakePosition(int x,int y)522 static void setSnakePosition(int x, int y)
523 {
524 Entity *e;
525
526 self->x = x;
527 self->y = y;
528
529 self->endX = x;
530 self->endY = y;
531
532 e = self->target;
533
534 while (e != NULL)
535 {
536 e->y = y;
537 e->x = x + 23;
538
539 e = e->target;
540 }
541 }
542
changeSidesInit()543 static void changeSidesInit()
544 {
545 self->targetX = self->endX;
546 self->targetY = self->endY;
547
548 calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
549
550 self->dirX *= 2;
551 self->dirY *= 2;
552
553 self->action = &changeSides;
554 }
555
changeSides()556 static void changeSides()
557 {
558 int side;
559 Entity *e, *smoke;
560 Target *t;
561
562 self->x += self->dirX;
563 self->y += self->dirY;
564
565 if (atTarget())
566 {
567 self->flags |= NO_DRAW;
568
569 self->takeDamage = NULL;
570
571 self->targetX = self->x;
572
573 self->dirX = 0;
574
575 self->thinkTime = 120;
576
577 side = prand() % 2;
578
579 if (side == 0)
580 {
581 t = getTargetByName("SNAKE_BOSS_TARGET_LEFT");
582
583 self->face = RIGHT;
584 }
585
586 else
587 {
588 t = getTargetByName("SNAKE_BOSS_TARGET_RIGHT");
589
590 self->face = LEFT;
591 }
592
593 if (t == NULL)
594 {
595 showErrorAndExit("Snake boss cannot find target");
596 }
597
598 self->damage = 0;
599
600 setSnakePosition(t->x, t->y);
601
602 self->targetX = self->x;
603 self->targetY = self->y - 64;
604
605 self->thinkTime = 120;
606
607 self->action = &riseUpWait;
608 }
609
610 else
611 {
612 e = self->target;
613
614 while (e != NULL)
615 {
616 if (e->target == NULL)
617 {
618 smoke = addSmoke(e->x + (prand() % self->w) * (prand() % 2 == 0 ? -1 : 1), e->y + prand() % e->h, "decoration/dust");
619
620 if (smoke != NULL)
621 {
622 smoke->dirY = 0;
623 }
624 }
625
626 e = e->target;
627 }
628 }
629
630 alignBodyToHead();
631 }
632
crushAttackInit()633 static void crushAttackInit()
634 {
635 self->maxThinkTime = 5;
636
637 self->targetX = self->endX + (self->face == LEFT ? -75 : 75);
638 self->targetY = self->endY - 128;
639
640 self->action = &crushAttackMoveToPosition;
641 }
642
crushAttackMoveToPosition()643 static void crushAttackMoveToPosition()
644 {
645 calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
646
647 self->dirX *= 9;
648 self->dirY *= 9;
649
650 checkToMap(self);
651
652 if (atTarget())
653 {
654 self->y = self->targetY;
655
656 self->x = self->targetX;
657
658 self->dirY = 0;
659
660 self->dirX = 0;
661
662 self->action = &crushAttack;
663 }
664
665 alignBodyToHead();
666 }
667
crushAttack()668 static void crushAttack()
669 {
670 self->dirX = 0;
671
672 self->dirY = 8;
673
674 self->flags |= UNBLOCKABLE;
675
676 self->flags &= ~FLY;
677
678 self->action = &crushAttackFinish;
679
680 self->touch = &crushAttackHit;
681
682 self->thinkTime = self->maxThinkTime == 1 ? 60 : 15;
683 }
684
crushAttackFinish()685 static void crushAttackFinish()
686 {
687 long onGround = self->flags & ON_GROUND;
688
689 checkToMap(self);
690
691 if (self->flags & ON_GROUND)
692 {
693 if (onGround == 0)
694 {
695 addSmokeAlongBody();
696
697 self->touch = &entityTouch;
698 }
699
700 self->thinkTime--;
701
702 if (self->thinkTime <= 0)
703 {
704 self->maxThinkTime--;
705
706 if (self->maxThinkTime <= 0)
707 {
708 self->action = &attackFinished;
709 }
710
711 else
712 {
713 self->targetX = self->targetX + (self->face == LEFT ? -75 : 75);
714 self->targetY = self->endY - 128;
715
716 self->action = &crushAttackMoveToPosition;
717 }
718 }
719 }
720
721 alignBodyToHead();
722 }
723
crushAttackHit(Entity * other)724 static void crushAttackHit(Entity *other)
725 {
726 if (other->type == PLAYER)
727 {
728 self->thinkTime = 0;
729
730 self->maxThinkTime = 0;
731 }
732 }
733
riseUpWait()734 static void riseUpWait()
735 {
736 self->thinkTime--;
737
738 if (self->thinkTime <= 0)
739 {
740 self->thinkTime = 120;
741
742 self->action = &riseUp;
743 }
744 }
745
attackFinished()746 static void attackFinished()
747 {
748 setEntityAnimation(self, "STAND");
749
750 self->flags &= ~UNBLOCKABLE;
751
752 self->flags |= FLY;
753
754 self->targetX = self->endX;
755 self->targetY = self->endY - 64;
756
757 calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
758
759 self->dirX *= self->speed;
760 self->dirY *= self->speed;
761
762 self->action = &returnToStart;
763 }
764
returnToStart()765 static void returnToStart()
766 {
767 checkToMap(self);
768
769 if (atTarget())
770 {
771 self->y = self->targetY;
772
773 self->targetX = self->x;
774
775 self->dirX = 0;
776
777 self->thinkTime = 120;
778
779 self->action = &headWait;
780 }
781
782 alignBodyToHead();
783 }
784
alignBodyToHead()785 static void alignBodyToHead()
786 {
787 float x, y, partDistanceX, partDistanceY;
788 Entity *e;
789
790 x = self->x;
791 y = self->y;
792
793 partDistanceX = self->endX - self->x;
794 partDistanceY = fabs(self->endY - self->y);
795
796 partDistanceX /= self->mental;
797 partDistanceY /= self->mental;
798
799 e = self->target;
800
801 while (e != NULL)
802 {
803 x += partDistanceX;
804 y += partDistanceY;
805
806 e->x = (e->target == NULL ? self->endX : x) + 23;
807 e->y = (e->target == NULL ? self->endY : y);
808
809 e->damage = self->damage;
810
811 e->face = self->face;
812
813 if (self->flags & NO_DRAW)
814 {
815 e->flags |= NO_DRAW;
816 }
817
818 else
819 {
820 e->flags &= ~NO_DRAW;
821 }
822
823 e = e->target;
824 }
825 }
826
takeDamage(Entity * other,int damage)827 static void takeDamage(Entity *other, int damage)
828 {
829 Entity *temp;
830
831 if (!(self->flags & INVULNERABLE))
832 {
833 /* Only takes proper damage against its own shot */
834
835 if (strcmpignorecase(other->name, "boss/snake_boss_special_shot") == 0 && other->parent->type == PLAYER)
836 {
837 self->health -= damage;
838
839 self->flags &= ~FLY;
840
841 self->dirY = -2;
842
843 self->dirX = self->face == LEFT ? -4 : 4;
844
845 self->action = &stunned;
846 }
847
848 else
849 {
850 damage = (self->flags & HELPLESS ? damage * 3 : damage);
851
852 self->health -= damage;
853 }
854
855 if (self->health > 0)
856 {
857 setCustomAction(self, &flashWhite, 6, 0, 0);
858 setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
859
860 enemyPain();
861 }
862
863 else
864 {
865 setEntityAnimation(self, "PAIN");
866
867 self->health = 0;
868
869 self->thinkTime = 60;
870
871 self->damage = 0;
872
873 self->takeDamage = NULL;
874 self->touch = NULL;
875
876 self->targetX = self->endX;
877 self->targetY = self->endY - 128;
878
879 clearCustomActions(self);
880
881 self->action = ¨
882
883 self->dirX = self->face == RIGHT ? 2 : -2;
884
885 self->flags &= ~(HELPLESS|FLY);
886 }
887
888 if (other->type == PROJECTILE)
889 {
890 temp = self;
891
892 self = other;
893
894 self->die();
895
896 self = temp;
897 }
898 }
899 }
900
die()901 static void die()
902 {
903 long onGround = self->flags & ON_GROUND;
904
905 self->action = ¨
906
907 checkToMap(self);
908
909 if (self->flags & FLY)
910 {
911 if (atTarget())
912 {
913 self->dirX = self->dirY = 0;
914
915 self->thinkTime = 120;
916
917 self->action = &fallToGround;
918 }
919 }
920
921 else
922 {
923 if (self->flags & ON_GROUND)
924 {
925 self->dirX = 0;
926
927 if (onGround == 0)
928 {
929 addSmokeAlongBody();
930 }
931
932 self->thinkTime--;
933
934 if (self->thinkTime <= 0)
935 {
936 calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
937
938 self->dirX *= 2;
939 self->dirY *= 2;
940
941 self->flags |= FLY;
942 }
943 }
944 }
945
946 alignBodyToHead();
947 }
948
fallToGround()949 static void fallToGround()
950 {
951 Entity *e;
952
953 self->thinkTime--;
954
955 if (self->thinkTime == 0)
956 {
957 playSoundToMap("sound/boss/snake_boss/snake_boss_die", BOSS_CHANNEL, self->x, self->y, 0);
958
959 self->flags &= ~FLY;
960
961 self->dirX = self->face == LEFT ? -8 : 8;
962
963 setEntityAnimation(self, "DIE");
964 }
965
966 checkToMap(self);
967
968 if (self->flags & ON_GROUND)
969 {
970 playSoundToMap("sound/common/crash", -1, self->x, self->y, 0);
971
972 setEntityAnimation(self, "PAIN");
973
974 self->thinkTime = 180;
975
976 self->dirX = 0;
977
978 self->action = &dieWait;
979
980 addSmokeAlongBody();
981
982 shakeScreen(MEDIUM, 90);
983
984 e = self->target;
985
986 while (e != NULL)
987 {
988 e->flags &= ~FLY;
989
990 e = e->target;
991 }
992
993 fadeBossMusic();
994 }
995
996 else
997 {
998 alignBodyToHead();
999 }
1000 }
1001
dieWait()1002 static void dieWait()
1003 {
1004 Entity *e;
1005
1006 checkToMap(self);
1007
1008 self->thinkTime--;
1009
1010 if (self->thinkTime <= 0)
1011 {
1012 clearContinuePoint();
1013
1014 increaseKillCount();
1015
1016 freeBossHealthBar();
1017
1018 e = addKeyItem("item/heart_container", self->x + self->w / 2, self->y);
1019
1020 e->dirY = ITEM_JUMP_HEIGHT;
1021
1022 entityDieNoDrop();
1023
1024 e = self;
1025
1026 self = self->target;
1027
1028 while (self != NULL)
1029 {
1030 self->die();
1031
1032 self = self->target;
1033 }
1034
1035 self = e;
1036 }
1037 }
1038
specialShotMove()1039 static void specialShotMove()
1040 {
1041 if (!(self->flags & FLY))
1042 {
1043 self->dirX = (self->face == LEFT ? 5 : -5);
1044
1045 self->dirY = -5;
1046
1047 self->type = ENEMY;
1048
1049 self->target = self->parent;
1050
1051 self->parent = NULL;
1052
1053 self->action = &specialShotWait;
1054
1055 self->touch = NULL;
1056
1057 self->thinkTime = 300;
1058
1059 self->die = &entityDieNoDrop;
1060 }
1061
1062 checkToMap(self);
1063 }
1064
specialShotBlock(Entity * other)1065 static void specialShotBlock(Entity *other)
1066 {
1067 self->dirX = 0;
1068
1069 self->flags &= ~FLY;
1070 }
1071
specialShotWait()1072 static void specialShotWait()
1073 {
1074 self->thinkTime--;
1075
1076 checkToMap(self);
1077
1078 if (self->flags & ON_GROUND)
1079 {
1080 self->touch = &specialShotTouch;
1081
1082 self->dirX = 0;
1083 }
1084
1085 if (self->thinkTime <= 0)
1086 {
1087 self->die();
1088 }
1089 }
1090
specialShotTouch(Entity * other)1091 static void specialShotTouch(Entity *other)
1092 {
1093 Entity *e;
1094
1095 if (other->type == WEAPON && (other->flags & ATTACKING))
1096 {
1097 e = addProjectile("boss/snake_boss_special_shot", &player, self->x, self->y, 0, 0);
1098
1099 e->targetX = self->target->x + self->target->w / 2;
1100 e->targetY = self->target->y + self->target->h / 2;
1101
1102 calculatePath(e->x, e->y, e->targetX, e->targetY, &e->dirX, &e->dirY);
1103
1104 e->dirX *= 12;
1105 e->dirY *= 12;
1106
1107 e->dirX = (player.face == RIGHT ? fabs(e->dirX) : fabs(e->dirX) * -1);
1108
1109 e->damage = 10;
1110
1111 self->inUse = FALSE;
1112 }
1113 }
1114
biteReactToBlock(Entity * other)1115 static void biteReactToBlock(Entity *other)
1116 {
1117 self->dirX = 0;
1118
1119 self->targetX = self->x;
1120
1121 self->x = (int)self->x;
1122 }
1123
stunned()1124 static void stunned()
1125 {
1126 int i;
1127 long onGround = self->flags & ON_GROUND;
1128 Entity *e;
1129
1130 setEntityAnimation(self, "PAIN");
1131
1132 checkToMap(self);
1133
1134 if (self->flags & ON_GROUND)
1135 {
1136 self->dirX = 0;
1137
1138 setCustomAction(self, &helpless, 300, 0, 0);
1139
1140 if (onGround == 0)
1141 {
1142 addSmokeAlongBody();
1143 }
1144
1145 self->action = &attackFinished;
1146
1147 for (i=0;i<2;i++)
1148 {
1149 e = getFreeEntity();
1150
1151 if (e == NULL)
1152 {
1153 showErrorAndExit("No free slots to add the Armour Boss's Star");
1154 }
1155
1156 loadProperties("boss/armour_boss_star", e);
1157
1158 e->x = self->x;
1159 e->y = self->y;
1160
1161 e->action = &starWait;
1162
1163 e->draw = &drawLoopingAnimationToMap;
1164
1165 e->thinkTime = 300;
1166
1167 e->head = self;
1168
1169 setEntityAnimation(e, "STAND");
1170
1171 e->currentFrame = (i == 0 ? 0 : 6);
1172
1173 e->x = self->x + self->w / 2 - e->w / 2;
1174
1175 e->y = self->y - e->h - 8;
1176 }
1177 }
1178
1179 alignBodyToHead();
1180 }
1181
addSmokeAlongBody()1182 static void addSmokeAlongBody()
1183 {
1184 int i, bodyLength;
1185
1186 shakeScreen(MEDIUM, 15);
1187
1188 bodyLength = abs(self->endX - self->x);
1189
1190 for (i=0;i<100;i++)
1191 {
1192 addSmoke((self->face == LEFT ? self->x : self->endX) + (prand() % bodyLength), self->y + prand() % self->h, "decoration/dust");
1193 }
1194
1195 playSoundToMap("sound/common/crash", BOSS_CHANNEL, self->x, self->y, 0);
1196 }
1197
bodyTakeDamage(Entity * other,int damage)1198 static void bodyTakeDamage(Entity *other, int damage)
1199 {
1200 Entity *temp;
1201
1202 if (self->head->takeDamage != NULL)
1203 {
1204 /* Hitting the body only does half the damage */
1205
1206 damage /= 2;
1207
1208 temp = self;
1209
1210 self = self->head;
1211
1212 self->takeDamage(other, damage);
1213
1214 self = temp;
1215 }
1216 }
1217
starWait()1218 static void starWait()
1219 {
1220 self->thinkTime--;
1221
1222 if (self->thinkTime <= 0 || self->head->health <= 0)
1223 {
1224 self->inUse = FALSE;
1225 }
1226 }
1227
creditsMove()1228 static void creditsMove()
1229 {
1230 if (self->health != -1)
1231 {
1232 createBody();
1233
1234 self->endY = self->y + 32;
1235
1236 self->y -= 32;
1237
1238 self->health = -1;
1239 }
1240
1241 bossMoveToMiddle();
1242
1243 self->endX = self->x;
1244
1245 alignBodyToHead();
1246
1247 if (self->thinkTime <= 0)
1248 {
1249 self->creditsAction = &creditsMoveOffScreen;
1250 }
1251 }
1252
creditsMoveOffScreen()1253 static void creditsMoveOffScreen()
1254 {
1255 self->x -= 20;
1256
1257 if (self->x <= -self->w)
1258 {
1259 self->inUse = FALSE;
1260 }
1261
1262 self->endX = self->x;
1263
1264 alignBodyToHead();
1265 }
1266