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