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