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