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