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 "../boss/sorceror.h"
25 #include "../collisions.h"
26 #include "../credits.h"
27 #include "../custom_actions.h"
28 #include "../dialog.h"
29 #include "../enemy/enemies.h"
30 #include "../enemy/rock.h"
31 #include "../entity.h"
32 #include "../game.h"
33 #include "../geometry.h"
34 #include "../graphics/animation.h"
35 #include "../graphics/decoration.h"
36 #include "../graphics/graphics.h"
37 #include "../hud.h"
38 #include "../init.h"
39 #include "../inventory.h"
40 #include "../item/key_items.h"
41 #include "../map.h"
42 #include "../player.h"
43 #include "../projectile.h"
44 #include "../system/error.h"
45 #include "../system/properties.h"
46 #include "../system/random.h"
47 #include "../weather.h"
48 #include "../world/target.h"
49 
50 extern Entity *self, player;
51 
52 static void initialise(void);
53 static void appear(void);
54 static void createLightBeam(void);
55 static void introRaise(void);
56 static void doIntro(void);
57 static void attackFinished(void);
58 static void entityWait(void);
59 static void raiseDeadInit(void);
60 static void raiseDeadMoveToTopTarget(void);
61 static void raiseDead(void);
62 static void raiseDeadFinish(void);
63 static void phantasmalBoltInit(void);
64 static void phantasmalBoltMoveToTarget(void);
65 static void phantasmalBolt(void);
66 static void phantasmalBoltWait(void);
67 static void phantasmalBoltFinish(void);
68 static void phantasmalBoltMove(void);
69 static void phantasmalBoltReflect(Entity *);
70 static void scytheThrowInit(void);
71 static void scytheThrowMoveToTarget(void);
72 static void scytheThrowReady(void);
73 static void scytheThrow(void);
74 static void scytheThrowWait(void);
75 static void scytheThrowTeleportAway(void);
76 static void scytheThrowFinish(void);
77 static void scytheMove(void);
78 static void soulStealInit(void);
79 static void soulStealMoveToPlayer(void);
80 static void soulSteal(void);
81 static void soulStealFinish(void);
82 static void soulWait(void);
83 static void spikeAttackInit(void);
84 static void spikeAttackMoveToTopTarget(void);
85 static void spikeAttackWait(void);
86 static void spikeRise(void);
87 static void spikeWait(void);
88 static void spikeSink(void);
89 static void beamWait(void);
90 static void redBeamWait(void);
91 static void beamAppearFinish(void);
92 static void beamDisappearFinish(void);
93 static void beamFinish(void);
94 static void redBeamFinish(void);
95 static int drawBeam(void);
96 static void soulLeave(void);
97 static void becomeTransparent(void);
98 static void takeDamage(Entity *, int);
99 static void die(void);
100 static void dieShudder(void);
101 static void dieMoveToTop(void);
102 static void dieWait(void);
103 static void lightningCageInit(void);
104 static void lightningCageCreate(void);
105 static void lightningCageWait(void);
106 static void lightningCageMoveAbovePlayer(void);
107 static void lightningCage(void);
108 static void lightningCageMoveBackToPlayer(void);
109 static void lightningCageFinish(void);
110 static void lightningCageTeleportAway(void);
111 static void cageLightningWait(void);
112 static void creditsMove(void);
113 static void addScythe(void);
114 static void scytheWait(void);
115 static void scytheCreditsMove(void);
116 static void soulStealSpellAttack(void);
117 int drawSoulStealSpell(void);
118 
addAzriel(int x,int y,char * name)119 Entity *addAzriel(int x, int y, char *name)
120 {
121 	Entity *e = getFreeEntity();
122 
123 	if (e == NULL)
124 	{
125 		showErrorAndExit("No free slots to add Azriel");
126 	}
127 
128 	loadProperties(name, e);
129 
130 	e->x = x;
131 	e->y = y;
132 
133 	e->action = &initialise;
134 
135 	e->draw = &drawLoopingAnimationToMap;
136 	e->touch = NULL;
137 	e->die = ¨
138 	e->takeDamage = NULL;
139 
140 	e->creditsAction = &creditsMove;
141 
142 	e->type = ENEMY;
143 
144 	setEntityAnimation(e, "INTRO");
145 
146 	return e;
147 }
148 
initialise()149 static void initialise()
150 {
151 	if (self->active == TRUE)
152 	{
153 		if (self->mental == 0)
154 		{
155 			if (strcmpignorecase(getWeather(), "HEAVY_RAIN") != 0)
156 			{
157 				self->flags &= ~NO_DRAW;
158 
159 				setWeather(HEAVY_RAIN);
160 
161 				playDefaultBossMusic();
162 			}
163 
164 			centerMapOnEntity(NULL);
165 
166 			self->action = &doIntro;
167 
168 			self->thinkTime = 60;
169 
170 			self->endX = 0;
171 
172 			self->touch = &entityTouch;
173 
174 			setContinuePoint(FALSE, self->name, NULL);
175 		}
176 
177 		else
178 		{
179 			self->action = &appear;
180 		}
181 	}
182 
183 	checkToMap(self);
184 }
185 
appear()186 static void appear()
187 {
188 	Entity *e = getEntityByObjectiveName("AZRIEL_GRAVE");
189 
190 	if (e == NULL)
191 	{
192 		showErrorAndExit("Azirel cannot find AZRIEL_GRAVE");
193 	}
194 
195 	self->layer = BACKGROUND_LAYER;
196 
197 	self->y = e->y + e->h;
198 
199 	self->active = FALSE;
200 
201 	self->action = &createLightBeam;
202 
203 	self->flags &= ~NO_DRAW;
204 
205 	e->mental = 1;
206 
207 	self->thinkTime = 120;
208 }
209 
createLightBeam()210 static void createLightBeam()
211 {
212 	Entity *e;
213 
214 	self->thinkTime--;
215 
216 	if (self->thinkTime <= 0)
217 	{
218 		e = getFreeEntity();
219 
220 		loadProperties("boss/azriel_light_beam", e);
221 
222 		setEntityAnimation(e, "APPEAR");
223 
224 		e->animationCallback = &beamAppearFinish;
225 
226 		self->target = e;
227 
228 		e->head = self;
229 
230 		e->x = self->x + self->w / 2 - e->w / 2;
231 
232 		e->y = getMapFloor(self->x + self->w / 2, self->y) - e->h;
233 
234 		e->startY = e->y;
235 
236 		e->action = &beamWait;
237 		e->draw = &drawBeam;
238 		e->touch = &entityTouch;
239 
240 		e->face = RIGHT;
241 
242 		e->type = ENEMY;
243 
244 		e->thinkTime = 3600;
245 
246 		e->mental = 0;
247 
248 		self->action = &introRaise;
249 
250 		self->thinkTime = 120;
251 
252 		self->mental = 1;
253 
254 		playDefaultBossMusic();
255 	}
256 }
257 
introRaise()258 static void introRaise()
259 {
260 	self->thinkTime--;
261 
262 	if (self->thinkTime <= 0)
263 	{
264 		self->y--;
265 
266 		if (self->y <= self->startY)
267 		{
268 			self->target->action = &beamFinish;
269 
270 			self->y = self->startY;
271 
272 			self->action = &initialise;
273 
274 			self->active = FALSE;
275 
276 			self->mental = 0;
277 		}
278 	}
279 }
280 
doIntro()281 static void doIntro()
282 {
283 	Entity *e;
284 	Target *t;
285 
286 	e = getFreeEntity();
287 
288 	if (e == NULL)
289 	{
290 		showErrorAndExit("No free slots to add Edgar's Soul");
291 	}
292 
293 	t = getTargetByName("EDGAR_SOUL_TARGET");
294 
295 	if (t == NULL)
296 	{
297 		showErrorAndExit("Azirel cannot find target");
298 	}
299 
300 	loadProperties("boss/edgar_soul", e);
301 
302 	e->x = t->x;
303 	e->y = t->y;
304 
305 	e->startY = e->y;
306 
307 	e->alpha = 0;
308 
309 	e->action = &soulWait;
310 
311 	e->draw = &drawLoopingAnimationToMap;
312 
313 	e->type = ENEMY;
314 
315 	e->thinkTime = e->maxThinkTime;
316 
317 	self->head = e;
318 
319 	e->target = self;
320 
321 	e->mental = 0;
322 
323 	setEntityAnimation(e, "STAND");
324 
325 	self->flags |= LIMIT_TO_SCREEN;
326 
327 	initBossHealthBar();
328 
329 	self->takeDamage = &takeDamage;
330 
331 	self->action = &attackFinished;
332 
333 	checkToMap(self);
334 
335 	becomeTransparent();
336 
337 	setEntityAnimation(self, "STAND");
338 
339 	addScythe();
340 }
341 
entityWait()342 static void entityWait()
343 {
344 	self->thinkTime--;
345 
346 	if (self->thinkTime <= 0 && player.health > 0)
347 	{
348 		if (self->head->thinkTime <= 0)
349 		{
350 			self->action = self->head->mental == 0 ? &phantasmalBoltInit : &soulStealInit;
351 		}
352 
353 		else
354 		{
355 			switch (prand() % 4)
356 			{
357 				case 0:
358 					self->action = &scytheThrowInit;
359 				break;
360 
361 				case 1:
362 					self->action = &raiseDeadInit;
363 				break;
364 
365 				case 2:
366 					self->action = &lightningCageInit;
367 				break;
368 
369 				default:
370 					self->action = &spikeAttackInit;
371 				break;
372 			}
373 		}
374 	}
375 
376 	checkToMap(self);
377 
378 	becomeTransparent();
379 }
380 
lightningCageInit()381 static void lightningCageInit()
382 {
383 	Target *t;
384 
385 	t = getTargetByName("AZRIEL_TOP_TARGET");
386 
387 	if (t == NULL)
388 	{
389 		showErrorAndExit("Azriel cannot find target");
390 	}
391 
392 	addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
393 
394 	self->x = t->x;
395 	self->y = t->y;
396 
397 	playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
398 
399 	self->flags |= NO_DRAW;
400 
401 	self->thinkTime = 30;
402 
403 	self->action = &lightningCageCreate;
404 
405 	self->mental = 0;
406 
407 	self->target->layer = BACKGROUND_LAYER;
408 
409 	becomeTransparent();
410 }
411 
lightningCageCreate()412 static void lightningCageCreate()
413 {
414 	int i;
415 	Entity *e;
416 
417 	self->thinkTime--;
418 
419 	if (self->thinkTime <= 0)
420 	{
421 		setEntityAnimation(self, "LIGHTNING_CAGE");
422 
423 		self->x = player.x + player.w / 2 - self->w / 2;
424 
425 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
426 
427 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
428 
429 		self->flags &= ~NO_DRAW;
430 
431 		self->face = prand() % 2 == 0 ? LEFT : RIGHT;
432 
433 		self->mental = 0;
434 
435 		self->thinkTime = 60;
436 
437 		self->maxThinkTime = 0;
438 
439 		for (i=0;i<2;i++)
440 		{
441 			e = getFreeEntity();
442 
443 			if (e == NULL)
444 			{
445 				showErrorAndExit("No free slots to add a lightning cage");
446 			}
447 
448 			loadProperties("boss/azriel_lightning_cage_spell", e);
449 
450 			e->action = &lightningCageWait;
451 
452 			e->draw = &drawLoopingAnimationToMap;
453 
454 			e->touch = &entityTouch;
455 
456 			e->head = self;
457 
458 			e->face = i == 0 ? LEFT : RIGHT;
459 
460 			setEntityAnimation(e, "STAND");
461 
462 			if (e->face == LEFT)
463 			{
464 				e->x = self->x + self->w - e->w - e->offsetX;
465 			}
466 
467 			else
468 			{
469 				e->x = self->x + e->offsetX;
470 			}
471 
472 			e->y = self->y + e->offsetY;
473 		}
474 
475 		self->action = &lightningCageMoveAbovePlayer;
476 	}
477 
478 	becomeTransparent();
479 }
480 
lightningCageMoveAbovePlayer()481 static void lightningCageMoveAbovePlayer()
482 {
483 	self->thinkTime--;
484 
485 	if (self->thinkTime <= 0)
486 	{
487 		if (self->maxThinkTime == 0)
488 		{
489 			self->thinkTime = 30;
490 
491 			self->maxThinkTime = 2;
492 
493 			self->mental = 1;
494 		}
495 
496 		else
497 		{
498 			self->action = &lightningCage;
499 
500 			self->targetX = self->face == LEFT ? getMapStartX() : getMapStartX() + SCREEN_WIDTH - self->w - 1;
501 
502 			self->dirX = self->targetX < self->x ? -player.speed / 2 : player.speed / 2;
503 
504 			self->thinkTime = 30;
505 		}
506 	}
507 
508 	checkToMap(self);
509 
510 	becomeTransparent();
511 }
512 
lightningCage()513 static void lightningCage()
514 {
515 	int playerMid, startX;
516 
517 	if (fabs(self->x - self->targetX) <= fabs(self->dirX))
518 	{
519 		self->x = self->targetX;
520 
521 		self->dirX = 0;
522 
523 		self->thinkTime--;
524 
525 		if (self->thinkTime <= 0)
526 		{
527 			self->maxThinkTime--;
528 
529 			if (self->maxThinkTime <= 0)
530 			{
531 				self->action = &lightningCageFinish;
532 			}
533 
534 			else
535 			{
536 				startX = getMapStartX();
537 
538 				self->targetX = self->x != startX ? startX : startX + SCREEN_WIDTH - self->w;
539 
540 				self->dirX = self->targetX < self->x ? -player.speed / 2 : player.speed / 2;
541 			}
542 
543 			self->thinkTime = 30;
544 		}
545 	}
546 
547 	else
548 	{
549 		self->x += self->dirX;
550 
551 		playerMid = player.x + player.w / 2;
552 
553 		if (player.health > 0 && !(playerMid >= self->x && playerMid <= self->x + self->w))
554 		{
555 			self->action = &lightningCageMoveBackToPlayer;
556 		}
557 	}
558 
559 	becomeTransparent();
560 }
561 
lightningCageMoveBackToPlayer()562 static void lightningCageMoveBackToPlayer()
563 {
564 	self->targetX = player.x - self->w / 2 + player.w / 2;
565 
566 	checkToMap(self);
567 
568 	/* Position above the player */
569 
570 	if (abs(self->x - self->targetX) <= player.speed / 2)
571 	{
572 		self->action = &lightningCage;
573 
574 		self->targetX = self->face == LEFT ? getMapStartX() : getMapStartX() + SCREEN_WIDTH - self->w - 1;
575 
576 		self->dirX = self->targetX < self->x ? -player.speed / 2 : player.speed / 2;
577 
578 		self->thinkTime = 30;
579 	}
580 
581 	else
582 	{
583 		self->dirX = self->targetX < self->x ? -player.speed / 2 : player.speed / 2;
584 	}
585 
586 	becomeTransparent();
587 }
588 
lightningCageFinish()589 static void lightningCageFinish()
590 {
591 	Target *t;
592 
593 	self->thinkTime--;
594 
595 	if (self->thinkTime <= 0)
596 	{
597 		self->mental = 2;
598 
599 		t = getTargetByName("AZRIEL_TOP_TARGET");
600 
601 		if (t == NULL)
602 		{
603 			showErrorAndExit("Azriel cannot find target");
604 		}
605 
606 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
607 
608 		self->x = t->x;
609 		self->y = t->y;
610 
611 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
612 
613 		self->flags |= NO_DRAW;
614 
615 		self->thinkTime = 30;
616 
617 		self->action = &lightningCageTeleportAway;
618 	}
619 
620 	becomeTransparent();
621 }
622 
lightningCageTeleportAway()623 static void lightningCageTeleportAway()
624 {
625 	self->thinkTime--;
626 
627 	if (self->thinkTime <= 0)
628 	{
629 		setEntityAnimation(self, "STAND");
630 
631 		self->flags &= ~NO_DRAW;
632 
633 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
634 
635 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
636 
637 		self->target->layer = MID_GROUND_LAYER;
638 
639 		self->action = &attackFinished;
640 	}
641 
642 	checkToMap(self);
643 
644 	becomeTransparent();
645 }
646 
lightningCageWait()647 static void lightningCageWait()
648 {
649 	int i, middle;
650 	Entity *e;
651 
652 	if (self->face == LEFT)
653 	{
654 		self->x = self->head->x + self->head->w - self->w - self->offsetX;
655 	}
656 
657 	else
658 	{
659 		self->x = self->head->x + self->offsetX;
660 	}
661 
662 	self->y = self->head->y + self->offsetY;
663 
664 	middle = 0;
665 
666 	e = NULL;
667 
668 	if (self->head->mental == 1)
669 	{
670 		if (self->mental == 0)
671 		{
672 			self->endY = getMapFloor(self->x + self->w / 2, self->y);
673 
674 			for (i=self->y;i<self->endY;i+=32)
675 			{
676 				e = getFreeEntity();
677 
678 				if (e == NULL)
679 				{
680 					showErrorAndExit("No free slots to add lightning");
681 				}
682 
683 				loadProperties("enemy/lightning", e);
684 
685 				setEntityAnimation(e, "STAND");
686 
687 				if (i == self->startY)
688 				{
689 					middle = self->targetX + self->w / 2 - e->w / 2;
690 				}
691 
692 				e->x = middle;
693 				e->y = i;
694 
695 				e->action = &cageLightningWait;
696 
697 				e->draw = &drawLoopingAnimationToMap;
698 				e->touch = &entityTouch;
699 
700 				e->head = self;
701 
702 				e->currentFrame = prand() % 6;
703 
704 				e->face = RIGHT;
705 
706 				e->thinkTime = 15;
707 			}
708 
709 			e->mental = 1;
710 
711 			self->mental = 1;
712 
713 			if (self->face == LEFT)
714 			{
715 				self->targetX = playSoundToMap("sound/boss/azriel/azriel_lightning_cage", -1, self->x, self->y, -1);
716 			}
717 		}
718 	}
719 
720 	else if (self->head->mental == 2)
721 	{
722 		if (self->face == LEFT)
723 		{
724 			stopSound(self->targetX);
725 		}
726 
727 		self->inUse = FALSE;
728 	}
729 }
730 
cageLightningWait()731 static void cageLightningWait()
732 {
733 	Entity *e;
734 
735 	self->x = self->head->x + self->head->w / 2 - self->w / 2;
736 
737 	if (self->head->inUse == FALSE)
738 	{
739 		self->inUse = FALSE;
740 	}
741 
742 	if (self->mental == 1)
743 	{
744 		self->thinkTime--;
745 
746 		if (self->thinkTime <= 0)
747 		{
748 			e = addSmallRock(self->x, self->endY, "common/small_rock");
749 
750 			e->x += (self->w - e->w) / 2;
751 			e->y = self->y;
752 
753 			e->dirX = -3;
754 			e->dirY = -8;
755 
756 			e = addSmallRock(self->x, self->endY, "common/small_rock");
757 
758 			e->x += (self->w - e->w) / 2;
759 			e->y = self->y;
760 
761 			e->dirX = 3;
762 			e->dirY = -8;
763 
764 			self->thinkTime = 15;
765 		}
766 	}
767 }
768 
soulStealInit()769 static void soulStealInit()
770 {
771 	self->flags |= NO_DRAW;
772 
773 	addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
774 
775 	playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
776 
777 	self->thinkTime = 30;
778 
779 	self->action = &soulStealMoveToPlayer;
780 
781 	checkToMap(self);
782 
783 	becomeTransparent();
784 }
785 
soulStealMoveToPlayer()786 static void soulStealMoveToPlayer()
787 {
788 	int mid;
789 	Entity *e;
790 	Target *t;
791 
792 	self->thinkTime--;
793 
794 	if (self->thinkTime <= 0)
795 	{
796 		setEntityAnimation(self, "SOUL_STEAL");
797 
798 		self->flags &= ~NO_DRAW;
799 
800 		t = getTargetByName("AZRIEL_LEFT_TARGET");
801 
802 		if (t == NULL)
803 		{
804 			showErrorAndExit("Azriel cannot find target");
805 		}
806 
807 		mid = getMapStartX() + SCREEN_WIDTH / 2;
808 
809 		self->x = player.x < mid ? player.x + player.w + 24 : player.x - self->w - 24;
810 
811 		self->y = t->y;
812 
813 		self->targetX = player.x;
814 
815 		setCustomAction(&player, &stickToFloor, 3, 0, 0);
816 
817 		facePlayer();
818 
819 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
820 
821 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
822 
823 		self->action = &soulSteal;
824 
825 		self->thinkTime = 600;
826 
827 		self->maxThinkTime = player.alpha;
828 
829 		self->targetY = player.alpha;
830 
831 		self->mental = -3;
832 
833 		e = getFreeEntity();
834 
835 		if (e == NULL)
836 		{
837 			showErrorAndExit("No free slots to add the Soul Steal Spell");
838 		}
839 
840 		loadProperties("boss/azriel_soul_steal_spell", e);
841 
842 		setEntityAnimation(e, "STAND");
843 
844 		e->face = self->face;
845 
846 		if (self->face == LEFT)
847 		{
848 			e->x = self->x + self->w - e->w - e->offsetX;
849 		}
850 
851 		else
852 		{
853 			e->x = self->x + e->offsetX;
854 		}
855 
856 		e->y = self->y + e->offsetY;
857 
858 		e->action = &soulStealSpellAttack;
859 
860 		e->startX = e->x;
861 		e->startY = e->y;
862 
863 		e->head = self;
864 
865 		e->endX = player.x + player.w / 2;
866 		e->endY = player.y + player.h / 2;
867 
868 		e->draw = &drawSoulStealSpell;
869 
870 		e->flags &= ~NO_DRAW;
871 	}
872 
873 	checkToMap(self);
874 
875 	becomeTransparent();
876 }
877 
soulStealSpellAttack()878 static void soulStealSpellAttack()
879 {
880 	self->endX = player.x + player.w / 2;
881 	self->endY = player.y + player.h / 2;
882 
883 	self->x = self->endX;
884 	self->y = self->endY;
885 
886 	if (self->head->flags & NO_DRAW)
887 	{
888 		self->inUse = FALSE;
889 	}
890 }
891 
drawSoulStealSpell()892 int drawSoulStealSpell()
893 {
894 	Colour colour1, colour2, colour3;
895 
896 	colour1.r = 38;
897 	colour1.g = 152;
898 	colour1.b = 38;
899 	colour1.a = 255;
900 
901 	colour2.r = 50;
902 	colour2.g = 200;
903 	colour2.b = 50;
904 	colour1.a = 255;
905 
906 	colour3.r = 56;
907 	colour3.g = 225;
908 	colour3.b = 56;
909 	colour1.a = 255;
910 
911 	drawDisintegrationLine(self->startX, self->startY, self->endX, self->endY, colour1, colour2, colour3);
912 
913 	return TRUE;
914 }
915 
soulSteal()916 static void soulSteal()
917 {
918 	Target *t;
919 
920 	setCustomAction(&player, &stickToFloor, 3, 0, 0);
921 
922 	self->thinkTime--;
923 
924 	player.x = self->targetX;
925 
926 	self->maxThinkTime += self->mental;
927 
928 	if (self->maxThinkTime > 255)
929 	{
930 		self->maxThinkTime = 255;
931 
932 		self->mental *= -1;
933 	}
934 
935 	else if (self->maxThinkTime < 0)
936 	{
937 		self->maxThinkTime = 0;
938 
939 		self->mental *= -1;
940 	}
941 
942 	player.alpha = self->maxThinkTime;
943 
944 	if (self->thinkTime <= 0)
945 	{
946 		self->flags |= NO_DRAW;
947 
948 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
949 
950 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
951 
952 		t = getTargetByName("AZRIEL_TOP_TARGET");
953 
954 		if (t == NULL)
955 		{
956 			showErrorAndExit("Azriel cannot find target");
957 		}
958 
959 		self->x = t->x;
960 		self->y = t->y;
961 
962 		self->head->health++;
963 
964 		self->head->alpha = self->head->health * 64;
965 
966 		if (self->head->alpha > 255)
967 		{
968 			self->head->alpha = 255;
969 		}
970 
971 		self->head->thinkTime = self->head->maxThinkTime;
972 
973 		self->head->mental = 0;
974 
975 		player.alpha = self->targetY;
976 
977 		player.alpha -= 64;
978 
979 		if (player.alpha <= 0)
980 		{
981 			player.alpha = 0;
982 
983 			player.thinkTime = 600;
984 
985 			player.health = 0;
986 
987 			setPlayerLocked(TRUE);
988 		}
989 
990 		self->thinkTime = 30;
991 
992 		self->action = &soulStealFinish;
993 	}
994 
995 	checkToMap(self);
996 
997 	becomeTransparent();
998 }
999 
soulStealFinish()1000 static void soulStealFinish()
1001 {
1002 	self->thinkTime--;
1003 
1004 	if (self->thinkTime <= 0)
1005 	{
1006 		if (player.health == 0)
1007 		{
1008 			self->target->inUse = FALSE;
1009 		}
1010 
1011 		setEntityAnimation(self, "STAND");
1012 
1013 		self->flags &= ~NO_DRAW;
1014 
1015 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
1016 
1017 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
1018 
1019 		self->action = &attackFinished;
1020 	}
1021 
1022 	checkToMap(self);
1023 
1024 	becomeTransparent();
1025 }
1026 
spikeAttackInit()1027 static void spikeAttackInit()
1028 {
1029 	Target *t = getTargetByName("AZRIEL_TOP_TARGET");
1030 
1031 	if (t == NULL)
1032 	{
1033 		showErrorAndExit("Azriel cannot find target");
1034 	}
1035 
1036 	self->targetX = t->x;
1037 	self->targetY = t->y;
1038 
1039 	calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1040 
1041 	self->dirX *= self->speed;
1042 	self->dirY *= self->speed;
1043 
1044 	self->action = &spikeAttackMoveToTopTarget;
1045 
1046 	self->thinkTime = 30;
1047 
1048 	checkToMap(self);
1049 
1050 	becomeTransparent();
1051 }
1052 
spikeAttackMoveToTopTarget()1053 static void spikeAttackMoveToTopTarget()
1054 {
1055 	Entity *e;
1056 
1057 	if (atTarget())
1058 	{
1059 		self->thinkTime--;
1060 
1061 		if (self->thinkTime <= 0)
1062 		{
1063 			e = getFreeEntity();
1064 
1065 			loadProperties("boss/azriel_light_beam", e);
1066 
1067 			setEntityAnimation(e, "APPEAR");
1068 
1069 			e->animationCallback = &beamAppearFinish;
1070 
1071 			e->head = self;
1072 
1073 			e->x = getMapStartX() + prand() % (SCREEN_WIDTH - e->w);
1074 
1075 			e->y = getMapFloor(self->x + self->w / 2, self->y) - e->h;
1076 
1077 			e->startY = e->y;
1078 
1079 			e->action = &beamWait;
1080 			e->draw = &drawBeam;
1081 			e->touch = &entityTouch;
1082 
1083 			e->face = RIGHT;
1084 
1085 			e->type = ENEMY;
1086 
1087 			e->thinkTime = 240;
1088 
1089 			e->mental = prand() % 3 == 0 ? 1 : 0;
1090 
1091 			e->targetX = playSoundToMap("sound/boss/grimlore/grimlore_summon", -1, e->x, e->y, -1);
1092 
1093 			self->action = &spikeAttackWait;
1094 
1095 			self->mental = 1;
1096 		}
1097 	}
1098 
1099 	checkToMap(self);
1100 
1101 	becomeTransparent();
1102 }
1103 
beamAppearFinish()1104 static void beamAppearFinish()
1105 {
1106 	setEntityAnimation(self, "STAND");
1107 }
1108 
drawBeam()1109 static int drawBeam()
1110 {
1111 	self->y = self->startY;
1112 
1113 	drawLoopingAnimationToMap();
1114 
1115 	while (self->y > 0)
1116 	{
1117 		self->y -= self->h;
1118 
1119 		drawSpriteToMap();
1120 	}
1121 
1122 	return TRUE;
1123 }
1124 
beamWait()1125 static void beamWait()
1126 {
1127 	int i, x, startX, floor;
1128 	Entity *e;
1129 
1130 	self->thinkTime--;
1131 
1132 	if (self->thinkTime <= 0)
1133 	{
1134 		if (self->mental == 1)
1135 		{
1136 			self->thinkTime = 60;
1137 
1138 			setEntityAnimation(self, "STAND_RED");
1139 
1140 			self->action = &redBeamWait;
1141 		}
1142 
1143 		else
1144 		{
1145 			i = 0;
1146 
1147 			floor = getMapFloor(self->head->x + self->head->w / 2, self->head->y);
1148 
1149 			/* Left side of beam */
1150 
1151 			x = self->x;
1152 
1153 			startX = getMapStartX();
1154 
1155 			for (;x>=startX;)
1156 			{
1157 				e = getFreeEntity();
1158 
1159 				loadProperties("boss/azriel_ground_spikes", e);
1160 
1161 				e->head = self;
1162 
1163 				e->x = x - e->w;
1164 				e->y = floor;
1165 
1166 				e->startY = e->y - e->h;
1167 
1168 				e->endY = e->y;
1169 
1170 				e->action = &spikeRise;
1171 				e->draw = &drawLoopingAnimationToMap;
1172 				e->touch = &entityTouch;
1173 
1174 				e->face = RIGHT;
1175 
1176 				e->type = ENEMY;
1177 
1178 				e->thinkTime = prand() % 30;
1179 
1180 				x = e->x;
1181 
1182 				i++;
1183 			}
1184 
1185 			/* Right side of beam */
1186 
1187 			x = self->x + self->w;
1188 
1189 			startX = getMapStartX() + SCREEN_WIDTH;
1190 
1191 			for (;x<startX;)
1192 			{
1193 				e = getFreeEntity();
1194 
1195 				loadProperties("boss/azriel_ground_spikes", e);
1196 
1197 				e->head = self;
1198 
1199 				e->x = x;
1200 				e->y = floor;
1201 
1202 				e->startY = e->y - e->h;
1203 
1204 				e->endY = e->y;
1205 
1206 				e->action = &spikeRise;
1207 				e->draw = &drawLoopingAnimationToMap;
1208 				e->touch = &entityTouch;
1209 
1210 				e->face = RIGHT;
1211 
1212 				e->type = ENEMY;
1213 
1214 				e->thinkTime = prand() % 30;
1215 
1216 				x = e->x + e->w;
1217 
1218 				i++;
1219 			}
1220 
1221 			self->mental = i;
1222 
1223 			self->thinkTime = 30;
1224 
1225 			self->action = &beamFinish;
1226 		}
1227 	}
1228 }
1229 
redBeamWait()1230 static void redBeamWait()
1231 {
1232 	Entity *e;
1233 
1234 	self->thinkTime--;
1235 
1236 	if (self->thinkTime <= 0)
1237 	{
1238 		e = getFreeEntity();
1239 
1240 		loadProperties("boss/azriel_ground_spikes", e);
1241 
1242 		e->head = self;
1243 
1244 		e->x = self->x + self->w / 2 - e->w / 2;
1245 		e->y = getMapFloor(self->head->x + self->head->w / 2, self->head->y);
1246 
1247 		e->startY = e->y - e->h;
1248 
1249 		e->endY = e->y;
1250 
1251 		e->action = &spikeRise;
1252 		e->draw = &drawLoopingAnimationToMap;
1253 		e->touch = &entityTouch;
1254 
1255 		e->face = RIGHT;
1256 
1257 		e->type = ENEMY;
1258 
1259 		self->thinkTime = 30;
1260 
1261 		self->action = &redBeamFinish;
1262 	}
1263 }
1264 
beamFinish()1265 static void beamFinish()
1266 {
1267 	if (self->mental <= 0)
1268 	{
1269 		setEntityAnimation(self, "DISAPPEAR");
1270 
1271 		self->animationCallback = &beamDisappearFinish;
1272 	}
1273 }
1274 
redBeamFinish()1275 static void redBeamFinish()
1276 {
1277 	if (self->mental <= 0)
1278 	{
1279 		setEntityAnimation(self, "DISAPPEAR_RED");
1280 
1281 		self->animationCallback = &beamDisappearFinish;
1282 	}
1283 }
1284 
beamDisappearFinish()1285 static void beamDisappearFinish()
1286 {
1287 	self->head->mental = 0;
1288 
1289 	self->inUse = FALSE;
1290 
1291 	stopSound(self->targetX);
1292 }
1293 
spikeRise()1294 static void spikeRise()
1295 {
1296 	Entity *e;
1297 
1298 	self->thinkTime--;
1299 
1300 	if (self->thinkTime <= 0)
1301 	{
1302 		if (self->y > self->startY)
1303 		{
1304 			self->y -= self->speed * 2;
1305 		}
1306 
1307 		else
1308 		{
1309 			playSoundToMap("sound/common/crumble", BOSS_CHANNEL, self->x, self->y, 0);
1310 
1311 			shakeScreen(MEDIUM, 15);
1312 
1313 			e = addSmallRock(self->x, self->y, "common/small_rock");
1314 
1315 			e->x += (self->w - e->w) / 2;
1316 			e->y += (self->h - e->h) / 2;
1317 
1318 			e->dirX = -3;
1319 			e->dirY = -8;
1320 
1321 			e = addSmallRock(self->x, self->y, "common/small_rock");
1322 
1323 			e->x += (self->w - e->w) / 2;
1324 			e->y += (self->h - e->h) / 2;
1325 
1326 			e->dirX = 3;
1327 			e->dirY = -8;
1328 
1329 			self->y = self->startY;
1330 
1331 			self->health = 15;
1332 
1333 			self->thinkTime = 120;
1334 
1335 			self->action = &spikeWait;
1336 		}
1337 	}
1338 }
1339 
spikeWait()1340 static void spikeWait()
1341 {
1342 	if (self->health > 0)
1343 	{
1344 		self->y = self->startY + cos(DEG_TO_RAD(self->endX)) * 2;
1345 
1346 		self->health--;
1347 
1348 		if (self->health <= 0)
1349 		{
1350 			self->y = self->startY;
1351 		}
1352 
1353 		self->endX += 90;
1354 	}
1355 
1356 	else
1357 	{
1358 		self->thinkTime--;
1359 
1360 		if (self->thinkTime <= 0)
1361 		{
1362 			self->action = &spikeSink;
1363 		}
1364 	}
1365 }
1366 
spikeSink()1367 static void spikeSink()
1368 {
1369 	if (self->y < self->endY)
1370 	{
1371 		self->y += self->speed * 2;
1372 	}
1373 
1374 	else
1375 	{
1376 		self->inUse = FALSE;
1377 
1378 		self->head->mental--;
1379 	}
1380 }
1381 
spikeAttackWait()1382 static void spikeAttackWait()
1383 {
1384 	if (self->mental == 0)
1385 	{
1386 		self->action = &attackFinished;
1387 	}
1388 }
1389 
scytheThrowInit()1390 static void scytheThrowInit()
1391 {
1392 	Target *t;
1393 
1394 	if (prand() % 2 == 0)
1395 	{
1396 		t = getTargetByName("AZRIEL_LEFT_TARGET");
1397 
1398 		self->face = RIGHT;
1399 	}
1400 
1401 	else
1402 	{
1403 		t = getTargetByName("AZRIEL_RIGHT_TARGET");
1404 
1405 		self->face = LEFT;
1406 	}
1407 
1408 	if (t == NULL)
1409 	{
1410 		showErrorAndExit("Azriel cannot find target");
1411 	}
1412 
1413 	self->targetX = t->x;
1414 	self->targetY = t->y;
1415 
1416 	calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1417 
1418 	self->dirX *= self->speed;
1419 	self->dirY *= self->speed;
1420 
1421 	self->action = &scytheThrowMoveToTarget;
1422 
1423 	checkToMap(self);
1424 
1425 	becomeTransparent();
1426 }
1427 
scytheThrowMoveToTarget()1428 static void scytheThrowMoveToTarget()
1429 {
1430 	if (atTarget())
1431 	{
1432 		self->mental = 3;
1433 
1434 		self->action = &scytheThrowReady;
1435 	}
1436 
1437 	checkToMap(self);
1438 
1439 	becomeTransparent();
1440 }
1441 
scytheThrowReady()1442 static void scytheThrowReady()
1443 {
1444 	Entity *e;
1445 
1446 	setEntityAnimation(self, "SCYTHE_THROW_READY");
1447 
1448 	self->animationCallback = &scytheThrow;
1449 
1450 	e = self->target;
1451 
1452 	e->endX = -1;
1453 
1454 	setEntityAnimation(e, "SCYTHE_THROW_READY");
1455 
1456 	e->face = self->face;
1457 
1458 	if (e->face == LEFT)
1459 	{
1460 		e->x = self->x + self->w - e->w - e->offsetX;
1461 	}
1462 
1463 	else
1464 	{
1465 		e->x = self->x + e->offsetX;
1466 	}
1467 
1468 	e->y = self->y + e->offsetY;
1469 
1470 	e->startX = e->x;
1471 
1472 	checkToMap(self);
1473 
1474 	becomeTransparent();
1475 }
1476 
scytheThrow()1477 static void scytheThrow()
1478 {
1479 	Entity *e;
1480 	int distance;
1481 
1482 	self->thinkTime--;
1483 
1484 	if (self->thinkTime <= 0)
1485 	{
1486 		e = self->target;
1487 
1488 		e->alpha = 255;
1489 
1490 		setEntityAnimation(self, "SCYTHE_THROW");
1491 
1492 		setEntityAnimation(e, "SCYTHE_THROW");
1493 
1494 		e->dirX = self->face == LEFT ? -e->speed : e->speed;
1495 
1496 		e->action = &scytheMove;
1497 		e->touch = &entityTouch;
1498 
1499 		switch (self->mental)
1500 		{
1501 			case 3:
1502 				distance = SCREEN_WIDTH / 3;
1503 			break;
1504 
1505 			case 2:
1506 				distance = SCREEN_WIDTH * 2 / 3;
1507 			break;
1508 
1509 			default:
1510 				distance = SCREEN_WIDTH;
1511 			break;
1512 		}
1513 
1514 		if (self->face == LEFT)
1515 		{
1516 			e->targetX = e->x - distance;
1517 		}
1518 
1519 		else
1520 		{
1521 			e->targetX = e->x + distance;
1522 		}
1523 
1524 		if (e->endX == -1)
1525 		{
1526 			e->endX = playSoundToMap("sound/boss/azriel/azriel_scythe_throw", -1, self->x, self->y, -1);
1527 		}
1528 
1529 		self->action = &scytheThrowWait;
1530 
1531 		self->thinkTime = 1;
1532 
1533 		self->mental--;
1534 	}
1535 
1536 	checkToMap(self);
1537 
1538 	becomeTransparent();
1539 }
1540 
scytheThrowWait()1541 static void scytheThrowWait()
1542 {
1543 	if (self->thinkTime <= 0)
1544 	{
1545 		setEntityAnimation(self, "STAND");
1546 
1547 		self->thinkTime = self->mental <= 0 ? 60 : 5;
1548 
1549 		self->action = self->mental <= 0 ? &scytheThrowTeleportAway : &scytheThrowReady;
1550 	}
1551 
1552 	checkToMap(self);
1553 
1554 	becomeTransparent();
1555 }
1556 
scytheThrowTeleportAway()1557 static void scytheThrowTeleportAway()
1558 {
1559 	Target *t;
1560 
1561 	self->thinkTime--;
1562 
1563 	if (self->thinkTime <= 0)
1564 	{
1565 		self->flags |= NO_DRAW;
1566 
1567 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
1568 
1569 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
1570 
1571 		t = getTargetByName("AZRIEL_TOP_TARGET");
1572 
1573 		if (t == NULL)
1574 		{
1575 			showErrorAndExit("Azriel cannot find target");
1576 		}
1577 
1578 		self->x = t->x;
1579 		self->y = t->y;
1580 
1581 		self->action = &scytheThrowFinish;
1582 
1583 		self->thinkTime = 30;
1584 	}
1585 }
1586 
scytheThrowFinish()1587 static void scytheThrowFinish()
1588 {
1589 	self->thinkTime--;
1590 
1591 	if (self->thinkTime <= 0)
1592 	{
1593 		self->flags &= ~NO_DRAW;
1594 
1595 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
1596 
1597 		self->action = &attackFinished;
1598 	}
1599 }
1600 
scytheMove()1601 static void scytheMove()
1602 {
1603 	if (self->face == LEFT)
1604 	{
1605 		if (self->dirX < 0 && self->x <= self->targetX)
1606 		{
1607 			self->dirX *= -1;
1608 		}
1609 
1610 		else if (self->dirX > 0 && self->x >= self->startX)
1611 		{
1612 			self->head->thinkTime = 0;
1613 
1614 			if (self->head->mental == 0)
1615 			{
1616 				self->action = &scytheWait;
1617 				self->touch = NULL;
1618 			}
1619 
1620 			stopSound(self->endX);
1621 		}
1622 	}
1623 
1624 	else
1625 	{
1626 		if (self->dirX > 0 && self->x >= self->targetX)
1627 		{
1628 			self->dirX *= -1;
1629 		}
1630 
1631 		else if (self->dirX < 0 && self->x <= self->startX)
1632 		{
1633 			self->head->thinkTime = 0;
1634 
1635 			if (self->head->mental == 0)
1636 			{
1637 				self->action = &scytheWait;
1638 				self->touch = NULL;
1639 			}
1640 
1641 			stopSound(self->endX);
1642 		}
1643 	}
1644 
1645 	checkToMap(self);
1646 }
1647 
phantasmalBoltInit()1648 static void phantasmalBoltInit()
1649 {
1650 	Target *t;
1651 
1652 	if (prand() % 2 == 0)
1653 	{
1654 		t = getTargetByName("AZRIEL_LEFT_TARGET");
1655 
1656 		self->face = RIGHT;
1657 	}
1658 
1659 	else
1660 	{
1661 		t = getTargetByName("AZRIEL_RIGHT_TARGET");
1662 
1663 		self->face = LEFT;
1664 	}
1665 
1666 	if (t == NULL)
1667 	{
1668 		showErrorAndExit("Azriel cannot find target");
1669 	}
1670 
1671 	self->targetX = t->x;
1672 	self->targetY = t->y;
1673 
1674 	calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1675 
1676 	self->dirX *= self->speed;
1677 	self->dirY *= self->speed;
1678 
1679 	self->action = &phantasmalBoltMoveToTarget;
1680 
1681 	checkToMap(self);
1682 
1683 	becomeTransparent();
1684 }
1685 
phantasmalBoltMoveToTarget()1686 static void phantasmalBoltMoveToTarget()
1687 {
1688 	Entity *e;
1689 
1690 	if (atTarget())
1691 	{
1692 		setEntityAnimation(self, "PHANTASMAL_BOLT_READY");
1693 
1694 		e = getFreeEntity();
1695 
1696 		if (e == NULL)
1697 		{
1698 			showErrorAndExit("No free slots to add Azriel's Phantasmal Bolt");
1699 		}
1700 
1701 		loadProperties("boss/azriel_phantasmal_bolt", e);
1702 
1703 		setEntityAnimationByID(e, 0);
1704 
1705 		e->face = self->face;
1706 
1707 		if (e->face == LEFT)
1708 		{
1709 			e->x = self->x + self->w - e->w - e->offsetX;
1710 		}
1711 
1712 		else
1713 		{
1714 			e->x = self->x + e->offsetX;
1715 		}
1716 
1717 		e->y = self->y + e->offsetY;
1718 
1719 		e->action = &phantasmalBoltWait;
1720 
1721 		e->draw = &drawLoopingAnimationToMap;
1722 
1723 		e->thinkTime = 30;
1724 
1725 		e->head = self;
1726 
1727 		self->mental = 1;
1728 
1729 		self->action = &phantasmalBolt;
1730 	}
1731 
1732 	checkToMap(self);
1733 
1734 	becomeTransparent();
1735 }
1736 
phantasmalBoltWait()1737 static void phantasmalBoltWait()
1738 {
1739 	self->thinkTime--;
1740 
1741 	if (self->thinkTime <= 0)
1742 	{
1743 		self->mental++;
1744 
1745 		if (self->mental > 6)
1746 		{
1747 			self->head->mental = 0;
1748 
1749 			self->mental = 6;
1750 
1751 			self->inUse = FALSE;
1752 		}
1753 
1754 		setEntityAnimationByID(self, self->mental);
1755 
1756 		self->thinkTime = self->mental == 6 ? 60 : 20;
1757 	}
1758 }
1759 
phantasmalBolt()1760 static void phantasmalBolt()
1761 {
1762 	Entity *e;
1763 
1764 	if (self->mental == 0)
1765 	{
1766 		setEntityAnimation(self, "PHANTASMAL_BOLT_FIRE");
1767 
1768 		e = addProjectile("boss/azriel_phantasmal_bolt", self, self->x, self->y, self->face == LEFT ? -8 : 8, 0);
1769 
1770 		e->face = self->face;
1771 
1772 		e->damage = player.health / 2;
1773 
1774 		if (e->damage == 0)
1775 		{
1776 			e->damage = 1;
1777 		}
1778 
1779 		setEntityAnimation(e, "FIRE");
1780 
1781 		playSoundToMap("sound/boss/snake_boss/snake_boss_shot", -1, self->x, self->y, 0);
1782 
1783 		if (self->face == LEFT)
1784 		{
1785 			e->x = self->x + self->w - e->w - e->offsetX;
1786 		}
1787 
1788 		else
1789 		{
1790 			e->x = self->x + e->offsetX;
1791 		}
1792 
1793 		e->y = self->y + e->offsetY;
1794 
1795 		e->action = &phantasmalBoltMove;
1796 
1797 		e->flags |= FLY;
1798 
1799 		e->reactToBlock = &phantasmalBoltReflect;
1800 
1801 		e->thinkTime = 1200;
1802 
1803 		e->mental = 2;
1804 
1805 		self->thinkTime = 120;
1806 
1807 		self->action = &phantasmalBoltFinish;
1808 
1809 		self->endY = 0;
1810 
1811 		self->head->mental = 1;
1812 	}
1813 
1814 	checkToMap(self);
1815 
1816 	becomeTransparent();
1817 }
1818 
phantasmalBoltFinish()1819 static void phantasmalBoltFinish()
1820 {
1821 	self->thinkTime--;
1822 
1823 	if (self->thinkTime <= 0)
1824 	{
1825 		setEntityAnimation(self, "STAND");
1826 
1827 		self->action = &attackFinished;
1828 	}
1829 
1830 	checkToMap(self);
1831 
1832 	becomeTransparent();
1833 }
1834 
phantasmalBoltMove()1835 static void phantasmalBoltMove()
1836 {
1837 	Entity *e;
1838 
1839 	self->dirX = self->face == LEFT ? -fabs(self->dirX) : fabs(self->dirX);
1840 
1841 	self->mental--;
1842 
1843 	if (self->mental <= 0)
1844 	{
1845 		e = addBasicDecoration(self->x, self->y, "decoration/bolt_trail");
1846 
1847 		if (e != NULL)
1848 		{
1849 			e->x = self->face == LEFT ? self->x + self->w - e->w : self->x;
1850 
1851 			e->y = self->y + self->h / 2 - e->h / 2;
1852 
1853 			e->y += (prand() % 8) * (prand() % 2 == 0 ? 1 : -1);
1854 
1855 			e->thinkTime = 15 + prand() % 15;
1856 
1857 			e->dirY = (1 + prand() % 10) * (prand() % 2 == 0 ? 1 : -1);
1858 
1859 			e->dirY /= 10;
1860 		}
1861 
1862 		self->mental = 2;
1863 	}
1864 
1865 	checkToMap(self);
1866 
1867 	if (self->dirX == 0 || self->thinkTime <= 0)
1868 	{
1869 		self->inUse = FALSE;
1870 	}
1871 }
1872 
phantasmalBoltReflect(Entity * other)1873 static void phantasmalBoltReflect(Entity *other)
1874 {
1875 	if (other->element != PHANTASMAL)
1876 	{
1877 		self->inUse = FALSE;
1878 
1879 		return;
1880 	}
1881 
1882 	if (other->mental <= 7)
1883 	{
1884 		self->damage = 1200;
1885 	}
1886 
1887 	else if (other->mental <= 15)
1888 	{
1889 		self->damage = 600;
1890 	}
1891 
1892 	else if (other->mental <= 30)
1893 	{
1894 		self->damage = 300;
1895 	}
1896 
1897 	else
1898 	{
1899 		self->damage = 0;
1900 	}
1901 
1902 	self->parent = other;
1903 
1904 	self->face = self->face == LEFT ? RIGHT : LEFT;
1905 }
1906 
raiseDeadInit()1907 static void raiseDeadInit()
1908 {
1909 	Target *t = getTargetByName("AZRIEL_TOP_TARGET");
1910 
1911 	if (t == NULL)
1912 	{
1913 		showErrorAndExit("Azriel cannot find target");
1914 	}
1915 
1916 	self->targetX = t->x;
1917 	self->targetY = t->y;
1918 
1919 	calculatePath(self->x, self->y, self->targetX, self->targetY, &self->dirX, &self->dirY);
1920 
1921 	self->dirX *= self->speed;
1922 	self->dirY *= self->speed;
1923 
1924 	self->action = &raiseDeadMoveToTopTarget;
1925 
1926 	checkToMap(self);
1927 
1928 	becomeTransparent();
1929 }
1930 
raiseDeadMoveToTopTarget()1931 static void raiseDeadMoveToTopTarget()
1932 {
1933 	char c;
1934 	int i, j;
1935 
1936 	if (atTarget())
1937 	{
1938 		setEntityAnimation(self, "PHANTASMAL_BOLT_FIRE");
1939 
1940 		self->thinkTime = 30;
1941 
1942 		self->action = &raiseDead;
1943 
1944 		self->mental = 2 + prand() % 4;
1945 
1946 		STRNCPY(self->description, "123456", sizeof(self->description));
1947 
1948 		for (i=0;i<6;i++)
1949 		{
1950 			j = prand() % 6;
1951 
1952 			c = self->description[i];
1953 
1954 			self->description[i] = self->description[j];
1955 
1956 			self->description[j] = c;
1957 		}
1958 	}
1959 
1960 	checkToMap(self);
1961 
1962 	becomeTransparent();
1963 }
1964 
raiseDead()1965 static void raiseDead()
1966 {
1967 	char targetName[MAX_VALUE_LENGTH];
1968 	Target *t;
1969 	Entity *e;
1970 
1971 	self->thinkTime--;
1972 
1973 	if (self->thinkTime <= 0)
1974 	{
1975 		SNPRINTF(targetName, MAX_VALUE_LENGTH, "GRAVE_%c", self->description[self->mental]);
1976 
1977 		t = getTargetByName(targetName);
1978 
1979 		if (t == NULL)
1980 		{
1981 			showErrorAndExit("Azriel cannot find target");
1982 		}
1983 
1984 		e = addEnemy("enemy/zombie", t->x, t->y);
1985 
1986 		e->y = getMapFloor(self->x + self->w / 2, self->y);
1987 
1988 		e->startX = e->x;
1989 
1990 		e->startY = e->y - e->h;
1991 
1992 		e->endY = e->y;
1993 
1994 		e->thinkTime = 15 + prand() % 105;
1995 
1996 		self->mental--;
1997 
1998 		if (self->mental <= 0)
1999 		{
2000 			self->thinkTime = 30;
2001 
2002 			self->action = &raiseDeadFinish;
2003 		}
2004 	}
2005 
2006 	checkToMap(self);
2007 
2008 	becomeTransparent();
2009 }
2010 
raiseDeadFinish()2011 static void raiseDeadFinish()
2012 {
2013 	Entity *e;
2014 
2015 	self->thinkTime--;
2016 
2017 	if (self->thinkTime <= 0)
2018 	{
2019 		e = getEntityByName("enemy/zombie");
2020 
2021 		if (e == NULL)
2022 		{
2023 			self->action = &attackFinished;
2024 		}
2025 
2026 		else
2027 		{
2028 			self->thinkTime = 30;
2029 		}
2030 	}
2031 
2032 	checkToMap(self);
2033 
2034 	becomeTransparent();
2035 }
2036 
attackFinished()2037 static void attackFinished()
2038 {
2039 	setEntityAnimation(self, player.alpha == 0 ? "INTRO" : "STAND");
2040 
2041 	self->thinkTime = 30;
2042 
2043 	self->action = &entityWait;
2044 
2045 	checkToMap(self);
2046 
2047 	becomeTransparent();
2048 }
2049 
becomeTransparent()2050 static void becomeTransparent()
2051 {
2052 	if (strcmpignorecase(self->name, "boss/azriel") != 0)
2053 	{
2054 		printf("%s cannot become transparent!\n", self->name);
2055 
2056 		cleanup(1);
2057 	}
2058 
2059 	self->endX--;
2060 
2061 	if (self->endX <= 0)
2062 	{
2063 		if (self->alpha > 128)
2064 		{
2065 			self->alpha--;
2066 
2067 			self->endX = 3;
2068 		}
2069 
2070 		else
2071 		{
2072 			self->alpha = 128;
2073 
2074 			self->endX = 0;
2075 		}
2076 	}
2077 }
2078 
takeDamage(Entity * other,int damage)2079 static void takeDamage(Entity *other, int damage)
2080 {
2081 	Entity *temp;
2082 
2083 	if (self->flags & INVULNERABLE)
2084 	{
2085 		return;
2086 	}
2087 
2088 	if (self->alpha != 255)
2089 	{
2090 		if (other->element == PHANTASMAL)
2091 		{
2092 			self->alpha = 255;
2093 
2094 			self->endX = damage;
2095 
2096 			entityTakeDamageNoFlinch(other, damage);
2097 
2098 			if (other->type == PROJECTILE)
2099 			{
2100 				temp = self;
2101 
2102 				self = other;
2103 
2104 				self->die();
2105 
2106 				self = temp;
2107 			}
2108 		}
2109 
2110 		else
2111 		{
2112 			if (prand() % 10 == 0)
2113 			{
2114 				setInfoBoxMessage(60, 255, 255, 255, _("This weapon is not having any effect..."));
2115 			}
2116 
2117 			damage = 0;
2118 
2119 			setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
2120 		}
2121 	}
2122 
2123 	else
2124 	{
2125 		/* Take minimal damage from bombs */
2126 
2127 		if (other->type == EXPLOSION)
2128 		{
2129 			damage = 1;
2130 		}
2131 
2132 		if (damage != 0)
2133 		{
2134 			self->health -= damage;
2135 
2136 			if (self->health > 0)
2137 			{
2138 				setCustomAction(self, &flashWhite, 6, 0, 0);
2139 
2140 				/* Don't make an enemy invulnerable from a projectile hit, allows multiple hits */
2141 
2142 				if (other->type != PROJECTILE)
2143 				{
2144 					setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
2145 				}
2146 
2147 				if (self->pain != NULL)
2148 				{
2149 					self->pain();
2150 				}
2151 			}
2152 
2153 			else
2154 			{
2155 				self->damage = 0;
2156 
2157 				if (other->type == WEAPON || other->type == PROJECTILE)
2158 				{
2159 					increaseKillCount();
2160 				}
2161 
2162 				self->die();
2163 			}
2164 
2165 			if (other->type == PROJECTILE)
2166 			{
2167 				temp = self;
2168 
2169 				self = other;
2170 
2171 				self->die();
2172 
2173 				self = temp;
2174 			}
2175 		}
2176 	}
2177 }
2178 
soulWait()2179 static void soulWait()
2180 {
2181 	self->thinkTime--;
2182 
2183 	if (self->thinkTime <= 0)
2184 	{
2185 		self->thinkTime = 0;
2186 	}
2187 
2188 	self->endX++;
2189 
2190 	if (self->endX >= 360)
2191 	{
2192 		self->endX = 0;
2193 	}
2194 
2195 	self->y = self->startY + sin(DEG_TO_RAD(self->endX)) * 8;
2196 
2197 	if (self->alpha >= 255)
2198 	{
2199 		self->thinkTime = 90;
2200 
2201 		self->action = &soulLeave;
2202 	}
2203 }
2204 
soulLeave()2205 static void soulLeave()
2206 {
2207 	if (self->flags & FLY)
2208 	{
2209 		self->thinkTime--;
2210 
2211 		if (self->thinkTime <= 0)
2212 		{
2213 			removeInventoryItemByObjectiveName("Amulet of Resurrection");
2214 
2215 			player.die();
2216 
2217 			self->thinkTime = 30;
2218 
2219 			self->flags &= ~FLY;
2220 		}
2221 	}
2222 
2223 	checkToMap(self);
2224 
2225 	if (self->flags & ON_GROUND)
2226 	{
2227 		self->thinkTime--;
2228 
2229 		if (self->thinkTime <= 0)
2230 		{
2231 			setEntityAnimation(self, "WALK");
2232 
2233 			self->dirX = -self->speed;
2234 		}
2235 	}
2236 }
2237 
die()2238 static void die()
2239 {
2240 	self->startX = self->x;
2241 
2242 	self->thinkTime = 180;
2243 
2244 	self->takeDamage = NULL;
2245 
2246 	self->action = &dieShudder;
2247 }
2248 
dieShudder()2249 static void dieShudder()
2250 {
2251 	Target *t;
2252 
2253 	self->x = self->startX + sin(DEG_TO_RAD(self->targetY)) * 4;
2254 
2255 	self->targetY += 90;
2256 
2257 	if (self->targetY >= 360)
2258 	{
2259 		self->targetY = 0;
2260 	}
2261 
2262 	self->thinkTime--;
2263 
2264 	if (self->thinkTime <= 0)
2265 	{
2266 		self->flags |= NO_DRAW;
2267 
2268 		addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
2269 
2270 		playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
2271 
2272 		t = getTargetByName("AZRIEL_TOP_TARGET");
2273 
2274 		if (t == NULL)
2275 		{
2276 			showErrorAndExit("Azriel cannot find target");
2277 		}
2278 
2279 		self->x = t->x;
2280 		self->y = self->startY;
2281 
2282 		self->action = &dieMoveToTop;
2283 
2284 		self->thinkTime = 60;
2285 
2286 		self->mental = 0;
2287 	}
2288 }
2289 
dieMoveToTop()2290 static void dieMoveToTop()
2291 {
2292 	EntityList *l, *list;
2293 	Entity *e;
2294 
2295 	self->thinkTime--;
2296 
2297 	if (self->thinkTime <= 0)
2298 	{
2299 		switch (self->mental)
2300 		{
2301 			case 0:
2302 				self->target->inUse = FALSE;
2303 
2304 				stopSound(self->target->endX);
2305 
2306 				setEntityAnimation(self, "INTRO");
2307 
2308 				self->flags &= ~NO_DRAW;
2309 
2310 				addParticleExplosion(self->x + self->w / 2, self->y + self->h / 2);
2311 
2312 				playSoundToMap("sound/common/spell", -1, self->x, self->y, 0);
2313 
2314 				self->thinkTime = 90;
2315 
2316 				self->mental = 1;
2317 			break;
2318 
2319 			case 1:
2320 				createAutoDialogBox(_("Azriel"), _("Until we meet again..."), 120);
2321 
2322 				self->mental = 2;
2323 
2324 				self->thinkTime = 180;
2325 			break;
2326 
2327 			default:
2328 				list = createPixelsFromSprite(getCurrentSprite(self));
2329 
2330 				for (l=list->next;l!=NULL;l=l->next)
2331 				{
2332 					e = l->entity;
2333 
2334 					e->dirX = prand() % 30 * (e->x < self->x + self->w / 2 ? -1 : 1);
2335 					e->dirY = prand() % 30 * (e->y < self->y + self->h / 2 ? -1 : 1);
2336 
2337 					e->dirX /= 10;
2338 					e->dirY /= 10;
2339 
2340 					e->thinkTime = 180 + prand() % 180;
2341 				}
2342 
2343 				self->flags |= NO_DRAW;
2344 
2345 				freeEntityList(list);
2346 
2347 				self->thinkTime = 120;
2348 
2349 				self->action = &dieWait;
2350 
2351 				playSoundToMap("sound/boss/azriel/azriel_die", BOSS_CHANNEL, self->x, self->y, 0);
2352 			break;
2353 		}
2354 	}
2355 }
2356 
dieWait()2357 static void dieWait()
2358 {
2359 	Entity *e;
2360 
2361 	self->thinkTime--;
2362 
2363 	if (self->thinkTime <= 0)
2364 	{
2365 		clearContinuePoint();
2366 
2367 		freeBossHealthBar();
2368 
2369 		e = addKeyItem("item/heart_container", self->x + self->w / 2, self->y);
2370 
2371 		e->x -= e->w;
2372 
2373 		e->dirY = ITEM_JUMP_HEIGHT;
2374 
2375 		self->action = &entityDieVanish;
2376 
2377 		fadeBossMusic();
2378 
2379 		player.alpha = 255;
2380 	}
2381 }
2382 
addScythe()2383 static void addScythe()
2384 {
2385 	Entity *e = getFreeEntity();
2386 
2387 	if (e == NULL)
2388 	{
2389 		showErrorAndExit("No free slots to add Azriel's Scythe");
2390 	}
2391 
2392 	loadProperties("boss/azriel_scythe", e);
2393 
2394 	e->x = self->x;
2395 	e->y = self->y;
2396 
2397 	e->action = &scytheWait;
2398 
2399 	e->draw = &drawLoopingAnimationToMap;
2400 	e->touch = NULL;
2401 	e->takeDamage = NULL;
2402 
2403 	e->creditsAction = &scytheCreditsMove;
2404 
2405 	e->type = ENEMY;
2406 
2407 	self->target = e;
2408 
2409 	e->head = self;
2410 
2411 	setEntityAnimation(e, "STAND");
2412 }
2413 
scytheWait()2414 static void scytheWait()
2415 {
2416 	self->face = self->head->face;
2417 
2418 	setEntityAnimation(self, getAnimationTypeAtIndex(self->head));
2419 
2420 	if (self->face == LEFT)
2421 	{
2422 		self->x = self->head->x + self->head->w - self->w - self->offsetX;
2423 	}
2424 
2425 	else
2426 	{
2427 		self->x = self->head->x + self->offsetX;
2428 	}
2429 
2430 	self->y = self->head->y + self->offsetY;
2431 
2432 	self->alpha = self->head->alpha;
2433 
2434 	if (self->head->flags & NO_DRAW)
2435 	{
2436 		self->flags |= NO_DRAW;
2437 	}
2438 
2439 	else
2440 	{
2441 		self->flags &= ~NO_DRAW;
2442 	}
2443 
2444 	if (self->head->flags & FLASH)
2445 	{
2446 		self->flags |= FLASH;
2447 	}
2448 
2449 	else
2450 	{
2451 		self->flags &= ~FLASH;
2452 	}
2453 }
2454 
creditsMove()2455 static void creditsMove()
2456 {
2457 	if (self->mental == 0)
2458 	{
2459 		addScythe();
2460 
2461 		self->mental = 1;
2462 	}
2463 
2464 	setEntityAnimation(self, "STAND");
2465 
2466 	self->creditsAction = &bossMoveToMiddle;
2467 }
2468 
scytheCreditsMove()2469 static void scytheCreditsMove()
2470 {
2471 	self->face = self->head->face;
2472 
2473 	setEntityAnimation(self, getAnimationTypeAtIndex(self->head));
2474 
2475 	if (self->face == LEFT)
2476 	{
2477 		self->x = self->head->x + self->head->w - self->w - self->offsetX;
2478 	}
2479 
2480 	else
2481 	{
2482 		self->x = self->head->x + self->offsetX;
2483 	}
2484 
2485 	self->y = self->head->y + self->offsetY;
2486 
2487 	if (self->head->inUse == FALSE)
2488 	{
2489 		self->inUse = FALSE;
2490 	}
2491 }
2492