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