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 "../enemy/rock.h"
28 #include "../entity.h"
29 #include "../game.h"
30 #include "../graphics/animation.h"
31 #include "../hud.h"
32 #include "../inventory.h"
33 #include "../item/key_items.h"
34 #include "../map.h"
35 #include "../player.h"
36 #include "../system/error.h"
37 #include "../system/properties.h"
38 #include "../system/random.h"
39 #include "../world/target.h"
40
41 extern Entity *self, player;
42
43 static void entityWait(void);
44 static void initialise(void);
45 static void takeDamage(Entity *, int);
46 static void attackFinished(void);
47 static void doIntro(void);
48 static void introPause(void);
49 static void floatInContainer(void);
50 static void stunnedTouch(Entity *);
51 static void splitAttackInit(void);
52 static void partWait(void);
53 static void partAttack(void);
54 static void activate(int);
55 static void leaveFinish(void);
56 static void reform(void);
57 static void headWait(void);
58 static void headReform(void);
59 static void partDie(void);
60 static void partGrab(Entity *);
61 static void stickToPlayerAndDrain(void);
62 static void fallOff(void);
63 static void bounceAroundInit(void);
64 static void bounceAround(void);
65 static void punchAttackInit(void);
66 static void punchSink(void);
67 static void lookForPlayer(void);
68 static void punch(void);
69 static void punchFinish(void);
70 static void eatInit(void);
71 static void eatAttack(void);
72 static void eat(void);
73 static void eatExplode(void);
74 static void explodeWait(void);
75 static void eatTakeDamage(Entity *, int);
76 static void shudder(void);
77 static Target *getCenterTarget(void);
78 static void grubAttackInit(void);
79 static void grubAttackWait(void);
80 static void spinAttackStart(void);
81 static void spinAttack(void);
82 static void spinAttackEnd(void);
83 static void grubAttackFinish(void);
84 static void punch2AttackInit(void);
85 static void punch2Sink(void);
86 static void punch2(void);
87 static void die(void);
88 static void dieSink(void);
89 static void dieRise(void);
90 static void dieSplit(void);
91 static void dieWait(void);
92 static void partFinalDie(void);
93 static void partTakeDamage(Entity *, int);
94
addBlobBoss(int x,int y,char * name)95 Entity *addBlobBoss(int x, int y, char *name)
96 {
97 Entity *e = getFreeEntity();
98
99 if (e == NULL)
100 {
101 showErrorAndExit("No free slots to add the Blob Boss");
102 }
103
104 loadProperties(name, e);
105
106 e->x = x;
107 e->y = y;
108
109 if (strcmpignorecase("boss/blob_boss_1", name) == 0)
110 {
111 e->action = &floatInContainer;
112 e->takeDamage = NULL;
113 }
114
115 else
116 {
117 e->action = &initialise;
118 e->touch = NULL;
119
120 e->flags &= ~FLY;
121 }
122
123 e->draw = &drawLoopingAnimationToMap;
124
125 e->creditsAction = &bossMoveToMiddle;
126
127 e->type = ENEMY;
128
129 e->active = FALSE;
130
131 setEntityAnimation(e, "STAND");
132
133 return e;
134 }
135
initialise()136 static void initialise()
137 {
138 if (self->active == TRUE)
139 {
140 if (cameraAtMinimum())
141 {
142 centerMapOnEntity(NULL);
143
144 self->startX = self->maxThinkTime = 60;
145
146 self->thinkTime = 6;
147
148 self->flags &= ~FLY;
149
150 self->flags |= LIMIT_TO_SCREEN;
151
152 self->action = &doIntro;
153
154 setEntityAnimation(self, "WALK");
155
156 self->frameSpeed = 0;
157
158 setContinuePoint(FALSE, self->name, NULL);
159 }
160 }
161 }
162
doIntro()163 static void doIntro()
164 {
165 Entity *e;
166
167 self->thinkTime--;
168
169 if (self->thinkTime <= 0)
170 {
171 e = getFreeEntity();
172
173 if (e == NULL)
174 {
175 showErrorAndExit("No free slots to add a Blob Boss Part");
176 }
177
178 loadProperties("boss/blob_boss_part", e);
179
180 setEntityAnimation(e, "STAND");
181
182 e->flags |= LIMIT_TO_SCREEN;
183
184 e->draw = &drawLoopingAnimationToMap;
185
186 e->type = ENEMY;
187
188 e->damage = 0;
189
190 e->head = self;
191
192 e->action = &reform;
193
194 e->health = 600;
195
196 e->x = self->x + self->w / 2 - e->w / 2;
197
198 e->y = self->startY + self->h / 2 - e->h / 2;
199
200 e->dirX = (10 + prand() % 50) * (prand() % 2 == 0 ? 1 : -1);
201
202 e->dirX /= 10;
203
204 e->thinkTime = 120;
205
206 e->targetX = self->x + self->w / 2;
207
208 self->maxThinkTime--;
209
210 if (self->maxThinkTime <= 0)
211 {
212 self->action = &introPause;
213 }
214
215 self->thinkTime = 6;
216 }
217
218 checkToMap(self);
219 }
220
reform()221 static void reform()
222 {
223 checkToMap(self);
224
225 if (self->thinkTime > 0)
226 {
227 self->thinkTime--;
228
229 if (self->flags & ON_GROUND)
230 {
231 self->dirX = 0;
232 }
233 }
234
235 else
236 {
237 self->health--;
238
239 if (fabs(self->targetX - self->x) <= fabs(self->dirX) || self->health <= 0)
240 {
241 self->head->startX--;
242
243 self->head->flags &= ~NO_DRAW;
244
245 self->inUse = FALSE;
246
247 if (((int)self->head->startX) % 10 == 0)
248 {
249 self->head->currentFrame++;
250 }
251
252 playSoundToMap("sound/boss/blob_boss/plop", BOSS_CHANNEL, self->x, self->y, 0);
253 }
254
255 if (self->flags & ON_GROUND)
256 {
257 self->dirX = (self->x < self->targetX ? self->speed : -self->speed);
258
259 self->dirY = -6;
260 }
261 }
262 }
263
introPause()264 static void introPause()
265 {
266 checkToMap(self);
267
268 if (self->startX <= 0)
269 {
270 self->takeDamage = &takeDamage;
271
272 self->action = &attackFinished;
273
274 playDefaultBossMusic();
275
276 initBossHealthBar();
277
278 self->touch = &entityTouch;
279
280 self->mental = 15;
281
282 self->endY = self->y;
283 }
284 }
285
entityWait()286 static void entityWait()
287 {
288 int i;
289
290 self->dirX = 0;
291
292 facePlayer();
293
294 self->thinkTime--;
295
296 setEntityAnimation(self, (self->mental <= 0 || self->health <= 300) ? "JUMP" : "STAND");
297
298 if (self->thinkTime <= 0 && player.health > 0)
299 {
300 if (self->mental <= 0)
301 {
302 self->action = &eatInit;
303 }
304
305 else if (self->health > 3000)
306 {
307 self->action = &bounceAroundInit;
308 }
309
310 else if (self->health > 2000)
311 {
312 self->action = prand() % 2 == 0 ? &bounceAroundInit : &punchAttackInit;
313 }
314
315 else if (self->health > 1000)
316 {
317 i = prand() % 3;
318
319 switch (i)
320 {
321 case 0:
322 self->action = &punchAttackInit;
323 break;
324
325 case 1:
326 self->action = &bounceAroundInit;
327 break;
328
329 default:
330 self->action = &grubAttackInit;
331 break;
332 }
333 }
334
335 else
336 {
337 i = prand() % 2;
338
339 switch (i)
340 {
341 case 0:
342 self->action = &splitAttackInit;
343 break;
344
345 case 1:
346 self->action = &punch2AttackInit;
347 break;
348 }
349 }
350 }
351
352 checkToMap(self);
353 }
354
punch2AttackInit()355 static void punch2AttackInit()
356 {
357 Target *t;
358
359 t = getCenterTarget();
360
361 self->startX = t->x;
362
363 t = getTargetByName(player.x < self->x ? "BLOB_TARGET_LEFT" : "BLOB_TARGET_RIGHT");
364
365 if (t == NULL)
366 {
367 showErrorAndExit("Blob Boss could not find target");
368 }
369
370 self->endX = t->x;
371
372 self->targetY = self->y + self->h;
373
374 self->layer = BACKGROUND_LAYER;
375
376 self->action = &punch2Sink;
377 }
378
punch2Sink()379 static void punch2Sink()
380 {
381 int x;
382 Target *t;
383
384 if (self->y < self->targetY)
385 {
386 self->y += 3;
387 }
388
389 else
390 {
391 self->y = self->targetY;
392
393 setEntityAnimation(self, "CUSTOM_1");
394
395 if (self->x != self->endX)
396 {
397 x = self->x < self->endX ? 48 : -48;
398
399 self->x += x;
400
401 if (x < 0 && self->x < self->endX)
402 {
403 self->x = self->endX;
404 }
405
406 else if (x > 0 && self->x > self->endX)
407 {
408 self->x = self->endX;
409 }
410
411 self->thinkTime = 15;
412
413 self->targetY = self->y - self->h;
414
415 self->action = &punch2;
416 }
417
418 else
419 {
420 self->action = &punchFinish;
421
422 t = getCenterTarget();
423
424 self->targetX = t->x;
425
426 self->targetY = self->y - self->h;
427
428 self->dirX = self->speed;
429 }
430 }
431 }
432
punch2()433 static void punch2()
434 {
435 Entity *e;
436
437 if (self->y > self->targetY)
438 {
439 self->thinkTime--;
440
441 if (self->thinkTime <= 0)
442 {
443 if (self->thinkTime == 0)
444 {
445 playSoundToMap("sound/common/crumble", BOSS_CHANNEL, self->x, self->y, 0);
446
447 shakeScreen(MEDIUM, 15);
448 }
449
450 e = addSmallRock(self->x, self->y, "common/small_rock");
451
452 e->x += (self->w - e->w) / 2;
453 e->y += (self->h - e->h) / 2;
454
455 e->dirX = -3;
456 e->dirY = -8;
457
458 e = addSmallRock(self->x, self->y, "common/small_rock");
459
460 e->x += (self->w - e->w) / 2;
461 e->y += (self->h - e->h) / 2;
462
463 e->dirX = 3;
464 e->dirY = -8;
465
466 self->y -= 12;
467
468 if (self->y <= self->targetY)
469 {
470 self->y = self->targetY;
471
472 self->thinkTime = self->x != self->endX ? 30 : 90;
473 }
474 }
475 }
476
477 else
478 {
479 self->thinkTime--;
480
481 if (self->thinkTime < 0)
482 {
483 self->targetY = self->y + self->h;
484
485 self->action = &punch2Sink;
486 }
487 }
488
489 facePlayer();
490 }
491
grubAttackInit()492 static void grubAttackInit()
493 {
494 facePlayer();
495
496 setEntityAnimation(self, "ATTACK_2");
497
498 self->thinkTime = 30;
499
500 self->animationCallback = &grubAttackWait;
501
502 self->maxThinkTime = 1 + prand() % 5;
503 }
504
grubAttackWait()505 static void grubAttackWait()
506 {
507 setEntityAnimation(self, "ATTACK_3");
508
509 self->thinkTime--;
510
511 self->action = &grubAttackWait;
512
513 if (self->thinkTime <= 0)
514 {
515 setEntityAnimation(self, "ATTACK_4");
516
517 self->action = &spinAttackStart;
518
519 self->thinkTime = 1;
520 }
521 }
522
spinAttackStart()523 static void spinAttackStart()
524 {
525 self->flags |= INVULNERABLE;
526
527 if (self->thinkTime > 0)
528 {
529 self->thinkTime--;
530
531 if (self->thinkTime == 0)
532 {
533 self->face = (player.x > self->x ? RIGHT : LEFT);
534
535 self->frameSpeed = 2;
536
537 self->dirY = -8;
538 }
539 }
540
541 else if (self->thinkTime == 0 && self->flags & ON_GROUND)
542 {
543 self->speed = self->originalSpeed * 6;
544
545 self->dirX = (self->face == RIGHT ? self->speed : -self->speed);
546
547 self->action = &spinAttack;
548
549 self->thinkTime = 180;
550
551 self->flags |= ATTACKING;
552 }
553
554 checkToMap(self);
555 }
556
spinAttack()557 static void spinAttack()
558 {
559 self->thinkTime--;
560
561 checkToMap(self);
562
563 if (self->dirX == 0 || isAtEdge(self))
564 {
565 shakeScreen(MEDIUM, 15);
566
567 self->dirX = self->face == LEFT ? 3 : -3;
568
569 self->dirY = -6;
570
571 self->action = &spinAttackEnd;
572
573 self->thinkTime = 0;
574
575 playSoundToMap("sound/common/crash", -1, self->x, self->y, 0);
576
577 facePlayer();
578 }
579
580 else if (self->thinkTime <= 0)
581 {
582 self->action = &spinAttackEnd;
583
584 self->thinkTime = 0;
585 }
586 }
587
spinAttackEnd()588 static void spinAttackEnd()
589 {
590 checkToMap(self);
591
592 if ((self->flags & ON_GROUND) && self->thinkTime == 0)
593 {
594 facePlayer();
595
596 self->dirX = 0;
597
598 self->maxThinkTime--;
599
600 if (self->maxThinkTime > 0)
601 {
602 self->action = &spinAttackStart;
603
604 self->thinkTime = 0;
605 }
606
607 else
608 {
609 self->action = &grubAttackFinish;
610
611 self->thinkTime = 30;
612 }
613 }
614 }
615
grubAttackFinish()616 static void grubAttackFinish()
617 {
618 if (self->frameSpeed > 0)
619 {
620 self->frameSpeed = -1;
621
622 facePlayer();
623
624 setEntityAnimation(self, "ATTACK_2");
625
626 self->animationCallback = &attackFinished;
627
628 self->frameSpeed = 0;
629 }
630
631 else if (self->thinkTime > 0)
632 {
633 self->thinkTime--;
634
635 if (self->thinkTime <= 0)
636 {
637 self->frameSpeed = -1;
638 }
639 }
640 }
641
eatInit()642 static void eatInit()
643 {
644 self->damage = 0;
645
646 self->layer = FOREGROUND_LAYER;
647
648 facePlayer();
649
650 self->action = &eatAttack;
651
652 self->takeDamage = &eatTakeDamage;
653
654 self->mental = 10000;
655 }
656
eatAttack()657 static void eatAttack()
658 {
659 int bossMid, playerMid;
660
661 bossMid = self->x + self->w / 2;
662
663 playerMid = player.x + player.w / 2;
664
665 if (abs(bossMid - playerMid) < 4)
666 {
667 self->dirX = 0;
668
669 self->thinkTime = 300;
670
671 self->target = &player;
672
673 self->action = &eat;
674
675 self->mental = 10;
676 }
677
678 else
679 {
680 self->dirX = bossMid < playerMid ? self->speed : -self->speed;
681 }
682
683 checkToMap(self);
684 }
685
eat()686 static void eat()
687 {
688 Entity *temp;
689
690 self->thinkTime--;
691
692 self->target->x = self->x + self->w / 2 - self->target->w / 2;
693
694 self->target->y = self->y + self->h / 2 - self->target->h / 2;
695
696 self->target->y += cos(DEG_TO_RAD(self->thinkTime)) * 8;
697
698 if (self->thinkTime <= 0)
699 {
700 temp = self;
701
702 self = self->target;
703
704 freeEntityList(playerGib());
705
706 self = temp;
707
708 self->action = &entityWait;
709
710 self->maxThinkTime = 0;
711 }
712 }
713
eatExplode()714 static void eatExplode()
715 {
716 int i;
717 Entity *e;
718 Target *t;
719
720 self->maxThinkTime = 0;
721
722 t = getCenterTarget();
723
724 self->startX = 0;
725
726 for (i=0;i<60;i++)
727 {
728 e = getFreeEntity();
729
730 if (e == NULL)
731 {
732 showErrorAndExit("No free slots to add a Blob Boss Part");
733 }
734
735 loadProperties("boss/blob_boss_part", e);
736
737 setEntityAnimation(e, "STAND");
738
739 e->flags |= LIMIT_TO_SCREEN;
740
741 e->x = self->x;
742 e->y = self->y;
743
744 e->x += prand() % self->w;
745
746 e->y += prand() % (self->h - e->h);
747
748 e->dirX = (10 + prand() % 20) * (prand() % 2 == 0 ? 1 : -1);
749
750 e->dirX /= 10;
751
752 e->dirY = -6 - prand() % 4;
753
754 e->touch = &entityTouch;
755
756 e->damage = 0;
757
758 e->health = 600;
759
760 e->action = &partWait;
761
762 e->pain = &enemyPain;
763
764 e->draw = &drawLoopingAnimationToMap;
765
766 e->head = self;
767
768 e->type = ENEMY;
769
770 e->targetX = t->x;
771
772 e->targetY = t->y;
773
774 self->startX++;
775 }
776
777 self->target = NULL;
778
779 self->thinkTime = 120;
780
781 self->action = &explodeWait;
782
783 setEntityAnimation(self, "WALK");
784
785 self->flags |= NO_DRAW;
786
787 self->frameSpeed = 0;
788
789 self->maxThinkTime = 1;
790
791 self->touch = NULL;
792 }
793
bounceAroundInit()794 static void bounceAroundInit()
795 {
796 self->maxThinkTime = 7;
797
798 self->touch = &entityTouch;
799
800 self->action = &bounceAround;
801
802 self->dirY = -16;
803
804 self->face = self->face == LEFT ? RIGHT : LEFT;
805 }
806
bounceAround()807 static void bounceAround()
808 {
809 long onGround = self->flags & ON_GROUND;
810
811 checkToMap(self);
812
813 if (self->flags & ON_GROUND)
814 {
815 if (onGround == 0)
816 {
817 playSoundToMap("sound/boss/blob_boss/bounce", BOSS_CHANNEL, self->x, self->y, 0);
818 }
819
820 self->maxThinkTime--;
821
822 if (self->maxThinkTime > 0)
823 {
824 self->dirY = -16;
825 }
826
827 else
828 {
829 self->action = &attackFinished;
830 }
831 }
832
833 if (self->dirX == 0 && self->maxThinkTime != 7)
834 {
835 self->face = self->face == LEFT ? RIGHT : LEFT;
836
837 self->dirX = self->face == LEFT ? -self->speed : self->speed;
838 }
839 }
840
punchAttackInit()841 static void punchAttackInit()
842 {
843 Target *t;
844
845 t = getTargetByName("BLOB_TARGET_LEFT");
846
847 if (t == NULL)
848 {
849 showErrorAndExit("Blob Boss could not find target");
850 }
851
852 self->startX = t->x;
853
854 t = getTargetByName("BLOB_TARGET_RIGHT");
855
856 if (t == NULL)
857 {
858 showErrorAndExit("Blob Boss could not find target");
859 }
860
861 self->endX = t->x;
862
863 self->targetY = self->y + self->h;
864
865 self->maxThinkTime = 3 + prand() % 3;
866
867 self->layer = BACKGROUND_LAYER;
868
869 self->action = &punchSink;
870 }
871
punchSink()872 static void punchSink()
873 {
874 Target *t;
875
876 if (self->y < self->targetY)
877 {
878 self->y += 3;
879 }
880
881 else
882 {
883 self->y = self->targetY;
884
885 setEntityAnimation(self, "ATTACK_1");
886
887 if (self->maxThinkTime > 0 && player.health > 0)
888 {
889 self->action = &lookForPlayer;
890
891 self->dirX = self->speed * 1.5;
892 }
893
894 else
895 {
896 self->action = &punchFinish;
897
898 t = getCenterTarget();
899
900 self->targetX = t->x;
901
902 self->targetY = self->y - self->h;
903
904 self->dirX = self->speed;
905 }
906 }
907 }
908
lookForPlayer()909 static void lookForPlayer()
910 {
911 float target = player.x - self->w / 2 + player.w / 2;
912
913 if (fabs(target - self->x) <= fabs(self->dirX))
914 {
915 self->targetY = self->y - self->h;
916
917 self->thinkTime = 30;
918
919 self->action = &punch;
920 }
921
922 else
923 {
924 self->x += target > self->x ? self->dirX : -self->dirX;
925
926 if (self->x < self->startX)
927 {
928 self->x = self->startX;
929
930 self->targetY = self->y - self->h;
931
932 self->thinkTime = 30;
933
934 self->action = &punch;
935 }
936
937 else if (self->x > self->endX)
938 {
939 self->x = self->endX;
940
941 self->targetY = self->y - self->h;
942
943 self->thinkTime = 30;
944
945 self->action = &punch;
946 }
947 }
948 }
949
punch()950 static void punch()
951 {
952 Entity *e;
953
954 if (self->y > self->targetY)
955 {
956 self->thinkTime--;
957
958 if (self->thinkTime <= 0)
959 {
960 if (self->thinkTime == 0)
961 {
962 playSoundToMap("sound/common/crumble", BOSS_CHANNEL, self->x, self->y, 0);
963
964 shakeScreen(MEDIUM, 15);
965 }
966
967 e = addSmallRock(self->x, self->y, "common/small_rock");
968
969 e->x += (self->w - e->w) / 2;
970 e->y += (self->h - e->h) / 2;
971
972 e->dirX = -3;
973 e->dirY = -8;
974
975 e = addSmallRock(self->x, self->y, "common/small_rock");
976
977 e->x += (self->w - e->w) / 2;
978 e->y += (self->h - e->h) / 2;
979
980 e->dirX = 3;
981 e->dirY = -8;
982
983 self->y -= 12;
984
985 if (self->y <= self->targetY)
986 {
987 self->y = self->targetY;
988
989 self->maxThinkTime--;
990
991 self->thinkTime = self->maxThinkTime > 0 ? 30 : 90;
992 }
993 }
994 }
995
996 else
997 {
998 self->thinkTime--;
999
1000 if (self->thinkTime < 0)
1001 {
1002 self->targetY = self->y + self->h;
1003
1004 self->action = &punchSink;
1005 }
1006 }
1007
1008 facePlayer();
1009 }
1010
punchFinish()1011 static void punchFinish()
1012 {
1013 if (fabs(self->x - self->targetX) <= fabs(self->dirX))
1014 {
1015 setEntityAnimation(self, (self->mental <= 0 || self->health <= 300) ? "JUMP" : "STAND");
1016
1017 if (self->y > self->targetY)
1018 {
1019 self->y -= 2;
1020 }
1021
1022 else
1023 {
1024 self->action = &attackFinished;
1025 }
1026 }
1027
1028 else
1029 {
1030 self->x += self->x < self->targetX ? self->dirX : -self->dirX;
1031 }
1032
1033 facePlayer();
1034 }
1035
attackFinished()1036 static void attackFinished()
1037 {
1038 self->flags &= ~INVULNERABLE;
1039
1040 self->layer = MID_GROUND_LAYER;
1041
1042 self->frameSpeed = 1;
1043
1044 setEntityAnimation(self, (self->mental <= 0 || self->health <= 300) ? "JUMP" : "STAND");
1045
1046 self->speed = self->originalSpeed;
1047
1048 self->dirX = 0;
1049
1050 self->thinkTime = 90;
1051
1052 self->damage = 1;
1053
1054 self->action = &entityWait;
1055
1056 self->touch = &entityTouch;
1057
1058 self->activate = NULL;
1059 }
1060
takeDamage(Entity * other,int damage)1061 static void takeDamage(Entity *other, int damage)
1062 {
1063 Entity *temp;
1064
1065 if (!(self->flags & INVULNERABLE))
1066 {
1067 if (other->element == LIGHTNING)
1068 {
1069 self->health -= damage;
1070
1071 self->thinkTime = 120;
1072
1073 setCustomAction(self, &invulnerableNoFlash, 120, 0, 0);
1074
1075 self->startX = self->x;
1076
1077 self->action = &shudder;
1078 }
1079
1080 else if (self->health > 1000)
1081 {
1082 setCustomAction(self, &flashWhite, 6, 0, 0);
1083 setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
1084
1085 self->mental--;
1086
1087 enemyPain();
1088 }
1089
1090 else if (self->health > 0)
1091 {
1092 setCustomAction(self, &flashWhite, 6, 0, 0);
1093 setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
1094
1095 /* Take minimal damage from bombs */
1096
1097 if (other->type == EXPLOSION)
1098 {
1099 damage = 1;
1100 }
1101
1102 self->health -= damage;
1103
1104 if (self->health > 0)
1105 {
1106 enemyPain();
1107 }
1108
1109 else
1110 {
1111 self->thinkTime = 120;
1112
1113 self->startX = self->x;
1114
1115 self->damage = 0;
1116
1117 self->action = &shudder;
1118 }
1119 }
1120
1121 if (other->type == PROJECTILE)
1122 {
1123 temp = self;
1124
1125 self = other;
1126
1127 self->die();
1128
1129 self = temp;
1130 }
1131 }
1132 }
1133
eatTakeDamage(Entity * other,int damage)1134 static void eatTakeDamage(Entity *other, int damage)
1135 {
1136 Entity *temp;
1137
1138 if (!(self->flags & INVULNERABLE))
1139 {
1140 setCustomAction(self, &flashWhite, 6, 0, 0);
1141 setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
1142
1143 self->mental--;
1144
1145 enemyPain();
1146
1147 if (self->mental <= 0)
1148 {
1149 self->action = &eatExplode;
1150 }
1151
1152 if (other->type == PROJECTILE)
1153 {
1154 temp = self;
1155
1156 self = other;
1157
1158 self->die();
1159
1160 self = temp;
1161 }
1162 }
1163 }
1164
activate(int val)1165 static void activate(int val)
1166 {
1167 Entity *e, *temp;
1168
1169 if (!(self->flags & NO_DRAW))
1170 {
1171 e = getInventoryItemByObjectiveName("Tesla Pack");
1172
1173 if (e != NULL && e->health != 0)
1174 {
1175 temp = self;
1176
1177 self = e;
1178
1179 self->target = temp;
1180
1181 self->activate(val);
1182
1183 self = temp;
1184 }
1185 }
1186 }
1187
stunnedTouch(Entity * other)1188 static void stunnedTouch(Entity *other)
1189 {
1190 Entity *e;
1191
1192 if (!(self->flags & NO_DRAW))
1193 {
1194 e = getInventoryItemByObjectiveName("Tesla Pack");
1195
1196 if (e != NULL && e->health != 0)
1197 {
1198 setInfoBoxMessage(0, 255, 255, 255, _("Press Action to attach the Tesla Pack"));
1199 }
1200 }
1201 }
1202
splitAttackInit()1203 static void splitAttackInit()
1204 {
1205 int i;
1206 Entity *e;
1207 Target *t;
1208
1209 self->maxThinkTime = 0;
1210
1211 t = getCenterTarget();
1212
1213 for (i=0;i<60;i++)
1214 {
1215 e = getFreeEntity();
1216
1217 if (e == NULL)
1218 {
1219 showErrorAndExit("No free slots to add a Blob Boss Part");
1220 }
1221
1222 loadProperties("boss/blob_boss_part", e);
1223
1224 e->flags |= LIMIT_TO_SCREEN;
1225
1226 setEntityAnimation(e, "WALK");
1227
1228 e->x = self->x;
1229 e->y = self->y;
1230
1231 e->x += prand() % self->w;
1232
1233 e->y += prand() % (self->h - e->h);
1234
1235 e->dirX = (10 + prand() % 20) * (prand() % 2 == 0 ? 1 : -1);
1236
1237 e->dirX /= 10;
1238
1239 e->dirY = -6;
1240
1241 e->action = &partAttack;
1242
1243 e->touch = &partGrab;
1244
1245 e->die = &partDie;
1246
1247 e->pain = &enemyPain;
1248
1249 e->draw = &drawLoopingAnimationToMap;
1250
1251 e->takeDamage = &partTakeDamage;
1252
1253 e->head = self;
1254
1255 e->type = ENEMY;
1256
1257 e->thinkTime = 60;
1258
1259 e->targetX = t->x;
1260
1261 e->targetY = t->y;
1262 }
1263
1264 self->maxThinkTime = 60;
1265
1266 self->action = &headWait;
1267
1268 setEntityAnimation(self, "BLOCK");
1269
1270 self->flags |= NO_DRAW;
1271
1272 self->frameSpeed = 0;
1273
1274 self->touch = NULL;
1275 }
1276
partTakeDamage(Entity * other,int damage)1277 static void partTakeDamage(Entity *other, int damage)
1278 {
1279 Entity *temp;
1280
1281 if (self->flags & INVULNERABLE)
1282 {
1283 return;
1284 }
1285
1286 if (damage != 0)
1287 {
1288 self->health -= damage;
1289
1290 if (self->health > 0)
1291 {
1292 setCustomAction(self, &flashWhite, 6, 0, 0);
1293
1294 /* Don't make an enemy invulnerable from a projectile hit, allows multiple hits */
1295
1296 if (other->type != PROJECTILE)
1297 {
1298 setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
1299 }
1300
1301 if (self->pain != NULL)
1302 {
1303 self->pain();
1304 }
1305 }
1306
1307 else
1308 {
1309 self->damage = 0;
1310
1311 self->die();
1312 }
1313
1314 if (other->type == PROJECTILE)
1315 {
1316 temp = self;
1317
1318 self = other;
1319
1320 self->die();
1321
1322 self = temp;
1323 }
1324 }
1325 }
1326
partDie()1327 static void partDie()
1328 {
1329 self->head->maxThinkTime--;
1330
1331 setEntityAnimation(self, "STAND");
1332
1333 self->action = &partWait;
1334
1335 self->touch = NULL;
1336
1337 self->takeDamage = NULL;
1338 }
1339
partAttack()1340 static void partAttack()
1341 {
1342 long onGround = (self->flags & ON_GROUND);
1343
1344 checkToMap(self);
1345
1346 /* Bounce towards player */
1347
1348 if (self->flags & ON_GROUND)
1349 {
1350 self->dirX = 0;
1351
1352 if (onGround == 0)
1353 {
1354 self->thinkTime = prand() % 60;
1355 }
1356
1357 else
1358 {
1359 self->thinkTime--;
1360
1361 if (self->thinkTime <= 0)
1362 {
1363 self->dirX = (self->x < player.x ? self->speed : -self->speed);
1364
1365 self->dirY = -6;
1366 }
1367 }
1368 }
1369 }
1370
partGrab(Entity * other)1371 static void partGrab(Entity *other)
1372 {
1373 if (self->health <= 0)
1374 {
1375 return;
1376 }
1377
1378 if (other->type == WEAPON && (other->flags & ATTACKING))
1379 {
1380 if (self->takeDamage != NULL && !(self->flags & INVULNERABLE))
1381 {
1382 self->takeDamage(other, other->damage);
1383 }
1384 }
1385
1386 else if (other->type == PROJECTILE && other->parent != self)
1387 {
1388 if (self->takeDamage != NULL && !(self->flags & INVULNERABLE))
1389 {
1390 self->takeDamage(other, other->damage);
1391 }
1392
1393 other->inUse = FALSE;
1394 }
1395
1396 else if (other->type == PLAYER && !(self->flags & GRABBING))
1397 {
1398 self->startX = (prand() % (other->w / 2)) * (prand() % 2 == 0 ? 1 : -1);
1399
1400 self->startY = prand() % (other->h - self->h);
1401
1402 setCustomAction(other, &slowDown, 3, 1, 0);
1403
1404 self->action = &stickToPlayerAndDrain;
1405
1406 self->touch = NULL;
1407
1408 self->flags |= GRABBING;
1409
1410 self->layer = FOREGROUND_LAYER;
1411
1412 other->flags |= GRABBED;
1413
1414 self->thinkTime = 0;
1415
1416 self->mental = 3 + (prand() % 3);
1417 }
1418 }
1419
stickToPlayerAndDrain()1420 static void stickToPlayerAndDrain()
1421 {
1422 Entity *temp;
1423
1424 setInfoBoxMessage(0, 255, 255, 255, _("Quickly turn left and right to shake off the pieces!"));
1425
1426 setCustomAction(&player, &slowDown, 3, 0, 0);
1427
1428 self->x = player.x + (player.w - self->w) / 2 + self->startX;
1429 self->y = player.y + self->startY;
1430
1431 self->thinkTime++;
1432
1433 if (self->face != player.face)
1434 {
1435 self->face = player.face;
1436
1437 if (self->thinkTime <= 15)
1438 {
1439 self->mental--;
1440 }
1441
1442 self->thinkTime = 0;
1443 }
1444
1445 if (self->thinkTime >= 60)
1446 {
1447 temp = self;
1448
1449 self = &player;
1450
1451 self->takeDamage(temp, 1);
1452
1453 self = temp;
1454
1455 self->thinkTime = 0;
1456
1457 if (player.health <= 0)
1458 {
1459 self->die();
1460 }
1461 }
1462
1463 if (self->mental <= 0)
1464 {
1465 self->x = player.x + player.w / 2 - self->w / 2;
1466
1467 self->dirX = self->speed * 2 * (prand() % 2 == 0 ? -1 : 1);
1468
1469 self->dirY = -6;
1470
1471 setCustomAction(&player, &slowDown, 3, -1, 0);
1472
1473 self->action = &fallOff;
1474
1475 self->thinkTime = 600;
1476
1477 self->damage = 0;
1478
1479 self->touch = &entityTouch;
1480
1481 player.flags &= ~GRABBED;
1482
1483 self->flags &= ~GRABBING;
1484 }
1485 }
1486
fallOff()1487 static void fallOff()
1488 {
1489 checkToMap(self);
1490
1491 if (self->flags & ON_GROUND)
1492 {
1493 self->dirX = 0;
1494 }
1495
1496 self->thinkTime--;
1497
1498 if (self->thinkTime <= 0)
1499 {
1500 self->action = &partAttack;
1501
1502 self->touch = &partGrab;
1503 }
1504 }
1505
partWait()1506 static void partWait()
1507 {
1508 checkToMap(self);
1509
1510 if (self->flags & ON_GROUND)
1511 {
1512 self->dirX = 0;
1513 }
1514
1515 if (self->head->maxThinkTime == 0 && self->head->startX != 0)
1516 {
1517 self->thinkTime = 60 + prand() % 180;
1518
1519 self->health = 600;
1520
1521 self->action = &reform;
1522 }
1523 }
1524
explodeWait()1525 static void explodeWait()
1526 {
1527 self->thinkTime--;
1528
1529 if (self->thinkTime <= 0)
1530 {
1531 self->maxThinkTime = 0;
1532
1533 self->action = &headWait;
1534
1535 self->touch = &stunnedTouch;
1536
1537 self->activate = &activate;
1538
1539 self->takeDamage = &takeDamage;
1540 }
1541 }
1542
headWait()1543 static void headWait()
1544 {
1545 Target *t;
1546
1547 checkToMap(self);
1548
1549 if (self->maxThinkTime <= 0)
1550 {
1551 t = getCenterTarget();
1552
1553 self->x = t->x - self->w / 2;
1554
1555 self->maxThinkTime = 0;
1556
1557 self->startX = 60;
1558
1559 self->action = &headReform;
1560 }
1561 }
1562
headReform()1563 static void headReform()
1564 {
1565 checkToMap(self);
1566
1567 if (self->startX <= 0)
1568 {
1569 if (self->health > 2000)
1570 {
1571 self->mental = 15;
1572 }
1573
1574 else if (self->health > 1000)
1575 {
1576 self->mental = 20;
1577 }
1578
1579 self->action = &attackFinished;
1580 }
1581 }
1582
floatInContainer()1583 static void floatInContainer()
1584 {
1585 if (self->active == TRUE)
1586 {
1587 self->health--;
1588 }
1589
1590 if (self->health > 0)
1591 {
1592 self->thinkTime++;
1593
1594 if (self->thinkTime >= 360)
1595 {
1596 self->thinkTime = 0;
1597 }
1598
1599 self->y = self->startY + cos(DEG_TO_RAD(self->thinkTime)) * 32;
1600 }
1601
1602 else
1603 {
1604 self->flags |= DO_NOT_PERSIST;
1605
1606 if (self->y < self->endY)
1607 {
1608 self->flags &= ~FLY;
1609
1610 checkToMap(self);
1611 }
1612
1613 else if (self->thinkTime > 0)
1614 {
1615 self->y = self->endY;
1616
1617 self->flags |= FLY;
1618
1619 setEntityAnimation(self, "WALK");
1620
1621 self->animationCallback = &leaveFinish;
1622 }
1623 }
1624 }
1625
leaveFinish()1626 static void leaveFinish()
1627 {
1628 self->inUse = FALSE;
1629 }
1630
shudder()1631 static void shudder()
1632 {
1633 self->thinkTime--;
1634
1635 self->x = self->startX + sin(DEG_TO_RAD(self->startY)) * 4;
1636
1637 self->startY += 90;
1638
1639 if (self->startY >= 360)
1640 {
1641 self->startY = 0;
1642 }
1643
1644 if (self->thinkTime <= 0)
1645 {
1646 self->x = self->startX;
1647
1648 if (self->health > 2000)
1649 {
1650 self->mental = 20;
1651 }
1652
1653 else if (self->health > 1000)
1654 {
1655 self->mental = 30;
1656 }
1657
1658 self->action = self->health <= 0 ? &die : &attackFinished;
1659 }
1660 }
1661
getCenterTarget()1662 static Target *getCenterTarget()
1663 {
1664 Target *t = getTargetByName("BLOB_TARGET");
1665
1666 if (t == NULL)
1667 {
1668 showErrorAndExit("Blob Boss could not find target");
1669 }
1670
1671 return t;
1672 }
1673
die()1674 static void die()
1675 {
1676 Target *t;
1677
1678 t = getCenterTarget();
1679
1680 self->targetY = self->endY + self->h;
1681
1682 self->targetX = t->x;
1683
1684 self->action = &dieSink;
1685
1686 self->layer = BACKGROUND_LAYER;
1687 }
1688
dieSink()1689 static void dieSink()
1690 {
1691 if (self->y < self->targetY)
1692 {
1693 self->y += 3;
1694 }
1695
1696 else
1697 {
1698 self->y = self->targetY;
1699
1700 setEntityAnimation(self, "STAND");
1701
1702 if (fabs(self->x - self->targetX) <= fabs(self->speed))
1703 {
1704 self->dirX = 0;
1705
1706 self->thinkTime = 60;
1707
1708 self->action = &dieRise;
1709 }
1710
1711 else
1712 {
1713 self->x += self->x < self->targetX ? self->speed : -self->speed;
1714 }
1715 }
1716 }
1717
dieRise()1718 static void dieRise()
1719 {
1720 if (self->y > self->endY)
1721 {
1722 self->y -= 3;
1723 }
1724
1725 else
1726 {
1727 self->y = self->endY;
1728
1729 self->thinkTime--;
1730
1731 if (self->thinkTime <= 0)
1732 {
1733 self->frameSpeed = -1;
1734
1735 setEntityAnimation(self, "WALK");
1736
1737 self->mental = 30;
1738
1739 self->frameSpeed = 0;
1740
1741 self->action = dieSplit;
1742 }
1743 }
1744 }
1745
dieSplit()1746 static void dieSplit()
1747 {
1748 Entity *e;
1749
1750 self->thinkTime--;
1751
1752 if (self->thinkTime <= 0)
1753 {
1754 e = getFreeEntity();
1755
1756 if (e == NULL)
1757 {
1758 showErrorAndExit("No free slots to add a Blob Boss Part");
1759 }
1760
1761 loadProperties("boss/blob_boss_part", e);
1762
1763 e->flags |= LIMIT_TO_SCREEN;
1764
1765 setEntityAnimation(e, "STAND");
1766
1767 e->draw = &drawLoopingAnimationToMap;
1768
1769 e->type = ENEMY;
1770
1771 e->damage = 0;
1772
1773 e->touch = NULL;
1774
1775 e->head = self;
1776
1777 e->action = &partFinalDie;
1778
1779 e->die = &entityDieNoDrop;
1780
1781 e->x = self->x + self->box.x + self->box.w / 2 - e->w / 2;
1782
1783 e->y = self->y + self->box.y + self->box.h / 2 - e->h / 2;
1784
1785 e->thinkTime = 120;
1786
1787 e->dirX = 10 + prand() % 80;
1788
1789 e->dirX /= 10;
1790
1791 e->dirX *= prand() % 2 == 0 ? -1 : 1;
1792
1793 e->dirY = -4 - prand() % 12;
1794
1795 e->targetX = self->x + self->w / 2;
1796
1797 self->mental--;
1798
1799 if (self->mental == 0)
1800 {
1801 if (self->currentFrame == 0)
1802 {
1803 self->flags |= NO_DRAW;
1804
1805 self->thinkTime = 120;
1806
1807 self->action = &dieWait;
1808
1809 return;
1810 }
1811
1812 else
1813 {
1814 self->mental = 30;
1815
1816 self->currentFrame--;
1817
1818 setFrameData(self);
1819 }
1820 }
1821
1822 self->thinkTime = 3;
1823 }
1824
1825 checkToMap(self);
1826 }
1827
dieWait()1828 static void dieWait()
1829 {
1830 Entity *e;
1831
1832 self->thinkTime--;
1833
1834 if (self->thinkTime <= 0)
1835 {
1836 clearContinuePoint();
1837
1838 increaseKillCount();
1839
1840 freeBossHealthBar();
1841
1842 e = addKeyItem("item/heart_container", self->x + self->w / 2, self->y);
1843
1844 e->dirY = ITEM_JUMP_HEIGHT;
1845
1846 fadeBossMusic();
1847
1848 entityDieVanish();
1849 }
1850 }
1851
partFinalDie()1852 static void partFinalDie()
1853 {
1854 checkToMap(self);
1855
1856 if (self->flags & ON_GROUND)
1857 {
1858 self->die();
1859 }
1860 }
1861