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