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 "../medal.h"
28 #include "../system/error.h"
29 #include "../system/properties.h"
30 
31 extern Entity *self, player;
32 extern Game game;
33 
34 static void init(void);
35 static void entityWait(void);
36 static void addTongue(void);
37 static void tongueWait(void);
38 static void tongueTouch(Entity *);
39 static void moveToMouth(void);
40 static void tongueTakeDamage(Entity *, int);
41 static void tongueRetreat(void);
42 static void tongueExtendOut(void);
43 static int drawTongue(void);
44 static void creeperTouch(Entity *);
45 static void moveToMouthFinish(void);
46 static void creditsAction(void);
47 static void creditsMove(void);
48 
addCeilingCreeper(int x,int y,char * name)49 Entity *addCeilingCreeper(int x, int y, char *name)
50 {
51 	Entity *e = getFreeEntity();
52 
53 	if (e == NULL)
54 	{
55 		showErrorAndExit("No free slots to add a Ceiling Creeper");
56 	}
57 
58 	loadProperties(name, e);
59 
60 	e->x = x;
61 	e->y = y;
62 
63 	e->action = &init;
64 	e->draw = &drawLoopingAnimationToMap;
65 	e->touch = &entityTouch;
66 
67 	e->creditsAction = &creditsAction;
68 
69 	e->type = ENEMY;
70 
71 	setEntityAnimation(e, "STAND");
72 
73 	return e;
74 }
75 
init()76 static void init()
77 {
78 	addTongue();
79 
80 	self->action = &entityWait;
81 }
82 
addTongue()83 static void addTongue()
84 {
85 	Entity *e = getFreeEntity();
86 
87 	if (e == NULL)
88 	{
89 		showErrorAndExit("No free slots to add a Ceiling Creeper Tongue");
90 	}
91 
92 	loadProperties("enemy/ceiling_creeper_tongue", e);
93 
94 	e->action = &tongueWait;
95 	e->draw = &drawTongue;
96 	e->touch = &tongueTouch;
97 	e->takeDamage = &tongueTakeDamage;
98 	e->pain = &enemyPain;
99 
100 	e->type = ENEMY;
101 
102 	e->head = self;
103 
104 	setEntityAnimation(e, "STAND");
105 
106 	e->x = self->x + self->w / 2 - e->w / 2;
107 
108 	e->y = self->y + self->h / 2 - e->h / 2;
109 
110 	e->startY = e->y;
111 	e->endY = self->endY;
112 }
113 
entityWait()114 static void entityWait()
115 {
116 	int height;
117 
118 	if (self->mental == 0 && self->startX != self->endX)
119 	{
120 		if (self->face == LEFT)
121 		{
122 			if (self->x <= self->startX)
123 			{
124 				self->x = self->startX;
125 
126 				self->face = RIGHT;
127 			}
128 		}
129 
130 		else
131 		{
132 			if (self->x + self->w >= self->endX)
133 			{
134 				self->x = self->endX - self->w;
135 
136 				self->face = LEFT;
137 			}
138 		}
139 
140 		self->dirX = self->face == LEFT ? -self->speed : self->speed;
141 	}
142 
143 	height = self->endY - self->startY;
144 
145 	if (player.health > 0 && self->mental == 0 && !(player.flags & FLY))
146 	{
147 		if (self->startX == self->endX &&
148 			collision(self->x, self->y, self->w, height, player.x ,player.y, player.w, player.h) == 1)
149 		{
150 			self->dirX = 0;
151 
152 			self->mental = 1;
153 		}
154 
155 		else if (self->startX != self->endX &&
156 			collision(self->x - 4 + self->w / 2, self->y, 8, height, player.x ,player.y, player.w, player.h) == 1)
157 		{
158 			self->dirX = 0;
159 
160 			self->mental = 1;
161 		}
162 	}
163 
164 	else if (self->mental == 2)
165 	{
166 		self->thinkTime--;
167 
168 		if (self->thinkTime <= 0)
169 		{
170 			self->mental = 0;
171 		}
172 	}
173 
174 	checkToMap(self);
175 }
176 
tongueWait()177 static void tongueWait()
178 {
179 	if (self->head->mental == 1)
180 	{
181 		self->health = self->maxHealth;
182 
183 		self->action = &tongueExtendOut;
184 
185 		self->touch = &tongueTouch;
186 
187 		playSoundToMap("sound/boss/armour_boss/tongue_start", -1, self->x, self->y, 0);
188 	}
189 
190 	self->x = self->head->x + self->head->w / 2 - self->w / 2;
191 }
192 
tongueTouch(Entity * other)193 static void tongueTouch(Entity *other)
194 {
195 	int y;
196 
197 	if (self->target == NULL && other->type == PLAYER && other->health > 0)
198 	{
199 		self->head->face = other->face;
200 
201 		self->head->touch = &creeperTouch;
202 
203 		self->target = other;
204 
205 		self->target->flags |= FLY;
206 
207 		self->thinkTime = 180;
208 
209 		self->action = &moveToMouth;
210 
211 		y = other->y + other->h - self->h;
212 
213 		if (y < self->endY)
214 		{
215 			self->y = y;
216 		}
217 	}
218 
219 	else
220 	{
221 		entityTouch(other);
222 	}
223 }
224 
moveToMouth()225 static void moveToMouth()
226 {
227 	self->thinkTime--;
228 
229 	self->y -= self->y - self->startY > 96 ? 1 : self->speed;
230 
231 	if (self->y <= self->startY)
232 	{
233 		if (self->target != NULL)
234 		{
235 			self->target->flags &= ~FLY;
236 		}
237 
238 		self->target = NULL;
239 
240 		self->y = self->startY;
241 
242 		self->action = &moveToMouthFinish;
243 
244 		self->thinkTime = 180;
245 	}
246 
247 	if (self->target != NULL)
248 	{
249 		self->target->x = self->x + self->w / 2 - self->target->w / 2;
250 		self->target->y = self->y + self->h / 2 - self->target->h / 2;
251 
252 		self->target->dirY = 0;
253 	}
254 }
255 
moveToMouthFinish()256 static void moveToMouthFinish()
257 {
258 	self->thinkTime--;
259 
260 	if (self->thinkTime <= 0)
261 	{
262 		self->action = &tongueWait;
263 	}
264 }
265 
tongueTakeDamage(Entity * other,int damage)266 static void tongueTakeDamage(Entity *other, int damage)
267 {
268 	Entity *temp;
269 
270 	if (self->flags & INVULNERABLE)
271 	{
272 		return;
273 	}
274 
275 	if (damage != 0)
276 	{
277 		self->health -= damage;
278 
279 		if (other->type == PROJECTILE)
280 		{
281 			temp = self;
282 
283 			self = other;
284 
285 			self->die();
286 
287 			self = temp;
288 		}
289 
290 		if (self->health > 0)
291 		{
292 			setCustomAction(self, &flashWhite, 6, 0, 0);
293 
294 			/* Don't make an enemy invulnerable from a projectile hit, allows multiple hits */
295 
296 			if (other->type != PROJECTILE)
297 			{
298 				setCustomAction(self, &invulnerableNoFlash, HIT_INVULNERABLE_TIME, 0, 0);
299 			}
300 
301 			if (self->pain != NULL)
302 			{
303 				self->pain();
304 			}
305 		}
306 
307 		else
308 		{
309 			self->touch = NULL;
310 
311 			if (self->target != NULL)
312 			{
313 				self->target->flags &= ~FLY;
314 			}
315 
316 			self->target = NULL;
317 
318 			self->action = &tongueRetreat;
319 
320 			self->thinkTime = 180;
321 		}
322 	}
323 }
324 
tongueRetreat()325 static void tongueRetreat()
326 {
327 	self->y -= self->speed;
328 
329 	if (self->y <= self->startY)
330 	{
331 		self->y = self->startY;
332 
333 		self->thinkTime--;
334 
335 		if (self->thinkTime <= 0)
336 		{
337 			self->head->mental = 0;
338 
339 			self->action = &tongueWait;
340 		}
341 	}
342 }
343 
tongueExtendOut()344 static void tongueExtendOut()
345 {
346 	self->y += self->speed;
347 
348 	if (self->y >= self->endY)
349 	{
350 		self->y = self->endY;
351 	}
352 
353 	self->box.h = self->endY - self->startY;
354 	self->box.y = -self->box.h;
355 }
356 
drawTongue()357 static int drawTongue()
358 {
359 	float y;
360 
361 	y = self->y;
362 
363 	setEntityAnimation(self, "WALK");
364 
365 	while (self->y >= self->startY)
366 	{
367 		drawSpriteToMap();
368 
369 		self->y -= self->h;
370 	}
371 
372 	setEntityAnimation(self, "STAND");
373 
374 	self->y = y;
375 
376 	drawLoopingAnimationToMap();
377 
378 	return TRUE;
379 }
380 
creeperTouch(Entity * other)381 static void creeperTouch(Entity *other)
382 {
383 	if (self->mental == 1 && other->type == PLAYER)
384 	{
385 		other->flags |= NO_DRAW;
386 
387 		other->fallout();
388 
389 		self->mental = 2;
390 
391 		self->thinkTime = 300;
392 
393 		self->touch = &entityTouch;
394 
395 		playSoundToMap("sound/enemy/whirlwind/suck", -1, self->x, self->y, 0);
396 
397 		game.timesEaten++;
398 
399 		if (game.timesEaten == 5)
400 		{
401 			addMedal("eaten_5");
402 		}
403 	}
404 
405 	else
406 	{
407 		entityTouch(other);
408 	}
409 }
410 
creditsAction()411 static void creditsAction()
412 {
413 	self->dirY -= GRAVITY_SPEED * self->weight;
414 
415 	checkToMap(self);
416 
417 	if (self->dirY == 0)
418 	{
419 		self->creditsAction = &creditsMove;
420 	}
421 }
422 
creditsMove()423 static void creditsMove()
424 {
425 	self->face = RIGHT;
426 
427 	setEntityAnimation(self, "STAND");
428 
429 	self->dirX = self->speed;
430 
431 	checkToMap(self);
432 
433 	if (self->dirX == 0)
434 	{
435 		self->inUse = FALSE;
436 	}
437 }
438