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