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 "../system/error.h"
29 #include "../system/properties.h"
30 #include "../system/random.h"
31 
32 extern Entity *self, player;
33 extern Game game;
34 
35 static void stickToTarget(void);
36 static void attack(void);
37 static void grab(Entity *other);
38 static void fallOff(void);
39 static void findPrey(void);
40 static void stickToTargetAndDrain(void);
41 static void fallOffWait(void);
42 static void creditsMove(void);
43 
addBabySlime(int x,int y,char * name)44 Entity *addBabySlime(int x, int y, char *name)
45 {
46 	Entity *e = getFreeEntity();
47 
48 	if (e == NULL)
49 	{
50 		showErrorAndExit("No free slots to add a Baby Slime");
51 	}
52 
53 	loadProperties(name, e);
54 
55 	e->x = x;
56 	e->y = y;
57 
58 	e->action = &attack;
59 	e->draw = &drawLoopingAnimationToMap;
60 	e->die = &entityDieNoDrop;
61 	e->pain = NULL;
62 	e->reactToBlock = NULL;
63 	e->touch = &grab;
64 
65 	e->creditsAction = &creditsMove;
66 
67 	if (strcmpignorecase(name, "enemy/purple_baby_slime") == 0)
68 	{
69 		e->takeDamage = &entityTakeDamageNoFlinch;
70 	}
71 
72 	else
73 	{
74 		e->takeDamage = &entityTakeDamageFlinch;
75 	}
76 
77 	e->type = ENEMY;
78 
79 	setEntityAnimation(e, "STAND");
80 
81 	return e;
82 }
83 
attack()84 static void attack()
85 {
86 	int channel;
87 	long onGround = (self->flags & ON_GROUND);
88 
89 	if (self->target == NULL || (self->target->type != PLAYER && self->target->health <= 0))
90 	{
91 		findPrey();
92 	}
93 
94 	faceTarget();
95 
96 	if ((self->flags & ON_GROUND) && (prand() % 30 == 0))
97 	{
98 		channel = 3 + (prand() % 3);
99 
100 		if (prand() % 3 == 0)
101 		{
102 			playSoundToMap("sound/enemy/jumping_slime/baby_jump2", channel, self->x, self->y, 0);
103 		}
104 
105 		else
106 		{
107 			playSoundToMap("sound/enemy/jumping_slime/baby_jump1", channel, self->x, self->y, 0);
108 		}
109 
110 		self->dirX = (self->face == LEFT ? -self->speed : self->speed);
111 
112 		self->dirY = -(8 + prand() % 4);
113 	}
114 
115 	checkToMap(self);
116 
117 	if (onGround == 0 && ((self->flags & ON_GROUND) || (self->standingOn != NULL)))
118 	{
119 		self->dirX = 0;
120 	}
121 
122 	if (self->mental == 0)
123 	{
124 		self->thinkTime--;
125 	}
126 
127 	if (self->thinkTime <= 0)
128 	{
129 		self->touch = NULL;
130 
131 		self->die();
132 	}
133 }
134 
grab(Entity * other)135 static void grab(Entity *other)
136 {
137 	if (self->health <= 0)
138 	{
139 		return;
140 	}
141 
142 	if (other->type == WEAPON && (other->flags & ATTACKING))
143 	{
144 		if (self->takeDamage != NULL && !(self->flags & INVULNERABLE))
145 		{
146 			self->takeDamage(other, other->damage);
147 		}
148 	}
149 
150 	else if (other->type == PROJECTILE && other->parent != self)
151 	{
152 		if (self->takeDamage != NULL && !(self->flags & INVULNERABLE))
153 		{
154 			self->takeDamage(other, other->damage);
155 		}
156 
157 		other->inUse = FALSE;
158 	}
159 
160 	else if (self->target == other && !(self->flags & GRABBING))
161 	{
162 		self->startX = (prand() % (other->w / 2)) * (prand() % 2 == 0 ? 1 : -1);
163 
164 		self->startY = prand() % (other->h - self->h);
165 
166 		setCustomAction(other, &slowDown, 3, 1, 0);
167 
168 		if (strcmpignorecase(self->name, "enemy/baby_slime") == 0)
169 		{
170 			self->action = &stickToTarget;
171 		}
172 
173 		else
174 		{
175 			self->action = &stickToTargetAndDrain;
176 		}
177 
178 		self->touch = NULL;
179 
180 		self->flags |= GRABBING;
181 
182 		self->layer = FOREGROUND_LAYER;
183 
184 		if (other->type == PLAYER)
185 		{
186 			other->flags |= GRABBED;
187 		}
188 
189 		self->thinkTime = 0;
190 
191 		self->mental = 3 + (prand() % 3);
192 	}
193 }
194 
stickToTarget()195 static void stickToTarget()
196 {
197 	setCustomAction(self->target, &slowDown, 3, 0, 0);
198 
199 	if (self->target->type == PLAYER && self->target->health > 0)
200 	{
201 		setInfoBoxMessage(0, 255, 255, 255, _("Quickly turn left and right to shake off the slimes!"));
202 	}
203 
204 	self->x = self->target->x + (self->target->w - self->w) / 2 + self->startX;
205 	self->y = self->target->y + self->startY;
206 
207 	self->thinkTime++;
208 
209 	if (self->face != self->target->face)
210 	{
211 		self->face = self->target->face;
212 
213 		if (self->thinkTime <= 15)
214 		{
215 			self->mental--;
216 		}
217 
218 		self->thinkTime = 0;
219 	}
220 
221 	if (self->mental <= 0)
222 	{
223 		self->dirX = self->speed * 2 * (prand() % 2 == 0 ? -1 : 1);
224 
225 		self->dirY = -6;
226 
227 		setCustomAction(self->target, &slowDown, 3, -1, 0);
228 
229 		self->action = &fallOff;
230 
231 		self->target->flags &= ~GRABBED;
232 	}
233 }
234 
stickToTargetAndDrain()235 static void stickToTargetAndDrain()
236 {
237 	Entity *temp;
238 
239 	if (self->target->type == PLAYER && self->target->health > 0)
240 	{
241 		setInfoBoxMessage(0, 255, 255, 255, _("Quickly turn left and right to shake off the slimes!"));
242 
243 		setCustomAction(self->target, &slowDown, 3, 0, 0);
244 	}
245 
246 	/* Fall off immediately if boss has armour */
247 
248 	else if (self->target->mental != 0)
249 	{
250 		if (prand() % 10 == 0)
251 		{
252 			setInfoBoxMessage(120, 255, 255, 255, _("The armour is too tough for them to penetrate..."));
253 		}
254 
255 		self->mental = 0;
256 	}
257 
258 	else if (self->target->maxThinkTime == 99)
259 	{
260 		self->mental = 0;
261 	}
262 
263 	self->x = self->target->x + (self->target->w - self->w) / 2 + self->startX;
264 	self->y = self->target->y + self->startY;
265 
266 	if (self->target->health > 0)
267 	{
268 		self->thinkTime++;
269 
270 		if (self->face != self->target->face)
271 		{
272 			self->face = self->target->face;
273 
274 			if (self->thinkTime <= 15)
275 			{
276 				self->mental--;
277 			}
278 
279 			self->thinkTime = 0;
280 		}
281 
282 		if (self->thinkTime >= 60)
283 		{
284 			temp = self;
285 
286 			self = self->target;
287 
288 			self->takeDamage(temp, 1);
289 
290 			self = temp;
291 
292 			self->thinkTime = self->target == PLAYER ? 0 : 45;
293 		}
294 	}
295 
296 	else
297 	{
298 		self->mental = 180 + (prand() % 420);
299 
300 		self->action = &fallOffWait;
301 	}
302 
303 	if (self->mental <= 0)
304 	{
305 		self->dirX = self->speed * 2 * (prand() % 2 == 0 ? -1 : 1);
306 
307 		self->dirY = -6;
308 
309 		if (self->target->type == PLAYER)
310 		{
311 			setCustomAction(self->target, &slowDown, 3, -1, 0);
312 		}
313 
314 		self->action = &fallOff;
315 
316 		self->target->flags &= ~GRABBED;
317 	}
318 }
319 
fallOffWait()320 static void fallOffWait()
321 {
322 	setCustomAction(self->target, &slowDown, 3, 0, 0);
323 
324 	self->mental--;
325 
326 	self->x = self->target->x + (self->target->w - self->w) / 2 + self->startX;
327 	self->y = self->target->y + self->startY;
328 
329 	if (self->mental <= 0)
330 	{
331 		self->dirX = self->speed * 2 * (prand() % 2 == 0 ? -1 : 1);
332 
333 		self->dirY = -6;
334 
335 		setCustomAction(self->target, &slowDown, 3, -1, 0);
336 
337 		self->action = &fallOff;
338 
339 		self->target->flags &= ~GRABBED;
340 	}
341 }
342 
fallOff()343 static void fallOff()
344 {
345 	checkToMap(self);
346 
347 	if (self->flags & ON_GROUND)
348 	{
349 		self->die();
350 	}
351 }
352 
findPrey()353 static void findPrey()
354 {
355 	Entity *e = getEntityByObjectiveName("ARMOUR_BOSS");
356 
357 	self->target = (e == NULL ? &player : e);
358 }
359 
creditsMove()360 static void creditsMove()
361 {
362 	int channel;
363 	float dirX;
364 	long onGround = (self->flags & ON_GROUND);
365 
366 	if (self->flags & ON_GROUND)
367 	{
368 		channel = 3 + (prand() % 3);
369 
370 		if (prand() % 3 == 0)
371 		{
372 			playSoundToMap("sound/enemy/jumping_slime/baby_jump2", channel, self->x, self->y, 0);
373 		}
374 
375 		else
376 		{
377 			playSoundToMap("sound/enemy/jumping_slime/baby_jump1", channel, self->x, self->y, 0);
378 		}
379 
380 		self->dirX = self->speed;
381 
382 		self->dirY = -(8 + prand() % 4);
383 	}
384 
385 	dirX = self->dirX;
386 
387 	checkToMap(self);
388 
389 	if (self->dirX == 0 && dirX != 0)
390 	{
391 		self->inUse = FALSE;
392 	}
393 
394 	if (onGround == 0 && ((self->flags & ON_GROUND) || (self->standingOn != NULL)))
395 	{
396 		self->dirX = 0;
397 	}
398 }
399