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/enemies.h"
28 #include "../enemy/rock.h"
29 #include "../entity.h"
30 #include "../event/global_trigger.h"
31 #include "../event/script.h"
32 #include "../event/trigger.h"
33 #include "../game.h"
34 #include "../geometry.h"
35 #include "../graphics/animation.h"
36 #include "../graphics/decoration.h"
37 #include "../hud.h"
38 #include "../item/item.h"
39 #include "../item/key_items.h"
40 #include "../map.h"
41 #include "../player.h"
42 #include "../projectile.h"
43 #include "../system/error.h"
44 #include "../system/properties.h"
45 #include "../system/random.h"
46 #include "../world/target.h"
47 
48 extern Input input;
49 extern Entity *self, player;
50 
51 static void initialise(void);
52 static void addStoneCoat(void);
53 static void init(void);
54 static void doIntro(void);
55 static void introFinish(void);
56 static void attackFinished(void);
57 static void attackFinishedMoveUp(void);
58 static void attackFinishedMoveHorizontal(void);
59 static void attackFinished(void);
60 static void entityWait(void);
61 static void takeDamage(Entity *, int);
62 static void stoneTakeDamage(Entity *, int);
63 static void die(void);
64 static void dieWait(void);
65 static void dieFinish(void);
66 static void activate(int);
67 static void stoneTouch(Entity *);
68 static void stoneDie(void);
69 static void coatWait(void);
70 static void createLanceInit(void);
71 static void createLance(void);
72 static void createLanceWait(void);
73 static void lanceAppearWait(void);
74 static void lanceAppearFinish(void);
75 static void lanceWait(void);
76 static void lanceThrowInit(void);
77 static void lanceThrowMoveToTarget(void);
78 static void lanceThrow(void);
79 static void lanceThrowWait(void);
80 static void lanceDrop(void);
81 static void lanceStabInit(void);
82 static void lanceStabMoveToTarget(void);
83 static void lanceStab(void);
84 static void lanceStabReactToBlock(Entity *);
85 static void lanceStabFinish(void);
86 static void lanceAttack1(void);
87 static void lanceAttack1Wait(void);
88 static void lanceAttack2(void);
89 static void lanceAttack3(void);
90 static void lanceAttackTeleportFinish(void);
91 static void lanceThrowTeleportFinish(void);
92 static void lanceDie(void);
93 static void weaponRemoveBlastInit(void);
94 static void weaponRemoveBlast(void);
95 static void blastRemoveWeapon(Entity *);
96 static void weaponRemoveBlastFinish(void);
97 static void createLightningOrb(void);
98 static void lightningGridAttackInit(void);
99 static void lightningGridAttack(void);
100 static void lightningGridAttackWait(void);
101 static void orbMoveToTop(void);
102 static void orbMoveToTarget(void);
103 static void orbFollowPlayer(void);
104 static void orbCastLightning1(void);
105 static void orbCastLightning2(void);
106 static void orbCastLightningFinish1(void);
107 static void orbCastLightningFinish2(void);
108 static void lightningWait(void);
109 static void becomeMiniGargoyleInit(void);
110 static void becomeMiniGargoyleWait(void);
111 static void becomeMiniGargoyleFinish(void);
112 static void addExitTrigger(Entity *);
113 static void petrifyAttackInit(void);
114 static void petrifyAttack(void);
115 static void petrifyAttackWait(void);
116 static void invisibleAttackInit(void);
117 static void becomeInvisible(void);
118 static void invisibleAttackMoveToTop(void);
119 static void invisibleAttackMoveToTop(void);
120 static void invisibleAttackFollowPlayer(void);
121 static void invisibleDrop(void);
122 static void invisibleDropWait(void);
123 static void bridgeDestroyInit(void);
124 static void bridgeDestroyMoveToTarget(void);
125 static void bridgeDestroyFollowPlayer(void);
126 static void bridgeDestroy(void);
127 static void bridgeDestroyWait(void);
128 static void bridgeDestroyFinish(void);
129 static void lanceAttack2(void);
130 static void lanceAttack2Wait(void);
131 static void lanceDestroyBridge(void);
132 static void lanceFallout(void);
133 static void lanceThrowFallout(void);
134 static void lanceAttack3(void);
135 static void fakeLanceDropInit(void);
136 static void fakeLanceDropAppear(void);
137 static void fakeLanceDropWait(void);
138 static void fakeLanceDrop(void);
139 static void fakeLanceDropExplodeWait(void);
140 static void fakeLanceDie(void);
141 static void lanceExplode(void);
142 static void splitInHalfInit(void);
143 static void splitInHalf(void);
144 static void splitInHalfMove(void);
145 static void cloneCheck(void);
146 static void cloneDie(void);
147 static void dropAttackInit(void);
148 static void dropAttackMoveToTop(void);
149 static void dropAttackFollowPlayer(void);
150 static void dropAttack(void);
151 static void creditsMove(void);
152 static void addLance(void);
153 static void lanceCreditsMove(void);
154 static void fallout(void);
155 static void falloutWait(void);
156 
addGargoyle(int x,int y,char * name)157 Entity *addGargoyle(int x, int y, char *name)
158 {
159 	Entity *e = getFreeEntity();
160 
161 	if (e == NULL)
162 	{
163 		showErrorAndExit("No free slots to add the Gargoyle");
164 	}
165 
166 	loadProperties(name, e);
167 
168 	e->x = x;
169 	e->y = y;
170 
171 	e->action = &init;
172 
173 	e->draw = &drawLoopingAnimationToMap;
174 	e->touch = &entityTouch;
175 	e->die = ¨
176 	e->takeDamage = NULL;
177 	e->fallout = &fallout;
178 
179 	e->creditsAction = &creditsMove;
180 
181 	e->type = ENEMY;
182 
183 	setEntityAnimation(e, "FACE_FRONT_CROUCH");
184 
185 	return e;
186 }
187 
init()188 static void init()
189 {
190 	switch (self->mental)
191 	{
192 		case -1:
193 			self->flags &= ~FLY;
194 
195 			setEntityAnimation(self, "REACH_STONE");
196 
197 			self->takeDamage = &stoneTakeDamage;
198 
199 			self->touch = &stoneTouch;
200 
201 			self->activate = &activate;
202 
203 			self->action = &dieFinish;
204 		break;
205 
206 		case 0:
207 			addStoneCoat();
208 
209 			self->action = &initialise;
210 		break;
211 
212 		default:
213 			setEntityAnimation(self, "FACE_FRONT_CROUCH");
214 
215 			self->action = &introFinish;
216 		break;
217 	}
218 }
219 
initialise()220 static void initialise()
221 {
222 	if (self->active == TRUE)
223 	{
224 		self->action = &doIntro;
225 	}
226 
227 	checkToMap(self);
228 }
229 
doIntro()230 static void doIntro()
231 {
232 	int i;
233 	Entity *e;
234 
235 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
236 	{
237 		shakeScreen(MEDIUM, 30);
238 
239 		playSoundToMap("sound/common/crash", BOSS_CHANNEL, self->x, self->y, 0);
240 
241 		for (i=0;i<30;i++)
242 		{
243 			e = addSmoke(self->x + (prand() % self->w), self->y + self->h, "decoration/dust");
244 
245 			if (e != NULL)
246 			{
247 				e->y -= prand() % e->h;
248 			}
249 		}
250 
251 		self->thinkTime = 60;
252 
253 		self->action = &introFinish;
254 	}
255 
256 	checkToMap(self);
257 }
258 
introFinish()259 static void introFinish()
260 {
261 	self->thinkTime--;
262 
263 	if (self->thinkTime <= 0)
264 	{
265 		if (self->mental == 0)
266 		{
267 			playSoundToMap("sound/boss/gargoyle/gargoyle_stone_to_flesh", BOSS_CHANNEL, self->x, self->y, -1);
268 
269 			self->mental = 1;
270 		}
271 
272 		else if (self->mental == 2)
273 		{
274 			stopSound(BOSS_CHANNEL);
275 
276 			playDefaultBossMusic();
277 
278 			self->maxThinkTime = 0;
279 
280 			setContinuePoint(FALSE, self->name, NULL);
281 
282 			initBossHealthBar();
283 
284 			self->takeDamage = &takeDamage;
285 
286 			self->action = &entityWait;
287 
288 			self->thinkTime = 90;
289 
290 			self->flags |= LIMIT_TO_SCREEN;
291 		}
292 	}
293 }
294 
attackFinished()295 static void attackFinished()
296 {
297 	Target *t = getTargetByName("GARGOYLE_BOTTOM_TARGET");
298 
299 	if (t == NULL)
300 	{
301 		showErrorAndExit("Gargoyle cannot find target");
302 	}
303 
304 	facePlayer();
305 
306 	setEntityAnimation(self, "FLY");
307 
308 	self->flags |= (FLY|UNBLOCKABLE);
309 
310 	self->targetX = self->x;
311 	self->targetY = t->y;
312 
313 	calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
314 
315 	self->dirX *= 3;
316 	self->dirY *= 3;
317 
318 	self->thinkTime = 30;
319 
320 	self->action = &attackFinishedMoveUp;
321 
322 	checkToMap(self);
323 }
324 
attackFinishedMoveUp()325 static void attackFinishedMoveUp()
326 {
327 	if (atTarget())
328 	{
329 		self->thinkTime--;
330 
331 		if (self->thinkTime <= 0)
332 		{
333 			self->targetX = getMapStartX() + prand() % (SCREEN_WIDTH - self->w);
334 			self->targetY = self->y;
335 
336 			calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
337 
338 			self->dirX *= 3;
339 			self->dirY *= 3;
340 
341 			self->thinkTime = 30;
342 
343 			self->action = &attackFinishedMoveHorizontal;
344 		}
345 	}
346 
347 	checkToMap(self);
348 }
349 
attackFinishedMoveHorizontal()350 static void attackFinishedMoveHorizontal()
351 {
352 	if (atTarget())
353 	{
354 		self->thinkTime = 0;
355 
356 		self->flags &= ~UNBLOCKABLE;
357 
358 		self->action = &entityWait;
359 	}
360 
361 	facePlayer();
362 
363 	checkToMap(self);
364 }
365 
entityWait()366 static void entityWait()
367 {
368 	self->thinkTime--;
369 
370 	if (self->thinkTime <= 0 && player.health > 0)
371 	{
372 		self->startX = getMapStartX();
373 		self->endX   = getMapStartX() + SCREEN_WIDTH - self->w - 1;
374 
375 		if ((self->target == NULL || self->target->inUse == FALSE) && self->maxThinkTime < 3)
376 		{
377 			self->action = &createLanceInit;
378 		}
379 
380 		else
381 		{
382 			switch (self->maxThinkTime)
383 			{
384 				case 0:
385 					if (self->health == self->maxHealth / 4)
386 					{
387 						self->action = &lanceThrowInit;
388 					}
389 
390 					else
391 					{
392 						if (player.element == SLIME)
393 						{
394 							self->action = &petrifyAttackInit;
395 						}
396 
397 						else
398 						{
399 							switch (prand() % 3)
400 							{
401 								case 0:
402 									self->action = &lanceStabInit;
403 								break;
404 
405 								case 1:
406 									self->action = &weaponRemoveBlastInit;
407 								break;
408 
409 								default:
410 									self->action = &lightningGridAttackInit;
411 								break;
412 							}
413 						}
414 					}
415 				break;
416 
417 				case 1:
418 					if (self->health == self->maxHealth / 4)
419 					{
420 						self->action = &lanceThrowInit;
421 					}
422 
423 					else
424 					{
425 						switch (prand() % 3)
426 						{
427 							case 0:
428 								self->action = &lanceStabInit;
429 							break;
430 
431 							case 1:
432 								self->action = &petrifyAttackInit;
433 							break;
434 
435 							default:
436 								self->action = &bridgeDestroyInit;
437 							break;
438 						}
439 					}
440 				break;
441 
442 				case 2:
443 					if (self->health == self->maxHealth / 4)
444 					{
445 						self->action = &lanceThrowInit;
446 					}
447 
448 					else
449 					{
450 						if (player.element == SLIME)
451 						{
452 							self->action = &petrifyAttackInit;
453 						}
454 
455 						else
456 						{
457 							switch (prand() % 4)
458 							{
459 								case 0:
460 									self->action = &lanceStabInit;
461 								break;
462 
463 								case 1:
464 									self->action = &weaponRemoveBlastInit;
465 								break;
466 
467 								case 2:
468 									self->action = &petrifyAttackInit;
469 								break;
470 
471 								default:
472 									self->action = &invisibleAttackInit;
473 								break;
474 							}
475 						}
476 					}
477 				break;
478 
479 				case 3:
480 					self->action = &splitInHalfInit;
481 				break;
482 
483 				default:
484 					switch (prand() % 2)
485 					{
486 						case 0:
487 							self->action = &dropAttackInit;
488 						break;
489 
490 						case 1:
491 							self->action = &weaponRemoveBlastInit;
492 						break;
493 					}
494 				break;
495 			}
496 		}
497 	}
498 
499 	checkToMap(self);
500 }
501 
splitInHalfInit()502 static void splitInHalfInit()
503 {
504 	self->maxThinkTime = 4;
505 
506 	setEntityAnimation(self, "DROP_ATTACK");
507 
508 	self->thinkTime = 60;
509 
510 	self->action = &splitInHalf;
511 
512 	checkToMap(self);
513 }
514 
splitInHalf()515 static void splitInHalf()
516 {
517 	Entity *e;
518 
519 	self->thinkTime--;
520 
521 	if (self->thinkTime <= 0)
522 	{
523 		e = getFreeEntity();
524 
525 		if (e == NULL)
526 		{
527 			showErrorAndExit("No free slots to add the Gargoyle's Duplicate");
528 		}
529 
530 		loadProperties(self->name, e);
531 
532 		setEntityAnimation(e, "DROP_ATTACK");
533 
534 		e->x = self->x;
535 		e->y = self->y;
536 
537 		e->action = &splitInHalfMove;
538 
539 		e->draw = &drawLoopingAnimationToMap;
540 
541 		e->touch = &entityTouch;
542 
543 		e->takeDamage = &entityTakeDamageNoFlinch;
544 
545 		e->die = &cloneDie;
546 
547 		e->pain = &enemyPain;
548 
549 		e->type = ENEMY;
550 
551 		e->head = self;
552 
553 		e->flags |= LIMIT_TO_SCREEN|DO_NOT_PERSIST|FLY;
554 
555 		e->targetX = self->x - self->w;
556 
557 		e->targetY = self->y;
558 
559 		calculatePath(e->x, e->y, e->targetX, e->targetY, &e->dirX, &e->dirY);
560 
561 		e->maxThinkTime = self->maxThinkTime;
562 
563 		e->thinkTime = 0;
564 
565 		e->startX = getMapStartX();
566 		e->endX   = getMapStartX() + SCREEN_WIDTH - e->w;
567 
568 		e->health = self->maxHealth;
569 
570 		self->action = &splitInHalfMove;
571 
572 		self->targetX = self->x + self->w;
573 
574 		self->targetY = self->y;
575 
576 		calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
577 
578 		self->target = e;
579 
580 		self->thinkTime = 0;
581 	}
582 }
583 
splitInHalfMove()584 static void splitInHalfMove()
585 {
586 	checkToMap(self);
587 
588 	if (atTarget() || self->dirX == 0)
589 	{
590 		if (self->head != NULL && self->head->dirX == 0)
591 		{
592 			self->flags &= ~NO_DRAW;
593 
594 			self->action = &attackFinished;
595 		}
596 
597 		else if (self->target != NULL && self->target->dirX == 0)
598 		{
599 			self->flags &= ~NO_DRAW;
600 
601 			self->action = &attackFinished;
602 		}
603 	}
604 
605 	else
606 	{
607 		self->thinkTime++;
608 
609 		if (self->thinkTime % 2 == 0)
610 		{
611 			self->flags ^= NO_DRAW;
612 		}
613 	}
614 }
615 
dropAttackInit()616 static void dropAttackInit()
617 {
618 	Target *t;
619 
620 	t = getTargetByName("GARGOYLE_TOP_TARGET");
621 
622 	if (t == NULL)
623 	{
624 		showErrorAndExit("Gargoyle cannot find target");
625 	}
626 
627 	self->targetX = self->x;
628 	self->targetY = t->y;
629 
630 	self->flags |= FLY;
631 
632 	calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
633 
634 	self->dirX *= 5;
635 	self->dirY *= 5;
636 
637 	self->action = &dropAttackMoveToTop;
638 
639 	checkToMap(self);
640 
641 	cloneCheck();
642 }
643 
dropAttackMoveToTop()644 static void dropAttackMoveToTop()
645 {
646 	if (atTarget())
647 	{
648 		setEntityAnimation(self, "DROP_ATTACK_READY");
649 
650 		self->action = &dropAttackFollowPlayer;
651 	}
652 
653 	checkToMap(self);
654 
655 	cloneCheck();
656 }
657 
dropAttackFollowPlayer()658 static void dropAttackFollowPlayer()
659 {
660 	float target;
661 
662 	target = player.x - self->w / 2 + player.w / 2;
663 
664 	/* Move above the player */
665 
666 	if (fabs(target - self->x) <= fabs(self->dirX))
667 	{
668 		self->targetY = self->y - self->h;
669 
670 		self->dirX = 0;
671 
672 		self->thinkTime = 30;
673 
674 		self->action = &dropAttack;
675 	}
676 
677 	else
678 	{
679 		self->dirX = self->speed * 1.5;
680 
681 		self->x += target > self->x ? self->dirX : -self->dirX;
682 
683 		if (self->x < self->startX)
684 		{
685 			self->x = self->startX;
686 
687 			/* Drop if at the edge of the screen */
688 
689 			if (self->x == getMapStartX())
690 			{
691 				self->dirX = 0;
692 
693 				self->thinkTime = 30;
694 
695 				self->action = &dropAttack;
696 			}
697 		}
698 
699 		else if (self->x > self->endX)
700 		{
701 			self->x = self->endX;
702 
703 			/* Drop if at the edge of the screen */
704 
705 			if (self->x == getMapStartX() + SCREEN_WIDTH - self->w)
706 			{
707 				self->dirX = 0;
708 
709 				self->thinkTime = 30;
710 
711 				self->action = &dropAttack;
712 			}
713 		}
714 	}
715 
716 	cloneCheck();
717 }
718 
dropAttack()719 static void dropAttack()
720 {
721 	int i;
722 	long onGround = self->flags & ON_GROUND;
723 	Entity *e;
724 
725 	self->thinkTime--;
726 
727 	if (self->thinkTime <= 0)
728 	{
729 		setEntityAnimation(self, "DROP_ATTACK");
730 
731 		self->flags &= ~FLY;
732 
733 		if (landedOnGround(onGround) == TRUE)
734 		{
735 			playSoundToMap("sound/enemy/red_grub/thud", -1, self->x, self->y, 0);
736 
737 			for (i=0;i<30;i++)
738 			{
739 				e = addSmoke(self->x + (prand() % self->w), self->y + self->h, "decoration/dust");
740 
741 				if (e != NULL)
742 				{
743 					e->y -= prand() % e->h;
744 				}
745 			}
746 
747 			self->thinkTime = 30;
748 		}
749 
750 		if (self->standingOn != NULL || (self->flags & ON_GROUND))
751 		{
752 			self->thinkTime--;
753 
754 			if (self->thinkTime <= 0)
755 			{
756 				self->action = &attackFinished;
757 			}
758 		}
759 	}
760 
761 	checkToMap(self);
762 
763 	cloneCheck();
764 }
765 
lightningGridAttackInit()766 static void lightningGridAttackInit()
767 {
768 	self->flags &= ~FLY;
769 
770 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
771 	{
772 		setEntityAnimation(self, "STAND");
773 
774 		self->thinkTime = 30;
775 
776 		self->action = &lightningGridAttack;
777 	}
778 
779 	checkToMap(self);
780 }
781 
lightningGridAttack()782 static void lightningGridAttack()
783 {
784 	int i, x, orbCount;
785 	Target *t;
786 	Entity *e;
787 
788 	self->thinkTime--;
789 
790 	if (self->thinkTime <= 0)
791 	{
792 		setEntityAnimation(self, "CREATE_LANCE");
793 
794 		self->mental = 0;
795 
796 		self->endY = 0;
797 
798 		orbCount = 6;
799 
800 		x = getMapStartX();
801 
802 		t = getTargetByName("GARGOYLE_TOP_TARGET");
803 
804 		if (t == NULL)
805 		{
806 			showErrorAndExit("Gargoyle cannot find target");
807 		}
808 
809 		for (i=0;i<orbCount+1;i++)
810 		{
811 			e = getFreeEntity();
812 
813 			if (e == NULL)
814 			{
815 				showErrorAndExit("No free slots to add a lightning orb");
816 			}
817 
818 			loadProperties("boss/gargoyle_lightning_orb", e);
819 
820 			e->x = self->x + self->w / 2 - e->w / 2;
821 			e->y = self->y + self->h / 2 - e->h / 2;
822 
823 			e->targetX = x - e->w / 2;
824 			e->targetY = t->y;
825 
826 			e->endY = self->y + self->h;
827 
828 			calculatePath(e->x, e->y, e->targetX, e->targetY, &e->dirX, &e->dirY);
829 
830 			e->dirX *= 12;
831 			e->dirY *= 12;
832 
833 			e->action = &orbMoveToTarget;
834 
835 			e->draw = &drawLoopingAnimationToMap;
836 			e->touch = NULL;
837 			e->takeDamage = NULL;
838 
839 			e->type = ENEMY;
840 
841 			e->thinkTime = 30;
842 
843 			e->mental = 1;
844 
845 			e->health = i == 0 ? -1 : 0;
846 
847 			e->head = self;
848 
849 			x += SCREEN_WIDTH / orbCount;
850 
851 			self->mental++;
852 
853 			self->endY++;
854 		}
855 
856 		setEntityAnimation(e, "STAND");
857 
858 		self->thinkTime = 60;
859 
860 		self->action = &lightningGridAttackWait;
861 	}
862 
863 	checkToMap(self);
864 }
865 
orbMoveToTarget()866 static void orbMoveToTarget()
867 {
868 	if (atTarget())
869 	{
870 		if (self->mental == 1)
871 		{
872 			self->head->endY--;
873 
874 			self->mental = 0;
875 		}
876 
877 		else if (self->head->endY <= 0)
878 		{
879 			self->thinkTime--;
880 
881 			if (self->thinkTime <= 0)
882 			{
883 				self->startY = self->targetY;
884 
885 				self->action = &orbCastLightning1;
886 			}
887 		}
888 	}
889 
890 	checkToMap(self);
891 }
892 
orbCastLightning1()893 static void orbCastLightning1()
894 {
895 	int i;
896 	Entity *e;
897 
898 	self->thinkTime--;
899 
900 	if (self->thinkTime <= 0)
901 	{
902 		if (self->health == -1)
903 		{
904 			playSoundToMap("sound/enemy/thunder_cloud/lightning", -1, self->x, self->y, 0);
905 		}
906 
907 		for (i=self->endY-32;i>=self->startY;i-=32)
908 		{
909 			e = getFreeEntity();
910 
911 			if (e == NULL)
912 			{
913 				showErrorAndExit("No free slots to add lightning");
914 			}
915 
916 			loadProperties("enemy/lightning", e);
917 
918 			setEntityAnimation(e, "STAND");
919 
920 			e->x = self->x + self->w / 2 - e->w / 2;
921 			e->y = i;
922 
923 			e->action = &lightningWait;
924 
925 			e->draw = &drawLoopingAnimationToMap;
926 			e->touch = &entityTouch;
927 
928 			e->head = self;
929 
930 			e->currentFrame = prand() % 6;
931 
932 			e->face = RIGHT;
933 
934 			e->thinkTime = 90;
935 		}
936 
937 		e = addSmallRock(self->x, self->endY, "common/small_rock");
938 
939 		e->x += (self->w - e->w) / 2;
940 		e->y -= e->h;
941 
942 		e->dirX = -3;
943 		e->dirY = -8;
944 
945 		e = addSmallRock(self->x, self->endY, "common/small_rock");
946 
947 		e->x += (self->w - e->w) / 2;
948 		e->y -= e->h;
949 
950 		e->dirX = 3;
951 		e->dirY = -8;
952 
953 		self->action = &orbCastLightningFinish1;
954 
955 		self->thinkTime = 90;
956 	}
957 }
958 
orbCastLightningFinish1()959 static void orbCastLightningFinish1()
960 {
961 	self->thinkTime--;
962 
963 	if (self->thinkTime <= 0)
964 	{
965 		self->head->mental--;
966 
967 		self->inUse = FALSE;
968 	}
969 }
970 
lightningGridAttackWait()971 static void lightningGridAttackWait()
972 {
973 	if (self->mental <= 0)
974 	{
975 		self->thinkTime--;
976 
977 		if (self->thinkTime <= 0)
978 		{
979 			self->action = &attackFinished;
980 		}
981 	}
982 
983 	checkToMap(self);
984 }
985 
lanceStabInit()986 static void lanceStabInit()
987 {
988 	self->thinkTime = 30;
989 
990 	self->action = &lanceStabMoveToTarget;
991 
992 	checkToMap(self);
993 }
994 
lanceStabMoveToTarget()995 static void lanceStabMoveToTarget()
996 {
997 	self->thinkTime--;
998 
999 	if (self->thinkTime <= 0)
1000 	{
1001 		facePlayer();
1002 
1003 		calculatePath(self->x, self->y, player.x + player.w / 2 - self->w / 2, player.y + player.h / 2 - self->h / 2, &self->dirX, &self->dirY);
1004 
1005 		self->dirX *= 16;
1006 		self->dirY *= 16;
1007 
1008 		self->action = &lanceStab;
1009 
1010 		self->reactToBlock = &lanceStabReactToBlock;
1011 
1012 		playSoundToMap("sound/boss/gargoyle/gargoyle_lance_stab", -1, self->x, self->y, 0);
1013 	}
1014 
1015 	checkToMap(self);
1016 }
1017 
lanceStab()1018 static void lanceStab()
1019 {
1020 	if (self->dirX == 0 || self->standingOn != NULL)
1021 	{
1022 		self->flags &= ~FLY;
1023 
1024 		self->dirX = self->face == RIGHT ? -5 : 5;
1025 		self->dirY = -6;
1026 
1027 		self->action = &lanceStabFinish;
1028 
1029 		self->thinkTime = 30;
1030 	}
1031 
1032 	checkToMap(self);
1033 }
1034 
lanceStabReactToBlock(Entity * other)1035 static void lanceStabReactToBlock(Entity *other)
1036 {
1037 	self->dirX = 0;
1038 }
1039 
lanceStabFinish()1040 static void lanceStabFinish()
1041 {
1042 	int i;
1043 	long onGround;
1044 	Entity *e;
1045 
1046 	self->reactToBlock = NULL;
1047 
1048 	onGround = self->flags & ON_GROUND;
1049 
1050 	checkToMap(self);
1051 
1052 	if (landedOnGround(onGround) == TRUE)
1053 	{
1054 		setEntityAnimation(self, "STAND");
1055 
1056 		for (i=0;i<5;i++)
1057 		{
1058 			e = addSmoke(self->x + (prand() % self->w), self->y + self->h, "decoration/dust");
1059 
1060 			if (e != NULL)
1061 			{
1062 				e->y -= prand() % e->h;
1063 			}
1064 		}
1065 	}
1066 
1067 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
1068 	{
1069 		self->dirX = 0;
1070 
1071 		self->thinkTime--;
1072 
1073 		if (self->thinkTime <= 0)
1074 		{
1075 			self->action = &attackFinished;
1076 		}
1077 	}
1078 }
1079 
invisibleAttackInit()1080 static void invisibleAttackInit()
1081 {
1082 	self->flags &= ~FLY;
1083 
1084 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
1085 	{
1086 		self->action = &becomeInvisible;
1087 
1088 		self->endY = self->y + self->h;
1089 	}
1090 
1091 	checkToMap(self);
1092 }
1093 
becomeInvisible()1094 static void becomeInvisible()
1095 {
1096 	Target *t;
1097 
1098 	setEntityAnimation(self, "CREATE_LANCE");
1099 
1100 	self->alpha -= 3;
1101 
1102 	if (self->alpha <= 0)
1103 	{
1104 		self->alpha = 255;
1105 
1106 		self->flags |= NO_DRAW;
1107 
1108 		self->mental = 1 + prand() % 3;
1109 
1110 		t = getTargetByName("GARGOYLE_TOP_TARGET");
1111 
1112 		if (t == NULL)
1113 		{
1114 			showErrorAndExit("Gargoyle cannot find target");
1115 		}
1116 
1117 		self->targetX = self->x;
1118 		self->targetY = t->y;
1119 
1120 		calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1121 
1122 		self->dirX *= 5;
1123 		self->dirY *= 5;
1124 
1125 		self->flags |= FLY;
1126 
1127 		self->action = &invisibleAttackMoveToTop;
1128 
1129 		checkToMap(self);
1130 	}
1131 }
1132 
invisibleAttackMoveToTop()1133 static void invisibleAttackMoveToTop()
1134 {
1135 	if (atTarget())
1136 	{
1137 		setEntityAnimation(self, "DROP_ATTACK");
1138 
1139 		self->action = &invisibleAttackFollowPlayer;
1140 	}
1141 
1142 	checkToMap(self);
1143 }
1144 
invisibleAttackFollowPlayer()1145 static void invisibleAttackFollowPlayer()
1146 {
1147 	float target;
1148 
1149 	target = player.x - self->w / 2 + player.w / 2;
1150 
1151 	/* Move above the player */
1152 
1153 	if (fabs(target - self->x) <= fabs(self->dirX))
1154 	{
1155 		self->targetY = self->y - self->h;
1156 
1157 		self->thinkTime = 60;
1158 
1159 		self->dirX = 0;
1160 
1161 		self->action = &invisibleDrop;
1162 	}
1163 
1164 	else
1165 	{
1166 		self->dirX = self->speed * 1.5;
1167 
1168 		self->x += target > self->x ? self->dirX : -self->dirX;
1169 
1170 		if (self->x < self->startX)
1171 		{
1172 			self->x = self->startX;
1173 
1174 			/* Drop if at the edge of the screen */
1175 
1176 			if (self->x == getMapStartX())
1177 			{
1178 				self->thinkTime = 60;
1179 
1180 				self->dirX = 0;
1181 
1182 				self->action = &invisibleDrop;
1183 			}
1184 		}
1185 
1186 		else if (self->x > self->endX)
1187 		{
1188 			self->x = self->endX;
1189 
1190 			/* Drop if at the edge of the screen */
1191 
1192 			if (self->x == getMapStartX() + SCREEN_WIDTH - self->w)
1193 			{
1194 				self->thinkTime = 60;
1195 
1196 				self->dirX = 0;
1197 
1198 				self->action = &invisibleDrop;
1199 			}
1200 		}
1201 	}
1202 }
1203 
invisibleDrop()1204 static void invisibleDrop()
1205 {
1206 	int i;
1207 	Entity *e;
1208 
1209 	self->thinkTime--;
1210 
1211 	if (self->thinkTime <= 0)
1212 	{
1213 		if (self->standingOn != NULL || (self->flags & ON_GROUND))
1214 		{
1215 			shakeScreen(LIGHT, 15);
1216 
1217 			for (i=0;i<30;i++)
1218 			{
1219 				e = addSmoke(self->x + (prand() % self->w), self->y + self->h, "decoration/dust");
1220 
1221 				if (e != NULL)
1222 				{
1223 					e->y -= prand() % e->h;
1224 				}
1225 			}
1226 
1227 			playSoundToMap("sound/enemy/red_grub/thud", -1, self->x, self->y, 0);
1228 
1229 			self->flags &= ~NO_DRAW;
1230 
1231 			self->mental--;
1232 
1233 			self->thinkTime = self->mental <= 0 ? 30 : 60;
1234 
1235 			self->action = &invisibleDropWait;
1236 		}
1237 
1238 		self->flags &= ~FLY;
1239 	}
1240 
1241 	else
1242 	{
1243 		if (self->thinkTime % 2 == 0)
1244 		{
1245 			e = addSmoke(self->x + self->w / 2, self->endY, "decoration/dust");
1246 
1247 			if (e != NULL)
1248 			{
1249 				e->dirX = -(10 + prand() % 30);
1250 
1251 				e->dirX /= 10;
1252 
1253 				e->y -= prand() % e->h;
1254 			}
1255 
1256 			e = addSmoke(self->x + self->w / 2, self->endY, "decoration/dust");
1257 
1258 			if (e != NULL)
1259 			{
1260 				e->dirX = (10 + prand() % 30);
1261 
1262 				e->dirX /= 10;
1263 
1264 				e->y -= prand() % e->h;
1265 			}
1266 		}
1267 	}
1268 
1269 	checkToMap(self);
1270 }
1271 
invisibleDropWait()1272 static void invisibleDropWait()
1273 {
1274 	Target *t;
1275 
1276 	self->thinkTime--;
1277 
1278 	if (self->thinkTime <= 0)
1279 	{
1280 		if (self->mental <= 0)
1281 		{
1282 			self->action = &attackFinished;
1283 		}
1284 
1285 		else
1286 		{
1287 			t = getTargetByName("GARGOYLE_TOP_TARGET");
1288 
1289 			if (t == NULL)
1290 			{
1291 				showErrorAndExit("Gargoyle cannot find target");
1292 			}
1293 
1294 			self->flags |= NO_DRAW;
1295 
1296 			self->targetX = self->x;
1297 			self->targetY = t->y;
1298 
1299 			calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1300 
1301 			self->dirX *= self->speed;
1302 			self->dirY *= self->speed;
1303 
1304 			self->flags |= FLY;
1305 
1306 			self->action = &invisibleAttackMoveToTop;
1307 		}
1308 	}
1309 
1310 	checkToMap(self);
1311 }
1312 
bridgeDestroyInit()1313 static void bridgeDestroyInit()
1314 {
1315 	Target *t = getTargetByName("GARGOYLE_BOTTOM_TARGET");
1316 
1317 	setEntityAnimation(self, "LANCE_THROW_READY");
1318 
1319 	if (t == NULL)
1320 	{
1321 		showErrorAndExit("Gargoyle cannot find target");
1322 	}
1323 
1324 	self->targetX = self->x;
1325 	self->targetY = t->y;
1326 
1327 	calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1328 
1329 	self->dirX *= self->speed;
1330 	self->dirY *= self->speed;
1331 
1332 	self->flags |= FLY;
1333 
1334 	self->action = &bridgeDestroyMoveToTarget;
1335 
1336 	checkToMap(self);
1337 }
1338 
bridgeDestroyMoveToTarget()1339 static void bridgeDestroyMoveToTarget()
1340 {
1341 	if (atTarget())
1342 	{
1343 		self->mental = 5;
1344 
1345 		self->action = &bridgeDestroyFollowPlayer;
1346 	}
1347 
1348 	checkToMap(self);
1349 }
1350 
bridgeDestroyFollowPlayer()1351 static void bridgeDestroyFollowPlayer()
1352 {
1353 	float target;
1354 
1355 	self->thinkTime--;
1356 
1357 	if (self->thinkTime <= 0)
1358 	{
1359 		self->thinkTime = 0;
1360 
1361 		target = player.x - self->w / 2 + player.w / 2;
1362 
1363 		/* Move above the player */
1364 
1365 		if (fabs(target - self->x) <= fabs(self->dirX))
1366 		{
1367 			self->targetY = self->y - self->h;
1368 
1369 			self->thinkTime = 15;
1370 
1371 			self->dirX = 0;
1372 
1373 			if (player.y < getMapStartY() + SCREEN_HEIGHT)
1374 			{
1375 				self->action = &bridgeDestroy;
1376 			}
1377 		}
1378 
1379 		else
1380 		{
1381 			self->dirX = self->speed;
1382 
1383 			self->x += target > self->x ? self->dirX : -self->dirX;
1384 
1385 			if (self->x < self->startX)
1386 			{
1387 				self->x = self->startX;
1388 
1389 				/* Throw if at the edge of the screen */
1390 
1391 				self->thinkTime = 15;
1392 
1393 				self->dirX = 0;
1394 
1395 				if (player.y < getMapStartY() + SCREEN_HEIGHT)
1396 				{
1397 					self->action = &bridgeDestroy;
1398 				}
1399 			}
1400 
1401 			else if (self->x > self->endX)
1402 			{
1403 				self->x = self->endX;
1404 
1405 				/* Throw if at the edge of the screen */
1406 
1407 				self->thinkTime = 15;
1408 
1409 				self->dirX = 0;
1410 
1411 				if (player.y < getMapStartY() + SCREEN_HEIGHT)
1412 				{
1413 					self->action = &bridgeDestroy;
1414 				}
1415 			}
1416 		}
1417 	}
1418 }
1419 
bridgeDestroy()1420 static void bridgeDestroy()
1421 {
1422 	int currentFrame;
1423 	float frameTimer;
1424 
1425 	self->thinkTime--;
1426 
1427 	if (self->thinkTime <= 0)
1428 	{
1429 		currentFrame = self->currentFrame;
1430 		frameTimer = self->frameTimer;
1431 
1432 		setEntityAnimation(self, "LANCE_THROW");
1433 
1434 		self->currentFrame = currentFrame;
1435 		self->frameTimer = frameTimer;
1436 
1437 		playSoundToMap("sound/boss/gargoyle/gargoyle_lance_stab", -1, self->x, self->y, 0);
1438 
1439 		self->target->mental = -2;
1440 
1441 		self->action = &bridgeDestroyWait;
1442 	}
1443 
1444 	checkToMap(self);
1445 }
1446 
bridgeDestroyWait()1447 static void bridgeDestroyWait()
1448 {
1449 	if (self->target->mental == 0)
1450 	{
1451 		setEntityAnimation(self, "LANCE_THROW_READY");
1452 
1453 		self->mental--;
1454 
1455 		self->thinkTime = 60;
1456 
1457 		self->action = self->mental > 0 ? &bridgeDestroyFollowPlayer : &bridgeDestroyFinish;
1458 	}
1459 
1460 	checkToMap(self);
1461 }
1462 
bridgeDestroyFinish()1463 static void bridgeDestroyFinish()
1464 {
1465 	self->thinkTime--;
1466 
1467 	if (self->thinkTime <= 0)
1468 	{
1469 		self->action = &attackFinished;
1470 	}
1471 
1472 	checkToMap(self);
1473 }
1474 
lanceThrowInit()1475 static void lanceThrowInit()
1476 {
1477 	Target *t = getTargetByName("GARGOYLE_MID_TARGET");
1478 
1479 	if (t == NULL)
1480 	{
1481 		showErrorAndExit("Gargoyle cannot find target");
1482 	}
1483 
1484 	self->face = RIGHT;
1485 
1486 	setEntityAnimation(self, "LANCE_THROW_READY");
1487 
1488 	self->targetX = self->x;
1489 	self->targetY = t->y;
1490 
1491 	calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1492 
1493 	self->dirX *= self->speed;
1494 	self->dirY *= self->speed;
1495 
1496 	self->flags |= FLY;
1497 
1498 	self->action = &lanceThrowMoveToTarget;
1499 
1500 	self->thinkTime = 30;
1501 
1502 	checkToMap(self);
1503 }
1504 
lanceThrowMoveToTarget()1505 static void lanceThrowMoveToTarget()
1506 {
1507 	if (atTarget())
1508 	{
1509 		self->thinkTime--;
1510 
1511 		if (self->thinkTime <= 0)
1512 		{
1513 			self->targetX = getMapStartX() + prand() % (SCREEN_WIDTH - self->w);
1514 			self->targetY = self->y;
1515 
1516 			calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1517 
1518 			self->dirX *= 3;
1519 			self->dirY *= 3;
1520 
1521 			self->thinkTime = 60;
1522 
1523 			self->action = &lanceThrow;
1524 		}
1525 	}
1526 
1527 	checkToMap(self);
1528 }
1529 
lanceThrow()1530 static void lanceThrow()
1531 {
1532 	int currentFrame;
1533 	float frameTimer;
1534 
1535 	if (atTarget())
1536 	{
1537 		self->thinkTime--;
1538 
1539 		if (self->thinkTime <= 0)
1540 		{
1541 			currentFrame = self->currentFrame;
1542 			frameTimer = self->frameTimer;
1543 
1544 			setEntityAnimation(self, "LANCE_THROW");
1545 
1546 			self->currentFrame = currentFrame;
1547 			self->frameTimer = frameTimer;
1548 
1549 			playSoundToMap("sound/boss/gargoyle/gargoyle_lance_stab", -1, self->x, self->y, 0);
1550 
1551 			self->target->mental = -1;
1552 
1553 			self->target->dirY = 4;
1554 
1555 			self->action = &lanceThrowWait;
1556 
1557 			self->thinkTime = 30;
1558 		}
1559 	}
1560 
1561 	checkToMap(self);
1562 }
1563 
lanceThrowWait()1564 static void lanceThrowWait()
1565 {
1566 	if (self->target->inUse == FALSE)
1567 	{
1568 		self->thinkTime--;
1569 
1570 		if (self->thinkTime <= 0)
1571 		{
1572 			self->action = &attackFinished;
1573 		}
1574 	}
1575 
1576 	checkToMap(self);
1577 }
1578 
createLanceInit()1579 static void createLanceInit()
1580 {
1581 	self->flags &= ~FLY;
1582 
1583 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
1584 	{
1585 		self->thinkTime = 30;
1586 
1587 		self->action = &createLance;
1588 	}
1589 
1590 	checkToMap(self);
1591 }
1592 
createLance()1593 static void createLance()
1594 {
1595 	Entity *e;
1596 
1597 	setEntityAnimation(self, "CREATE_LANCE");
1598 
1599 	self->face = RIGHT;
1600 
1601 	self->mental = 1;
1602 
1603 	self->thinkTime--;
1604 
1605 	if (self->thinkTime <= 0)
1606 	{
1607 		playSoundToMap("sound/boss/gargoyle/gargoyle_create_lance", -1, self->x, self->y, 0);
1608 
1609 		e = getFreeEntity();
1610 
1611 		if (e == NULL)
1612 		{
1613 			showErrorAndExit("No free slots to add the Gargoyle's Lance");
1614 		}
1615 
1616 		switch (self->maxThinkTime)
1617 		{
1618 			case 0:
1619 				loadProperties("boss/gargoyle_lance_1", e);
1620 			break;
1621 
1622 			case 1:
1623 				loadProperties("boss/gargoyle_lance_2", e);
1624 			break;
1625 
1626 			default:
1627 				loadProperties("boss/gargoyle_lance_3", e);
1628 			break;
1629 		}
1630 
1631 		e->action = &lanceAppearWait;
1632 
1633 		e->draw = &drawLoopingAnimationToMap;
1634 
1635 		e->type = ENEMY;
1636 
1637 		e->head = self;
1638 
1639 		setEntityAnimation(e, "LANCE_APPEAR");
1640 
1641 		e->animationCallback = &lanceAppearFinish;
1642 
1643 		self->target = e;
1644 
1645 		e->maxThinkTime = self->maxThinkTime;
1646 
1647 		self->action = &createLanceWait;
1648 
1649 		self->thinkTime = 90;
1650 	}
1651 
1652 	checkToMap(self);
1653 }
1654 
createLanceWait()1655 static void createLanceWait()
1656 {
1657 	if (self->mental == 0)
1658 	{
1659 		self->thinkTime--;
1660 
1661 		if (self->thinkTime <= 0)
1662 		{
1663 			self->health = self->maxHealth;
1664 
1665 			facePlayer();
1666 
1667 			setEntityAnimation(self, "STAND");
1668 
1669 			self->action = &attackFinished;
1670 		}
1671 	}
1672 
1673 	checkToMap(self);
1674 }
1675 
lanceAppearWait()1676 static void lanceAppearWait()
1677 {
1678 	self->face = self->head->face;
1679 
1680 	if (self->face == LEFT)
1681 	{
1682 		self->x = self->head->x + self->head->w - self->w - self->offsetX;
1683 	}
1684 
1685 	else
1686 	{
1687 		self->x = self->head->x + self->offsetX;
1688 	}
1689 
1690 	self->y = self->head->y + self->offsetY;
1691 }
1692 
lanceAppearFinish()1693 static void lanceAppearFinish()
1694 {
1695 	self->head->mental = 0;
1696 
1697 	self->action = &lanceWait;
1698 }
1699 
lanceWait()1700 static void lanceWait()
1701 {
1702 	self->face = self->head->face;
1703 
1704 	setEntityAnimation(self, getAnimationTypeAtIndex(self->head));
1705 
1706 	if (self->face == LEFT)
1707 	{
1708 		self->x = self->head->x + self->head->w - self->w - self->offsetX;
1709 	}
1710 
1711 	else
1712 	{
1713 		self->x = self->head->x + self->offsetX;
1714 	}
1715 
1716 	self->y = self->head->y + self->offsetY;
1717 
1718 	if (self->head->flags & FLASH)
1719 	{
1720 		self->flags |= FLASH;
1721 	}
1722 
1723 	else
1724 	{
1725 		self->flags &= ~FLASH;
1726 	}
1727 
1728 	if (self->head->flags & NO_DRAW)
1729 	{
1730 		self->flags |= NO_DRAW;
1731 	}
1732 
1733 	else
1734 	{
1735 		self->flags &= ~NO_DRAW;
1736 	}
1737 
1738 	if (self->mental == -1)
1739 	{
1740 		self->dirY = 14;
1741 
1742 		setEntityAnimation(self, "LANCE_THROW");
1743 
1744 		self->pain = &enemyPain;
1745 
1746 		self->touch = &entityTouch;
1747 
1748 		self->fallout = &lanceThrowFallout;
1749 
1750 		self->action = &lanceDrop;
1751 	}
1752 
1753 	else if (self->mental == -2)
1754 	{
1755 		self->dirY = 14;
1756 
1757 		setEntityAnimation(self, "LANCE_THROW");
1758 
1759 		self->flags |= ATTACKING;
1760 
1761 		self->touch = &entityTouch;
1762 
1763 		self->fallout = &lanceFallout;
1764 
1765 		self->action = &lanceDestroyBridge;
1766 	}
1767 
1768 	self->alpha = self->head->alpha;
1769 }
1770 
lanceDestroyBridge()1771 static void lanceDestroyBridge()
1772 {
1773 	checkToMap(self);
1774 
1775 	if (self->y > getMapStartY() + SCREEN_HEIGHT)
1776 	{
1777 		lanceFallout();
1778 	}
1779 }
1780 
lanceFallout()1781 static void lanceFallout()
1782 {
1783 	int distance;
1784 	float checkpointX, checkpointY, x;
1785 	int startX, endX;
1786 	EntityList *el, *entities;
1787 
1788 	entities = getEntities();
1789 
1790 	distance = 0;
1791 
1792 	startX = getMapStartX();
1793 	endX   = getMapStartX() + SCREEN_WIDTH;
1794 
1795 	checkpointX = startX;
1796 
1797 	/* Get a piece furthest away from the boss */
1798 
1799 	for (el=entities->next;el!=NULL;el=el->next)
1800 	{
1801 		if (el->entity->inUse == TRUE && el->entity->type == WEAK_WALL && el->entity->mental == -1 &&
1802 			el->entity->touch != NULL && el->entity->x >= startX && el->entity->x < endX)
1803 		{
1804 			if (abs(el->entity->x - self->head->x) > distance)
1805 			{
1806 				distance = abs(el->entity->x - self->head->x);
1807 
1808 				checkpointX = el->entity->x;
1809 			}
1810 		}
1811 	}
1812 
1813 	getCheckpoint(&x, &checkpointY);
1814 
1815 	setCheckpoint(checkpointX, checkpointY);
1816 
1817 	self->currentFrame = self->head->currentFrame;
1818 	self->frameTimer = self->head->frameTimer;
1819 
1820 	self->y = self->head->y + self->offsetY;
1821 
1822 	self->action = &lanceAttackTeleportFinish;
1823 }
1824 
lanceThrowFallout()1825 static void lanceThrowFallout()
1826 {
1827 	self->currentFrame = self->head->currentFrame;
1828 	self->frameTimer = self->head->frameTimer;
1829 
1830 	self->y = self->head->y + self->offsetY;
1831 
1832 	self->action = &lanceThrowTeleportFinish;
1833 }
1834 
lanceDrop()1835 static void lanceDrop()
1836 {
1837 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
1838 	{
1839 		self->takeDamage = &entityTakeDamageNoFlinch;
1840 
1841 		self->die = &lanceDie;
1842 
1843 		setEntityAnimation(self, "LANCE_IN_GROUND");
1844 
1845 		playSoundToMap("sound/enemy/ground_spear/spear", -1, self->x, self->y, 0);
1846 
1847 		self->head->maxThinkTime++;
1848 
1849 		switch (self->maxThinkTime)
1850 		{
1851 			case 0:
1852 				self->action = &lanceAttack1;
1853 			break;
1854 
1855 			case 1:
1856 				self->action = &lanceAttack2;
1857 			break;
1858 
1859 			default:
1860 				self->action = &lanceAttack3;
1861 			break;
1862 		}
1863 
1864 		self->thinkTime = 30;
1865 	}
1866 
1867 	checkToMap(self);
1868 }
1869 
lanceDie()1870 static void lanceDie()
1871 {
1872 	int i;
1873 	Entity *e;
1874 	char name[MAX_VALUE_LENGTH];
1875 
1876 	playSoundToMap("sound/enemy/centurion/centurion_die", -1, self->x, self->y, 0);
1877 
1878 	SNPRINTF(name, sizeof(name), "%s_piece", self->name);
1879 
1880 	for (i=0;i<6;i++)
1881 	{
1882 		e = addTemporaryItem(name, self->x, self->y, self->face, 0, 0);
1883 
1884 		e->x += (self->w - e->w) / 2;
1885 		e->y += (self->w - e->w) / 2;
1886 
1887 		e->dirX = (prand() % 5) * (prand() % 2 == 0 ? -1 : 1);
1888 		e->dirY = ITEM_JUMP_HEIGHT + (prand() % ITEM_JUMP_HEIGHT);
1889 
1890 		setEntityAnimationByID(e, i);
1891 
1892 		e->thinkTime = 60 + (prand() % 60);
1893 	}
1894 
1895 	self->inUse = FALSE;
1896 }
1897 
petrifyAttackInit()1898 static void petrifyAttackInit()
1899 {
1900 	self->flags &= ~FLY;
1901 
1902 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
1903 	{
1904 		setEntityAnimation(self, "STAND");
1905 
1906 		self->thinkTime = 30;
1907 
1908 		self->action = &petrifyAttack;
1909 
1910 		self->mental = 0;
1911 	}
1912 
1913 	checkToMap(self);
1914 }
1915 
petrifyAttack()1916 static void petrifyAttack()
1917 {
1918 	self->thinkTime--;
1919 
1920 	if (self->thinkTime <= 0)
1921 	{
1922 		if (self->mental == 0)
1923 		{
1924 			setEntityAnimation(self, "CREATE_LANCE");
1925 
1926 			self->thinkTime = 60;
1927 
1928 			self->mental = 1;
1929 		}
1930 
1931 		else
1932 		{
1933 			playSoundToMap("sound/boss/gargoyle/gargoyle_petrify", -1, self->x, self->y, 0);
1934 
1935 			fadeFromColour(255, 255, 0, 30);
1936 
1937 			if (player.element == SLIME)
1938 			{
1939 				player.die();
1940 			}
1941 
1942 			else
1943 			{
1944 				setPlayerPetrified();
1945 			}
1946 
1947 			self->thinkTime = 300;
1948 
1949 			self->action = &petrifyAttackWait;
1950 		}
1951 	}
1952 
1953 	checkToMap(self);
1954 }
1955 
petrifyAttackWait()1956 static void petrifyAttackWait()
1957 {
1958 	self->thinkTime--;
1959 
1960 	if (self->thinkTime <= 0)
1961 	{
1962 		self->action = &attackFinished;
1963 	}
1964 
1965 	checkToMap(self);
1966 }
1967 
weaponRemoveBlastInit()1968 static void weaponRemoveBlastInit()
1969 {
1970 	self->flags &= ~FLY;
1971 
1972 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
1973 	{
1974 		setEntityAnimation(self, "STAND");
1975 
1976 		self->thinkTime = 30;
1977 
1978 		self->action = &weaponRemoveBlast;
1979 
1980 		self->mental = 1 + prand() % 3;
1981 	}
1982 
1983 	checkToMap(self);
1984 
1985 	cloneCheck();
1986 }
1987 
weaponRemoveBlast()1988 static void weaponRemoveBlast()
1989 {
1990 	Entity *e;
1991 
1992 	facePlayer();
1993 
1994 	self->thinkTime--;
1995 
1996 	if (self->thinkTime <= 0)
1997 	{
1998 		setEntityAnimation(self, "WEAPON_REMOVE");
1999 
2000 		playSoundToMap("sound/boss/snake_boss/snake_boss_shot", -1, self->x, self->y, 0);
2001 
2002 		e = addProjectile("boss/gargoyle_weapon_remove_blast", self, self->x, self->y, self->face == LEFT ? -8 : 8, 0);
2003 
2004 		e->face = self->face;
2005 
2006 		e->damage = 0;
2007 
2008 		if (self->face == LEFT)
2009 		{
2010 			e->x = self->x + self->w - e->w - e->offsetX;
2011 		}
2012 
2013 		else
2014 		{
2015 			e->x = self->x + e->offsetX;
2016 		}
2017 
2018 		e->y = self->y + e->offsetY;
2019 
2020 		e->touch = &blastRemoveWeapon;
2021 
2022 		e->flags |= FLY;
2023 
2024 		e->thinkTime = 1200;
2025 
2026 		self->action = &weaponRemoveBlastFinish;
2027 
2028 		self->mental--;
2029 
2030 		self->thinkTime = self->mental > 0 ? 30 : 60;
2031 	}
2032 
2033 	checkToMap(self);
2034 
2035 	cloneCheck();
2036 }
2037 
blastRemoveWeapon(Entity * other)2038 static void blastRemoveWeapon(Entity *other)
2039 {
2040 	Entity *e;
2041 
2042 	if (other->type == PLAYER && !(other->flags & INVULNERABLE))
2043 	{
2044 		e = removePlayerWeapon();
2045 
2046 		if (e != NULL)
2047 		{
2048 			e->x = self->x;
2049 			e->y = self->y;
2050 
2051 			e->dirX = (6 + prand() % 3) * (prand() % 2 == 0 ? -1 : 1);
2052 			e->dirY = -12;
2053 
2054 			setCustomAction(e, &invulnerable, 120, 0, 0);
2055 
2056 			addExitTrigger(e);
2057 
2058 			e->flags |= LIMIT_TO_SCREEN;
2059 		}
2060 
2061 		e = removePlayerShield();
2062 
2063 		if (e != NULL)
2064 		{
2065 			e->x = self->x;
2066 			e->y = self->y;
2067 
2068 			e->dirX = (6 + prand() % 3) * (prand() % 2 == 0 ? -1 : 1);
2069 			e->dirY = -12;
2070 
2071 			setCustomAction(e, &invulnerable, 120, 0, 0);
2072 
2073 			addExitTrigger(e);
2074 
2075 			e->flags |= LIMIT_TO_SCREEN;
2076 		}
2077 
2078 		playSoundToMap("sound/common/punch", EDGAR_CHANNEL, self->x, self->y, 0);
2079 
2080 		setCustomAction(other, &invulnerable, 60, 0, 0);
2081 
2082 		setPlayerStunned(30);
2083 
2084 		other->x -= other->dirX;
2085 		other->y -= other->dirY;
2086 
2087 		other->dirX = (6 + prand() % 3) * (self->dirX < 0 ? -1 : 1);
2088 		other->dirY = -8;
2089 
2090 		self->inUse = FALSE;
2091 	}
2092 }
2093 
weaponRemoveBlastFinish()2094 static void weaponRemoveBlastFinish()
2095 {
2096 	self->thinkTime--;
2097 
2098 	if (self->thinkTime <= 0)
2099 	{
2100 		self->action = self->mental > 0 ? &weaponRemoveBlast : &attackFinished;
2101 	}
2102 
2103 	checkToMap(self);
2104 
2105 	cloneCheck();
2106 }
2107 
takeDamage(Entity * other,int damage)2108 static void takeDamage(Entity *other, int damage)
2109 {
2110 	Entity *temp;
2111 
2112 	if (self->flags & INVULNERABLE)
2113 	{
2114 		return;
2115 	}
2116 
2117 	/* Take minimal damage from bombs */
2118 
2119 	if (other->type == EXPLOSION)
2120 	{
2121 		damage = 1;
2122 	}
2123 
2124 	if (damage != 0)
2125 	{
2126 		self->health -= damage;
2127 
2128 		if (self->health > 0)
2129 		{
2130 			setCustomAction(self, &flashWhite, 6, 0, 0);
2131 
2132 			/* Don't make an enemy invulnerable from a projectile hit, allows multiple hits */
2133 
2134 			if (other->type != PROJECTILE)
2135 			{
2136 				setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
2137 			}
2138 
2139 			if (self->pain != NULL)
2140 			{
2141 				self->pain();
2142 			}
2143 
2144 			/* Don't die if you've still got lances to wield */
2145 
2146 			if (self->maxThinkTime < 3 && self->health < self->maxHealth / 4)
2147 			{
2148 				self->health = self->maxHealth / 4;
2149 			}
2150 		}
2151 
2152 		else
2153 		{
2154 			self->health = 0;
2155 
2156 			if (self->maxThinkTime >= 3)
2157 			{
2158 				self->damage = 0;
2159 
2160 				self->die();
2161 			}
2162 		}
2163 
2164 		if (other->type == PROJECTILE)
2165 		{
2166 			temp = self;
2167 
2168 			self = other;
2169 
2170 			self->die();
2171 
2172 			self = temp;
2173 		}
2174 	}
2175 
2176 	if (other->type == PROJECTILE)
2177 	{
2178 		temp = self;
2179 
2180 		self = other;
2181 
2182 		self->die();
2183 
2184 		self = temp;
2185 	}
2186 }
2187 
lanceAttack1()2188 static void lanceAttack1()
2189 {
2190 	self->thinkTime--;
2191 
2192 	if (self->thinkTime <= 0)
2193 	{
2194 		self->mental = 2;
2195 
2196 		self->endX = 1;
2197 
2198 		self->action = &createLightningOrb;
2199 	}
2200 
2201 	checkToMap(self);
2202 }
2203 
lanceAttack2()2204 static void lanceAttack2()
2205 {
2206 	self->thinkTime--;
2207 
2208 	if (self->thinkTime <= 0)
2209 	{
2210 		self->head->action = &becomeMiniGargoyleInit;
2211 
2212 		self->action = &lanceAttack2Wait;
2213 	}
2214 
2215 	checkToMap(self);
2216 }
2217 
lanceAttack2Wait()2218 static void lanceAttack2Wait()
2219 {
2220 	checkToMap(self);
2221 }
2222 
lanceAttack3()2223 static void lanceAttack3()
2224 {
2225 	self->thinkTime--;
2226 
2227 	if (self->thinkTime <= 0)
2228 	{
2229 		self->maxThinkTime = 0;
2230 
2231 		self->action = &fakeLanceDropInit;
2232 	}
2233 
2234 	checkToMap(self);
2235 }
2236 
fakeLanceDropInit()2237 static void fakeLanceDropInit()
2238 {
2239 	Target *t = getTargetByName("GARGOYLE_TOP_TARGET");
2240 
2241 	if (t == NULL)
2242 	{
2243 		showErrorAndExit("Lance cannot find target");
2244 	}
2245 
2246 	self->flags |= (FLY|NO_DRAW);
2247 
2248 	setEntityAnimation(self, "LANCE_THROW");
2249 
2250 	addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
2251 
2252 	playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
2253 
2254 	self->y = t->y;
2255 
2256 	self->thinkTime = 30;
2257 
2258 	self->action = &fakeLanceDropAppear;
2259 }
2260 
fakeLanceDropAppear()2261 static void fakeLanceDropAppear()
2262 {
2263 	int i, real, x, lanceCount;
2264 	Entity *e;
2265 
2266 	self->thinkTime--;
2267 
2268 	if (self->thinkTime <= 0)
2269 	{
2270 		lanceCount = 5;
2271 
2272 		real = prand() % lanceCount;
2273 
2274 		x = getMapStartX() + SCREEN_WIDTH / (lanceCount + 1);
2275 
2276 		for (i=0;i<lanceCount;i++)
2277 		{
2278 			if (i == real)
2279 			{
2280 				e = self;
2281 			}
2282 
2283 			else
2284 			{
2285 				e = getFreeEntity();
2286 
2287 				if (e == NULL)
2288 				{
2289 					showErrorAndExit("No free slots to add a fake lance");
2290 				}
2291 
2292 				loadProperties("boss/gargoyle_fake_lance", e);
2293 
2294 				e->draw = &drawLoopingAnimationToMap;
2295 				e->touch = &entityTouch;
2296 				e->takeDamage = &entityTakeDamageNoFlinch;
2297 				e->die = &fakeLanceDie;
2298 				e->pain = &enemyPain;
2299 
2300 				e->type = ENEMY;
2301 
2302 				setEntityAnimation(e, "LANCE_THROW");
2303 
2304 				e->mental = 0;
2305 
2306 				e->head = self;
2307 			}
2308 
2309 			e->action = &fakeLanceDropWait;
2310 
2311 			e->flags |= (FLY|NO_DRAW);
2312 
2313 			e->x = x;
2314 			e->y = self->y;
2315 
2316 			e->thinkTime = 30 * i;
2317 
2318 			e->maxThinkTime = 30 * (lanceCount - i);
2319 
2320 			x += SCREEN_WIDTH / (lanceCount + 1);
2321 		}
2322 
2323 		self->endX = lanceCount - 1;
2324 	}
2325 }
2326 
fakeLanceDropWait()2327 static void fakeLanceDropWait()
2328 {
2329 	self->thinkTime--;
2330 
2331 	if (self->thinkTime <= 0)
2332 	{
2333 		self->flags &= ~NO_DRAW;
2334 
2335 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
2336 
2337 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
2338 
2339 		self->action = &fakeLanceDrop;
2340 
2341 		self->thinkTime = self->maxThinkTime;
2342 	}
2343 
2344 	checkToMap(self);
2345 }
2346 
fakeLanceDrop()2347 static void fakeLanceDrop()
2348 {
2349 	self->thinkTime--;
2350 
2351 	if (self->thinkTime <= 0)
2352 	{
2353 		self->dirY = 14;
2354 	}
2355 
2356 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
2357 	{
2358 		setEntityAnimation(self, "LANCE_IN_GROUND");
2359 
2360 		if (self->mental == -1)
2361 		{
2362 			self->thinkTime = 600;
2363 
2364 			playSoundToMap("sound/enemy/ground_spear/spear", -1, self->x, self->y, 0);
2365 		}
2366 
2367 		else
2368 		{
2369 			self->thinkTime = 180 + prand() % 120;
2370 		}
2371 
2372 		self->action = &fakeLanceDropExplodeWait;
2373 	}
2374 
2375 	checkToMap(self);
2376 }
2377 
fakeLanceDropExplodeWait()2378 static void fakeLanceDropExplodeWait()
2379 {
2380 	if (self->mental != -1)
2381 	{
2382 		self->thinkTime--;
2383 
2384 		if (self->thinkTime < 120)
2385 		{
2386 			if (self->thinkTime % 3 == 0)
2387 			{
2388 				self->flags ^= FLASH;
2389 			}
2390 		}
2391 
2392 		if (self->thinkTime <= 0)
2393 		{
2394 			self->action = &lanceExplode;
2395 		}
2396 	}
2397 
2398 	else if (self->endX <= 0)
2399 	{
2400 		self->action = &fakeLanceDropInit;
2401 	}
2402 
2403 	checkToMap(self);
2404 }
2405 
lanceExplode()2406 static void lanceExplode()
2407 {
2408 	int x, y;
2409 	Entity *e;
2410 
2411 	e = addProjectile("common/green_blob", self->head, 0, 0, -6, 0);
2412 
2413 	x = self->x + self->w / 2 - e->w / 2;
2414 	y = self->y + self->h / 2 - e->h / 2;
2415 
2416 	e->x = x;
2417 	e->y = y;
2418 
2419 	e->flags |= FLY;
2420 
2421 	e->reactToBlock = &bounceOffShield;
2422 
2423 	e = addProjectile("common/green_blob", self->head, x, y, 6, 0);
2424 
2425 	e->flags |= FLY;
2426 
2427 	e->reactToBlock = &bounceOffShield;
2428 
2429 	playSoundToMap("sound/common/explosion", -1, self->x, self->y, 0);
2430 
2431 	self->head->endX--;
2432 
2433 	self->inUse = FALSE;
2434 }
2435 
fakeLanceDie()2436 static void fakeLanceDie()
2437 {
2438 	self->head->endX--;
2439 
2440 	entityDieNoDrop();
2441 }
2442 
createLightningOrb()2443 static void createLightningOrb()
2444 {
2445 	Entity *e;
2446 	Target *t;
2447 
2448 	self->thinkTime--;
2449 
2450 	if (self->thinkTime <= 0)
2451 	{
2452 		e = getFreeEntity();
2453 
2454 		if (e == NULL)
2455 		{
2456 			showErrorAndExit("No free slots to add a lightning orb");
2457 		}
2458 
2459 		t = getTargetByName("GARGOYLE_TOP_TARGET");
2460 
2461 		if (t == NULL)
2462 		{
2463 			showErrorAndExit("Gargoyle cannot find target");
2464 		}
2465 
2466 		loadProperties("boss/gargoyle_lightning_orb", e);
2467 
2468 		e->x = self->x;
2469 		e->y = self->y;
2470 
2471 		e->startX = getMapStartX();
2472 		e->endX   = getMapStartX() + SCREEN_WIDTH - e->w;
2473 
2474 		e->targetY = t->y;
2475 
2476 		e->endY = self->y + self->h;
2477 
2478 		e->action = &orbMoveToTop;
2479 
2480 		e->draw = &drawLoopingAnimationToMap;
2481 		e->touch = NULL;
2482 		e->takeDamage = NULL;
2483 
2484 		e->type = ENEMY;
2485 
2486 		e->head = self;
2487 
2488 		e->mental = self->endX;
2489 
2490 		setEntityAnimation(e, "STAND");
2491 
2492 		self->mental--;
2493 
2494 		self->endX++;
2495 
2496 		if (self->mental <= 0)
2497 		{
2498 			self->action = &lanceAttack1Wait;
2499 		}
2500 
2501 		else
2502 		{
2503 			self->thinkTime = 15;
2504 		}
2505 	}
2506 
2507 	checkToMap(self);
2508 }
2509 
lanceAttack1Wait()2510 static void lanceAttack1Wait()
2511 {
2512 	checkToMap(self);
2513 }
2514 
orbMoveToTop()2515 static void orbMoveToTop()
2516 {
2517 	self->y -= self->speed * 2;
2518 
2519 	if (self->y <= self->targetY)
2520 	{
2521 		self->startY = self->targetY;
2522 
2523 		self->action = &orbFollowPlayer;
2524 	}
2525 }
2526 
orbFollowPlayer()2527 static void orbFollowPlayer()
2528 {
2529 	float target = player.x - self->w / 2 + player.w / 2;
2530 
2531 	/* Move above the player */
2532 
2533 	if (fabs(target - self->x) <= fabs(self->dirX))
2534 	{
2535 		self->thinkTime = 30;
2536 
2537 		self->dirX = 0;
2538 
2539 		self->action = &orbCastLightning2;
2540 	}
2541 
2542 	else
2543 	{
2544 		self->dirX = self->speed * 1.5;
2545 
2546 		self->x += target > self->x ? self->dirX : -self->dirX;
2547 
2548 		if (self->x < self->startX)
2549 		{
2550 			self->x = self->startX;
2551 
2552 			self->thinkTime = 30;
2553 
2554 			self->dirX = 0;
2555 
2556 			self->action = &orbCastLightning2;
2557 		}
2558 
2559 		else if (self->x > self->endX)
2560 		{
2561 			self->x = self->endX;
2562 
2563 			self->thinkTime = 30;
2564 
2565 			self->dirX = 0;
2566 
2567 			self->action = &orbCastLightning2;
2568 		}
2569 	}
2570 
2571 	if (self->head->inUse == FALSE)
2572 	{
2573 		self->inUse = FALSE;
2574 	}
2575 }
2576 
orbCastLightning2()2577 static void orbCastLightning2()
2578 {
2579 	int i;
2580 	Entity *e;
2581 
2582 	self->thinkTime--;
2583 
2584 	if (self->thinkTime <= 0 && player.health > 0)
2585 	{
2586 		playSoundToMap("sound/enemy/thunder_cloud/lightning", -1, self->x, self->y, 0);
2587 
2588 		for (i=self->endY-32;i>=self->startY;i-=32)
2589 		{
2590 			e = getFreeEntity();
2591 
2592 			if (e == NULL)
2593 			{
2594 				showErrorAndExit("No free slots to add lightning");
2595 			}
2596 
2597 			loadProperties("enemy/lightning", e);
2598 
2599 			setEntityAnimation(e, "STAND");
2600 
2601 			e->x = self->x + self->w / 2 - e->w / 2;
2602 			e->y = i;
2603 
2604 			e->action = &lightningWait;
2605 
2606 			e->draw = &drawLoopingAnimationToMap;
2607 			e->touch = &entityTouch;
2608 
2609 			e->head = self;
2610 
2611 			e->currentFrame = prand() % 6;
2612 
2613 			e->face = RIGHT;
2614 
2615 			e->thinkTime = 15;
2616 		}
2617 
2618 		e = addSmallRock(self->x, self->endY, "common/small_rock");
2619 
2620 		e->x += (self->w - e->w) / 2;
2621 		e->y -= e->h;
2622 
2623 		e->dirX = -3;
2624 		e->dirY = -8;
2625 
2626 		e = addSmallRock(self->x, self->endY, "common/small_rock");
2627 
2628 		e->x += (self->w - e->w) / 2;
2629 		e->y -= e->h;
2630 
2631 		e->dirX = 3;
2632 		e->dirY = -8;
2633 
2634 		self->action = &orbCastLightningFinish2;
2635 
2636 		self->thinkTime = 30 * self->mental;
2637 	}
2638 }
2639 
orbCastLightningFinish2()2640 static void orbCastLightningFinish2()
2641 {
2642 	self->thinkTime--;
2643 
2644 	if (self->thinkTime <= 0)
2645 	{
2646 		self->action = &orbFollowPlayer;
2647 
2648 		self->thinkTime = 30;
2649 	}
2650 }
2651 
lightningWait()2652 void lightningWait()
2653 {
2654 	self->thinkTime--;
2655 
2656 	if (self->thinkTime <= 0)
2657 	{
2658 		self->inUse = FALSE;
2659 	}
2660 }
2661 
becomeMiniGargoyleInit()2662 static void becomeMiniGargoyleInit()
2663 {
2664 	int i;
2665 	Entity *e;
2666 
2667 	fadeFromColour(255, 255, 255, 30);
2668 
2669 	self->flags |= NO_DRAW;
2670 
2671 	self->touch = NULL;
2672 
2673 	self->mental = 0;
2674 
2675 	self->endX = 0;
2676 
2677 	for (i=0;i<18;i++)
2678 	{
2679 		e = addEnemy("boss/mini_gargoyle", 0, 0);
2680 
2681 		setEntityAnimationByID(e, i);
2682 
2683 		e->x = self->x + e->offsetX;
2684 		e->y = self->y + e->offsetY;
2685 
2686 		e->endX = e->x;
2687 		e->endY = e->y;
2688 
2689 		e->flags |= LIMIT_TO_SCREEN;
2690 
2691 		setEntityAnimation(e, "STAND");
2692 
2693 		e->targetY = getMapStartY() - player.h - TILE_SIZE;
2694 
2695 		e->head = self;
2696 
2697 		e->thinkTime = 60 + prand() % 120;
2698 
2699 		self->mental++;
2700 
2701 		self->endX++;
2702 	}
2703 
2704 	self->action = &becomeMiniGargoyleWait;
2705 
2706 	checkToMap(self);
2707 }
2708 
becomeMiniGargoyleWait()2709 static void becomeMiniGargoyleWait()
2710 {
2711 	if (self->target->inUse == FALSE)
2712 	{
2713 		self->action = &becomeMiniGargoyleFinish;
2714 	}
2715 
2716 	checkToMap(self);
2717 }
2718 
becomeMiniGargoyleFinish()2719 static void becomeMiniGargoyleFinish()
2720 {
2721 	if (self->endX <= 0)
2722 	{
2723 		self->thinkTime--;
2724 
2725 		if (self->thinkTime <= 0)
2726 		{
2727 			if (self->flags & NO_DRAW)
2728 			{
2729 				fadeFromColour(255, 255, 255, 30);
2730 
2731 				self->flags &= ~NO_DRAW;
2732 
2733 				self->thinkTime = 60;
2734 			}
2735 
2736 			else
2737 			{
2738 				self->touch = &entityTouch;
2739 
2740 				self->action = &attackFinished;
2741 			}
2742 		}
2743 	}
2744 
2745 	checkToMap(self);
2746 }
2747 
lanceAttackTeleportFinish()2748 static void lanceAttackTeleportFinish()
2749 {
2750 	self->thinkTime--;
2751 
2752 	if (self->thinkTime <= 0)
2753 	{
2754 		self->flags &= ~(NO_DRAW|ATTACKING);
2755 
2756 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
2757 
2758 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
2759 
2760 		self->touch = NULL;
2761 
2762 		self->action = &lanceWait;
2763 
2764 		self->mental = 0;
2765 	}
2766 }
2767 
lanceThrowTeleportFinish()2768 static void lanceThrowTeleportFinish()
2769 {
2770 	self->thinkTime--;
2771 
2772 	if (self->thinkTime <= 0)
2773 	{
2774 		self->flags &= ~(NO_DRAW|ATTACKING);
2775 
2776 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
2777 
2778 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
2779 
2780 		self->touch = NULL;
2781 
2782 		self->action = &lanceWait;
2783 
2784 		self->head->action = &lanceThrowInit;
2785 
2786 		self->mental = 0;
2787 	}
2788 }
2789 
addStoneCoat()2790 static void addStoneCoat()
2791 {
2792 	Entity *e = getFreeEntity();
2793 
2794 	if (e == NULL)
2795 	{
2796 		showErrorAndExit("No free slots to add the Gargoyle Stone Coat");
2797 	}
2798 
2799 	loadProperties("boss/gargoyle_stone_coat", e);
2800 
2801 	e->x = self->x;
2802 	e->y = self->y;
2803 
2804 	e->action = &coatWait;
2805 
2806 	e->draw = &drawLoopingAnimationToMap;
2807 	e->touch = &entityTouch;
2808 	e->takeDamage = NULL;
2809 
2810 	e->type = ENEMY;
2811 
2812 	e->head = self;
2813 
2814 	self->target = e;
2815 
2816 	setEntityAnimation(e, getAnimationTypeAtIndex(self));
2817 }
2818 
coatWait()2819 static void coatWait()
2820 {
2821 	self->face = self->head->face;
2822 
2823 	setEntityAnimation(self, getAnimationTypeAtIndex(self->head));
2824 
2825 	if (self->head->mental == 1)
2826 	{
2827 		self->alpha--;
2828 
2829 		if (self->alpha <= 0)
2830 		{
2831 			self->head->mental = 2;
2832 
2833 			self->inUse = FALSE;
2834 		}
2835 	}
2836 
2837 	else if (self->head->mental == 2)
2838 	{
2839 		self->alpha++;
2840 
2841 		if (self->alpha >= 255)
2842 		{
2843 			self->head->mental = 0;
2844 
2845 			self->alpha = 255;
2846 		}
2847 	}
2848 
2849 	else if (self->head->mental == -1)
2850 	{
2851 		self->inUse = FALSE;
2852 	}
2853 
2854 	self->x = self->head->x;
2855 	self->y = self->head->y;
2856 
2857 	if (self->head->inUse == FALSE)
2858 	{
2859 		self->inUse = FALSE;
2860 	}
2861 }
2862 
die()2863 static void die()
2864 {
2865 	int i;
2866 	long onGround;
2867 	Entity *e;
2868 
2869 	self->action = &die;
2870 
2871 	self->damage = 0;
2872 
2873 	self->takeDamage = NULL;
2874 
2875 	setEntityAnimation(self, "FACE_FRONT_CROUCH");
2876 
2877 	self->dirX = 0;
2878 
2879 	self->flags &= ~(FLY|LIMIT_TO_SCREEN);
2880 
2881 	onGround = self->flags & ON_GROUND;
2882 
2883 	checkToMap(self);
2884 
2885 	if (landedOnGround(onGround) == TRUE)
2886 	{
2887 		playSoundToMap("sound/enemy/red_grub/thud", -1, self->x, self->y, 0);
2888 
2889 		shakeScreen(MEDIUM, 30);
2890 
2891 		for (i=0;i<30;i++)
2892 		{
2893 			e = addSmoke(self->x + (prand() % self->w), self->y + self->h, "decoration/dust");
2894 
2895 			if (e != NULL)
2896 			{
2897 				e->y -= prand() % e->h;
2898 			}
2899 		}
2900 	}
2901 
2902 	if (self->standingOn != NULL || (self->flags & ON_GROUND))
2903 	{
2904 		self->maxThinkTime = 0;
2905 
2906 		self->health = 0;
2907 
2908 		self->thinkTime = 180;
2909 
2910 		self->action = &dieWait;
2911 
2912 		self->startX = self->x;
2913 	}
2914 }
2915 
dieWait()2916 static void dieWait()
2917 {
2918 	self->thinkTime--;
2919 
2920 	if (self->thinkTime <= 0)
2921 	{
2922 		switch (self->maxThinkTime)
2923 		{
2924 			case 0:
2925 				setEntityAnimation(self, "FACE_FRONT");
2926 
2927 				self->thinkTime = 60;
2928 
2929 				self->maxThinkTime = 1;
2930 			break;
2931 
2932 			case 1:
2933 				facePlayer();
2934 
2935 				setEntityAnimation(self, "FACE_PLAYER");
2936 
2937 				self->thinkTime = 60;
2938 
2939 				self->maxThinkTime = 2;
2940 			break;
2941 
2942 			case 2:
2943 				setEntityAnimation(self, "STAND");
2944 
2945 				self->thinkTime = 60;
2946 
2947 				self->maxThinkTime = 3;
2948 			break;
2949 
2950 			case 3:
2951 				setEntityAnimation(self, "REACH");
2952 
2953 				self->thinkTime = 30;
2954 
2955 				self->maxThinkTime = 4;
2956 			break;
2957 
2958 			case 4:
2959 				addStoneCoat();
2960 
2961 				self->target->alpha = 0;
2962 
2963 				self->mental = 2;
2964 
2965 				self->maxThinkTime = 5;
2966 
2967 				playSoundToMap("sound/boss/gargoyle/gargoyle_stone_to_flesh", BOSS_CHANNEL, self->x, self->y, -1);
2968 			break;
2969 
2970 			case 5:
2971 				self->x = self->startX + 1 * (prand() % 2 == 0 ? 1 : -1);
2972 
2973 				if (self->mental == 0)
2974 				{
2975 					stopSound(BOSS_CHANNEL);
2976 
2977 					self->x = self->startX;
2978 
2979 					self->mental = -1;
2980 
2981 					setEntityAnimation(self, "REACH_STONE");
2982 
2983 					self->thinkTime = 30;
2984 
2985 					self->maxThinkTime = 6;
2986 				}
2987 			break;
2988 
2989 			default:
2990 				self->takeDamage = &stoneTakeDamage;
2991 
2992 				self->touch = &stoneTouch;
2993 
2994 				self->activate = &activate;
2995 
2996 				self->health = 8;
2997 
2998 				self->action = &dieFinish;
2999 
3000 				clearContinuePoint();
3001 
3002 				increaseKillCount();
3003 
3004 				freeBossHealthBar();
3005 
3006 				fadeBossMusic();
3007 
3008 				fireTrigger(self->objectiveName);
3009 
3010 				fireGlobalTrigger(self->objectiveName);
3011 			break;
3012 		}
3013 	}
3014 }
3015 
stoneTouch(Entity * other)3016 static void stoneTouch(Entity *other)
3017 {
3018 	if (other->type == PLAYER)
3019 	{
3020 		setInfoBoxMessage(0, 255, 255, 255, _("Press Action to interact"));
3021 	}
3022 
3023 	else
3024 	{
3025 		entityTouch(other);
3026 	}
3027 }
3028 
activate(int val)3029 static void activate(int val)
3030 {
3031 	runScript(self->requires);
3032 }
3033 
dieFinish()3034 static void dieFinish()
3035 {
3036 	checkToMap(self);
3037 }
3038 
stoneTakeDamage(Entity * other,int damage)3039 static void stoneTakeDamage(Entity *other, int damage)
3040 {
3041 	Entity *temp;
3042 
3043 	if (strcmpignorecase("weapon/pickaxe", other->name) == 0)
3044 	{
3045 		self->health -= damage;
3046 
3047 		setCustomAction(self, &flashWhite, 6, 0, 0);
3048 		setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
3049 
3050 		if (self->health <= 0)
3051 		{
3052 			self->action = &stoneDie;
3053 		}
3054 	}
3055 
3056 	else
3057 	{
3058 		setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
3059 
3060 		playSoundToMap("sound/common/dink", -1, self->x, self->y, 0);
3061 
3062 		if (other->reactToBlock != NULL)
3063 		{
3064 			temp = self;
3065 
3066 			self = other;
3067 
3068 			self->reactToBlock(temp);
3069 
3070 			self = temp;
3071 		}
3072 
3073 		if (other->type != PROJECTILE && prand() % 10 == 0)
3074 		{
3075 			setInfoBoxMessage(60, 255, 255, 255, _("This weapon is not having any effect..."));
3076 		}
3077 
3078 		damage = 0;
3079 	}
3080 }
3081 
stoneDie()3082 static void stoneDie()
3083 {
3084 	int i;
3085 
3086 	Entity *e = addKeyItem("item/heart_container", self->x + self->w / 2, self->y);
3087 
3088 	e->dirY = ITEM_JUMP_HEIGHT;
3089 
3090 	for (i=0;i<7;i++)
3091 	{
3092 		e = addTemporaryItem("boss/gargoyle_piece", self->x, self->y, RIGHT, 0, 0);
3093 
3094 		e->x += self->w / 2 - e->w / 2;
3095 		e->y += self->h / 2 - e->h / 2;
3096 
3097 		e->dirX = (prand() % 10) * (prand() % 2 == 0 ? -1 : 1);
3098 		e->dirY = ITEM_JUMP_HEIGHT + (prand() % ITEM_JUMP_HEIGHT);
3099 
3100 		setEntityAnimationByID(e, i);
3101 
3102 		e->thinkTime = 60 + (prand() % 60);
3103 	}
3104 
3105 	playSoundToMap("sound/common/crumble", BOSS_CHANNEL, self->x, self->y, 0);
3106 
3107 	self->inUse = FALSE;
3108 }
3109 
addExitTrigger(Entity * e)3110 static void addExitTrigger(Entity *e)
3111 {
3112 	char itemName[MAX_LINE_LENGTH];
3113 
3114 	SNPRINTF(itemName, MAX_LINE_LENGTH, "\"%s\" 1 UPDATE_EXIT \"GARGOYLE_BOSS\"", e->objectiveName);
3115 
3116 	addGlobalTriggerFromScript(itemName);
3117 }
3118 
cloneCheck()3119 static void cloneCheck()
3120 {
3121 	if (self->head != NULL && self->head->health <= 0)
3122 	{
3123 		self->action = &cloneDie;
3124 	}
3125 }
3126 
cloneDie()3127 static void cloneDie()
3128 {
3129 	self->action = &cloneDie;
3130 
3131 	self->frameSpeed = 0;
3132 
3133 	self->damage = 0;
3134 
3135 	self->dirX = 0;
3136 	self->dirY = 0;
3137 
3138 	self->takeDamage = NULL;
3139 
3140 	self->alpha -= 2;
3141 
3142 	if (self->alpha <= 0)
3143 	{
3144 		self->inUse = FALSE;
3145 	}
3146 }
3147 
creditsMove()3148 static void creditsMove()
3149 {
3150 	if (self->mental == 0)
3151 	{
3152 		addLance();
3153 
3154 		self->mental = 1;
3155 	}
3156 
3157 	setEntityAnimation(self, "STAND");
3158 
3159 	self->creditsAction = &bossMoveToMiddle;
3160 }
3161 
addLance()3162 static void addLance()
3163 {
3164 	Entity *e;
3165 
3166 	e = getFreeEntity();
3167 
3168 	if (e == NULL)
3169 	{
3170 		showErrorAndExit("No free slots to add the Gargoyle's Lance");
3171 	}
3172 
3173 	loadProperties("boss/gargoyle_lance_1", e);
3174 
3175 	e->creditsAction = &lanceCreditsMove;
3176 
3177 	e->draw = &drawLoopingAnimationToMap;
3178 
3179 	e->type = ENEMY;
3180 
3181 	e->head = self;
3182 
3183 	setEntityAnimation(e, "STAND");
3184 }
3185 
lanceCreditsMove()3186 static void lanceCreditsMove()
3187 {
3188 	self->face = self->head->face;
3189 
3190 	setEntityAnimation(self, getAnimationTypeAtIndex(self->head));
3191 
3192 	if (self->face == LEFT)
3193 	{
3194 		self->x = self->head->x + self->head->w - self->w - self->offsetX;
3195 	}
3196 
3197 	else
3198 	{
3199 		self->x = self->head->x + self->offsetX;
3200 	}
3201 
3202 	self->y = self->head->y + self->offsetY;
3203 
3204 	if (self->head->inUse == FALSE)
3205 	{
3206 		self->inUse = FALSE;
3207 	}
3208 }
3209 
fallout()3210 static void fallout()
3211 {
3212 	Target *t = getTargetByName("GARGOYLE_TOP_TARGET");
3213 
3214 	if (t == NULL)
3215 	{
3216 		showErrorAndExit("Gargoyle cannot find target");
3217 	}
3218 
3219 	setEntityAnimation(self, "FLY");
3220 
3221 	self->x = t->x;
3222 	self->y = t->y;
3223 
3224 	self->dirX = 0;
3225 	self->dirY = 0;
3226 
3227 	self->flags |= FLY|NO_DRAW;
3228 
3229 	self->thinkTime = 60;
3230 
3231 	self->action = &falloutWait;
3232 }
3233 
falloutWait()3234 static void falloutWait()
3235 {
3236 	self->thinkTime--;
3237 
3238 	if (self->thinkTime <= 0)
3239 	{
3240 		self->flags &= ~NO_DRAW;
3241 
3242 		self->thinkTime = 30;
3243 
3244 		self->action = &entityWait;
3245 
3246 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
3247 
3248 		playSoundToMap("sound/common/teleport", BOSS_CHANNEL, self->x, self->y, 0);
3249 	}
3250 }
3251