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 "../collisions.h"
24 #include "../custom_actions.h"
25 #include "../entity.h"
26 #include "../graphics/animation.h"
27 #include "../hud.h"
28 #include "../inventory.h"
29 #include "../item/item.h"
30 #include "../item/key_items.h"
31 #include "../map.h"
32 #include "../system/error.h"
33 #include "../system/properties.h"
34 #include "../system/random.h"
35
36 extern Entity *self;
37
38 static void jumpOverGap(void);
39 static void lookForPlayer(void);
40 static void moveAndJump(void);
41 static void jumpUp(void);
42 static int canJumpUp(void);
43 static int canDropDown(void);
44 static int isGapJumpable(void);
45 static void takeDamage(Entity *, int);
46 static void changeArmour(void);
47 static void boxWait(void);
48 static void die(void);
49 static void init(void);
50 static int draw(void);
51 static void creditsMove(void);
52
addArmourChanger(int x,int y,char * name)53 Entity *addArmourChanger(int x, int y, char *name)
54 {
55 Entity *e = getFreeEntity();
56
57 if (e == NULL)
58 {
59 showErrorAndExit("No free slots to add an Armour Changer");
60 }
61
62 loadProperties(name, e);
63
64 e->x = x;
65 e->y = y;
66
67 e->action = &init;
68
69 e->draw = &draw;
70 e->die = ¨
71 e->takeDamage = &takeDamage;
72 e->reactToBlock = &changeDirection;
73 e->touch = &entityTouch;
74
75 e->creditsAction = &init;
76
77 e->type = ENEMY;
78
79 setEntityAnimation(e, "STAND_1");
80
81 return e;
82 }
83
init()84 static void init()
85 {
86 if (self->mental == 0)
87 {
88 self->mental = 1 + prand() % 3;
89 }
90
91 self->action = &lookForPlayer;
92
93 self->creditsAction = &creditsMove;
94 }
95
lookForPlayer()96 static void lookForPlayer()
97 {
98 self->thinkTime--;
99
100 if (self->thinkTime <= 0)
101 {
102 changeArmour();
103 }
104
105 switch (self->mental)
106 {
107 case 2:
108 setEntityAnimation(self, "WALK_2");
109 break;
110
111 case 3:
112 setEntityAnimation(self, "WALK_3");
113 break;
114
115 default:
116 setEntityAnimation(self, "WALK_1");
117 break;
118 }
119
120 moveAndJump();
121 }
122
moveAndJump()123 static void moveAndJump()
124 {
125 if (self->dirX == 0)
126 {
127 self->x += self->face == LEFT ? self->box.x : -self->box.x;
128
129 self->face = self->face == RIGHT ? LEFT : RIGHT;
130 }
131
132 if (self->standingOn == NULL || self->standingOn->dirX == 0)
133 {
134 self->dirX = (self->face == RIGHT ? self->speed : -self->speed);
135 }
136
137 else
138 {
139 self->dirX += (self->face == RIGHT ? self->speed : -self->speed);
140 }
141
142 if (isAtEdge(self) == TRUE)
143 {
144 if (isGapJumpable() == TRUE)
145 {
146 self->action = &jumpOverGap;
147
148 switch (self->mental)
149 {
150 case 2:
151 setEntityAnimation(self, "STAND_2");
152 break;
153
154 case 3:
155 setEntityAnimation(self, "STAND_3");
156 break;
157
158 default:
159 setEntityAnimation(self, "STAND_1");
160 break;
161 }
162 }
163
164 else if (canDropDown() == FALSE)
165 {
166 self->dirX = 0;
167 }
168 }
169
170 checkToMap(self);
171
172 if (self->dirX == 0)
173 {
174 if (canJumpUp() == TRUE)
175 {
176 self->action = &jumpUp;
177 }
178
179 else
180 {
181 self->dirX = (self->face == RIGHT ? -self->speed : self->speed);
182
183 self->face = (self->face == RIGHT ? LEFT : RIGHT);
184 }
185 }
186 }
187
jumpUp()188 static void jumpUp()
189 {
190 long onGround;
191
192 if (self->flags & ON_GROUND)
193 {
194 self->dirY = -JUMP_HEIGHT;
195 }
196
197 self->dirX = (self->face == RIGHT ? self->speed : -self->speed);
198
199 onGround = (self->flags & ON_GROUND);
200
201 checkToMap(self);
202
203 if (onGround == 0 && ((self->flags & ON_GROUND) || (self->standingOn != NULL)))
204 {
205 self->action = &lookForPlayer;
206 }
207 }
208
jumpOverGap()209 static void jumpOverGap()
210 {
211 long onGround;
212
213 self->dirX = (self->face == RIGHT ? 4 : -4);
214
215 if (self->flags & ON_GROUND)
216 {
217 self->dirY = -JUMP_HEIGHT;
218 }
219
220 onGround = (self->flags & ON_GROUND);
221
222 checkToMap(self);
223
224 if (onGround == 0 && ((self->flags & ON_GROUND) || (self->standingOn != NULL)))
225 {
226 self->action = &lookForPlayer;
227 }
228 }
229
canJumpUp()230 static int canJumpUp()
231 {
232 int tile, tile2, i;
233 int x = self->face == LEFT ? floor(self->x) : ceil(self->x) + self->w;
234 int y = self->y + self->h - 1;
235
236 x /= TILE_SIZE;
237 y /= TILE_SIZE;
238
239 x += self->face == LEFT ? -1 : 0;
240
241 for (i=0;i<4;i++)
242 {
243 tile = mapTileAt(x, y - (i + 1));
244
245 tile2 = mapTileAt(x, y - i);
246
247 if (!(tile != BLANK_TILE && tile < BACKGROUND_TILE_START) && (tile2 != BLANK_TILE && tile2 < BACKGROUND_TILE_START))
248 {
249 return TRUE;
250 }
251 }
252
253 return FALSE;
254 }
255
canDropDown()256 static int canDropDown()
257 {
258 int tile, i, j, width;
259 int x = self->face == LEFT ? floor(self->x) : ceil(self->x) + self->w;
260 int y = self->y + self->h - 1;
261
262 x /= TILE_SIZE;
263 y /= TILE_SIZE;
264
265 width = self->w / TILE_SIZE;
266
267 for (j=0;j<width;j++)
268 {
269 for (i=0;i<8;i++)
270 {
271 tile = mapTileAt(x + (self->face == LEFT ? -j : j), y + i);
272
273 if (tile >= WATER_TILE_START)
274 {
275 break;
276 }
277
278 if (tile != BLANK_TILE && tile < BACKGROUND_TILE_START)
279 {
280 x = (x + (self->face == LEFT ? -j : j)) * TILE_SIZE;
281 y = (y + i) * TILE_SIZE;
282
283 return TRUE;
284 }
285 }
286 }
287
288 return FALSE;
289 }
290
isGapJumpable()291 static int isGapJumpable()
292 {
293 int tile1, tile2;
294 int x = self->face == LEFT ? floor(self->x) : ceil(self->x) + self->w;
295 int y = self->y + self->h - 1;
296
297 x /= TILE_SIZE;
298 y /= TILE_SIZE;
299
300 y++;
301
302 x += self->face == LEFT ? -3 : 3;
303
304 tile1 = mapTileAt(x, y);
305
306 if (tile1 != BLANK_TILE && tile1 < BACKGROUND_TILE_START)
307 {
308 y--;
309
310 tile2 = mapTileAt(x, y);
311
312 if (tile2 == BLANK_TILE || (tile2 >= BACKGROUND_TILE_START && tile2 <= BACKGROUND_TILE_END))
313 {
314 return TRUE;
315 }
316 }
317
318 return FALSE;
319 }
320
die()321 static void die()
322 {
323 Entity *e;
324
325 playSoundToMap("sound/enemy/armadillo/armadillo_die", -1, self->x, self->y, 0);
326
327 if (prand() % 3 != 0)
328 {
329 e = addKeyItem("item/poison_meat", self->x + self->w / 2, self->y);
330
331 e->x -= e->w / 2;
332
333 e->action = &generalItemAction;
334
335 e->flags |= DO_NOT_PERSIST;
336 }
337
338 entityDie();
339 }
340
changeArmour()341 static void changeArmour()
342 {
343 int r;
344 Entity *e = getFreeEntity();
345
346 if (e == NULL)
347 {
348 showErrorAndExit("No free slots to add a Weapon Box");
349 }
350
351 loadProperties("boss/grimlore_weapon_box", e);
352
353 r = hasLightningSword() == TRUE ? 6 : 5;
354
355 switch (prand() % r)
356 {
357 case 0:
358 STRNCPY(self->requires, "weapon/pickaxe", sizeof(self->requires));
359 setEntityAnimationByID(e, 0);
360 break;
361
362 case 1:
363 STRNCPY(self->requires, "weapon/wood_axe", sizeof(self->requires));
364 setEntityAnimationByID(e, 1);
365 break;
366
367 case 2:
368 STRNCPY(self->requires, "weapon/basic_sword", sizeof(self->requires));
369 setEntityAnimationByID(e, 2);
370 break;
371
372 case 3:
373 STRNCPY(self->requires, "weapon/normal_arrow", sizeof(self->requires));
374 setEntityAnimationByID(e, 3);
375 break;
376
377 case 4:
378 STRNCPY(self->requires, "weapon/flaming_arrow", sizeof(self->requires));
379 setEntityAnimationByID(e, 4);
380 break;
381
382 default:
383 STRNCPY(self->requires, "weapon/lightning_sword", sizeof(self->requires));
384 setEntityAnimationByID(e, 5);
385 break;
386 }
387
388 e->x = self->x + self->w / 2 - e-> w /2;
389 e->y = self->y - e->h - 16;
390
391 e->action = &boxWait;
392
393 e->creditsAction = &boxWait;
394
395 e->thinkTime = 60;
396
397 e->draw = &drawLoopingAnimationToMap;
398
399 e->head = self;
400
401 self->thinkTime = 600;
402
403 self->targetX = 60;
404
405 self->targetY = self->targetX;
406 }
407
takeDamage(Entity * other,int damage)408 static void takeDamage(Entity *other, int damage)
409 {
410 Entity *temp;
411
412 if (self->flags & INVULNERABLE)
413 {
414 return;
415 }
416
417 if (strcmpignorecase(other->name, self->requires) != 0)
418 {
419 playSoundToMap("sound/common/dink", -1, self->x, self->y, 0);
420
421 if (other->reactToBlock != NULL)
422 {
423 temp = self;
424
425 self = other;
426
427 self->reactToBlock(temp);
428
429 self = temp;
430 }
431
432 if (prand() % 10 == 0)
433 {
434 setInfoBoxMessage(60, 255, 255, 255, _("This weapon is not having any effect..."));
435 }
436
437 damage = 0;
438
439 setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
440 }
441
442 else
443 {
444 /* Always take 1 hit point damage */
445
446 entityTakeDamageNoFlinch(other, 1);
447 }
448 }
449
boxWait()450 static void boxWait()
451 {
452 self->x = self->head->x + self->head->w / 2 - self-> w / 2;
453 self->y = self->head->y - self->h - 16;
454
455 self->thinkTime--;
456
457 if (self->thinkTime <= 0 || self->head->health <= 0)
458 {
459 self->inUse = FALSE;
460 }
461 }
462
draw()463 static int draw()
464 {
465 int currentFrame, drawn;
466 float frameTimer;
467 char animationName[MAX_VALUE_LENGTH];
468
469 drawn = drawLoopingAnimationToMap();
470
471 if (self->targetX > 0)
472 {
473 self->targetX--;
474
475 STRNCPY(animationName, self->animationName, MAX_VALUE_LENGTH);
476
477 currentFrame = self->currentFrame;
478
479 frameTimer = self->frameTimer;
480
481 setEntityAnimation(self, strstr(animationName, "STAND") != NULL ? "GLOW_STAND" : "GLOW_WALK");
482
483 self->currentFrame = currentFrame;
484
485 self->alpha = (255 * self->targetX) / self->targetY;
486
487 drawSpriteToMap();
488
489 self->alpha = 255;
490
491 setEntityAnimation(self, animationName);
492
493 self->currentFrame = currentFrame;
494
495 self->frameTimer = frameTimer;
496 }
497
498 return drawn;
499 }
500
creditsMove()501 static void creditsMove()
502 {
503 self->thinkTime--;
504
505 if (self->thinkTime <= 0)
506 {
507 changeArmour();
508
509 self->thinkTime = 120;
510 }
511
512 switch (self->mental)
513 {
514 case 2:
515 setEntityAnimation(self, "WALK_2");
516 break;
517
518 case 3:
519 setEntityAnimation(self, "WALK_3");
520 break;
521
522 default:
523 setEntityAnimation(self, "WALK_1");
524 break;
525 }
526
527 self->dirX = self->speed;
528
529 checkToMap(self);
530
531 if (self->dirX == 0)
532 {
533 self->inUse = FALSE;
534 }
535 }
536