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 = &die;
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 = &die;
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