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