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 "../enemy/enemies.h"
25 #include "../entity.h"
26 #include "../game.h"
27 #include "../geometry.h"
28 #include "../graphics/animation.h"
29 #include "../hud.h"
30 #include "../system/error.h"
31 #include "../system/properties.h"
32 #include "../system/random.h"
33
34 static void init(void);
35 static void lookForPlayer(void);
36 static void followPlayer(void);
37 static void closedEyeMove(void);
38 static void summonEnemies(void);
39 static void teleportPlayer(void);
40 static void creditsMove(void);
41 static void blueCreditsMove(void);
42
43 extern Entity *self, player;
44
addScanner(int x,int y,char * name)45 Entity *addScanner(int x, int y, char *name)
46 {
47 Entity *e = getFreeEntity();
48
49 if (e == NULL)
50 {
51 showErrorAndExit("No free slots to add a Scanner");
52 }
53
54 loadProperties(name, e);
55
56 e->x = x;
57 e->y = y;
58
59 e->action = &init;
60 e->draw = &drawLoopingAnimationToMap;
61
62 e->creditsAction = strcmpignorecase("enemy/blue_scanner", e->name) == 0 ? &blueCreditsMove : &creditsMove;
63
64 e->type = ENEMY;
65
66 setEntityAnimation(e, "STAND");
67
68 return e;
69 }
70
init()71 static void init()
72 {
73 self->endY = getMapFloor(self->x, self->y) - self->y;
74
75 self->action = strcmpignorecase("enemy/blue_scanner", self->name) == 0 ? &closedEyeMove : &lookForPlayer;
76 }
77
lookForPlayer()78 static void lookForPlayer()
79 {
80 int frame;
81 float timer;
82
83 if (self->active == TRUE)
84 {
85 self->flags &= ~NO_DRAW;
86
87 moveLeftToRight();
88
89 if (self->currentFrame == 3)
90 {
91 if (self->health == 0)
92 {
93 playSoundToMap("sound/enemy/gazer/flap", -1, self->x, self->y, 0);
94
95 self->health = 1;
96 }
97 }
98
99 else
100 {
101 self->health = 0;
102 }
103
104 if (player.health > 0 && player.alpha == 255 && collision(self->x + self->w / 2 - 10, self->y, 20, self->endY, player.x, player.y, player.w, player.h) == 1)
105 {
106 playSoundToMap("sound/enemy/gazer/growl", -1, self->x, self->y, 0);
107
108 setInfoBoxMessage(120, 255, 255, 255, _("INTRUDER!"));
109
110 self->thinkTime = 300;
111
112 activateEntitiesWithRequiredName(self->objectiveName, FALSE);
113
114 if (self->mental == 1)
115 {
116 summonEnemies();
117 }
118
119 frame = self->currentFrame;
120 timer = self->frameTimer;
121
122 setEntityAnimation(self, "ATTACK_1");
123
124 self->currentFrame = frame;
125 self->frameTimer = timer;
126
127 self->target = &player;
128
129 self->action = &followPlayer;
130 }
131 }
132
133 else
134 {
135 self->flags |= NO_DRAW;
136 }
137 }
138
closedEyeMove()139 static void closedEyeMove()
140 {
141 int frame;
142 float timer;
143
144 moveLeftToRight();
145
146 if (self->currentFrame == 3)
147 {
148 if (self->health == 0)
149 {
150 playSoundToMap("sound/enemy/gazer/flap", -1, self->x, self->y, 0);
151
152 self->health = 1;
153 }
154 }
155
156 else
157 {
158 self->health = 0;
159 }
160
161 self->thinkTime--;
162
163 if (self->thinkTime <= 0)
164 {
165 self->mental = 1 - self->mental;
166
167 frame = self->currentFrame;
168 timer = self->frameTimer;
169
170 setEntityAnimation(self, self->mental == 0 ? "STAND" : "WALK");
171
172 self->currentFrame = frame;
173 self->frameTimer = timer;
174
175 self->thinkTime = self->maxThinkTime;
176 }
177
178 if (player.health > 0 && self->mental == 0 && collision(self->x + self->w / 2 - 10, self->y, 20, self->endY, player.x, player.y, player.w, player.h) == 1)
179 {
180 playSoundToMap("sound/enemy/gazer/growl", -1, self->x, self->y, 0);
181
182 setInfoBoxMessage(120, 255, 255, 255, _("INTRUDER!"));
183
184 self->thinkTime = 300;
185
186 activateEntitiesWithRequiredName(self->objectiveName, FALSE);
187
188 if (self->damage == 1)
189 {
190 summonEnemies();
191 }
192
193 frame = self->currentFrame;
194 timer = self->frameTimer;
195
196 setEntityAnimation(self, "ATTACK_1");
197
198 self->currentFrame = frame;
199 self->frameTimer = timer;
200
201 self->target = &player;
202
203 if (self->damage == 2)
204 {
205 self->thinkTime = 120;
206
207 self->action = &teleportPlayer;
208 }
209
210 else
211 {
212 self->action = &followPlayer;
213 }
214 }
215 }
216
teleportPlayer()217 static void teleportPlayer()
218 {
219 int frame;
220 float timer, x, y;
221
222 if (self->target != NULL)
223 {
224 getCheckpoint(&x, &y);
225
226 self->target->targetX = x;
227 self->target->targetY = y;
228
229 calculatePath(self->target->x, self->target->y, self->target->targetX, self->target->targetY, &self->target->dirX, &self->target->dirY);
230
231 self->target->flags |= (NO_DRAW|HELPLESS|TELEPORTING);
232
233 playSoundToMap("sound/common/teleport", (self->target->type == PLAYER ? EDGAR_CHANNEL : -1), self->target->x, self->target->y, 0);
234
235 self->target = NULL;
236 }
237
238 self->thinkTime--;
239
240 if (self->thinkTime <= 0)
241 {
242 frame = self->currentFrame;
243 timer = self->frameTimer;
244
245 setEntityAnimation(self, "STAND");
246
247 self->currentFrame = frame;
248 self->frameTimer = timer;
249
250 self->action = strcmpignorecase("enemy/blue_scanner", self->name) == 0 ? &closedEyeMove : &lookForPlayer;
251 }
252 }
253
followPlayer()254 static void followPlayer()
255 {
256 int frame;
257 float timer;
258
259 self->targetX = self->target->x - self->w / 2 + self->target->w / 2;
260
261 if (self->speed != 0)
262 {
263 /* Position under the player */
264
265 if (abs(self->x - self->targetX) <= self->speed * 3)
266 {
267 self->dirX = 0;
268 }
269
270 else
271 {
272 self->dirX = self->targetX < self->x ? -self->target->speed * 3 : self->target->speed * 3;
273 }
274 }
275
276 checkToMap(self);
277
278 if (player.health <= 0 || collision(self->x, self->y, self->w, self->endY, player.x, player.y, player.w, player.h) == 0)
279 {
280 self->thinkTime--;
281
282 if (self->thinkTime <= 0)
283 {
284 activateEntitiesWithRequiredName(self->objectiveName, TRUE);
285
286 frame = self->currentFrame;
287 timer = self->frameTimer;
288
289 setEntityAnimation(self, "STAND");
290
291 self->currentFrame = frame;
292 self->frameTimer = timer;
293
294 self->action = strcmpignorecase("enemy/blue_scanner", self->name) == 0 ? &closedEyeMove : &lookForPlayer;
295 }
296 }
297 }
298
summonEnemies()299 static void summonEnemies()
300 {
301 char summonList[MAX_VALUE_LENGTH], enemyToSummon[MAX_VALUE_LENGTH];
302 char *token;
303 int i, summonIndex = 0, summonCount = 0;
304 Entity *e;
305
306 for (i=0;i<2;i++)
307 {
308 summonCount = 0;
309
310 summonIndex = 0;
311
312 STRNCPY(summonList, self->requires, MAX_VALUE_LENGTH);
313
314 token = strtok(summonList, "|");
315
316 while (token != NULL)
317 {
318 token = strtok(NULL, "|");
319
320 summonCount++;
321 }
322
323 if (summonCount == 0)
324 {
325 showErrorAndExit("Scanner at %f %f has no summon list", self->x, self->y);
326 }
327
328 summonIndex = prand() % summonCount;
329
330 STRNCPY(summonList, self->requires, MAX_VALUE_LENGTH);
331
332 summonCount = 0;
333
334 token = strtok(summonList, "|");
335
336 while (token != NULL)
337 {
338 if (summonCount == summonIndex)
339 {
340 break;
341 }
342
343 token = strtok(NULL, "|");
344
345 summonCount++;
346 }
347
348 SNPRINTF(enemyToSummon, MAX_VALUE_LENGTH, "enemy/%s", token);
349
350 e = addEnemy(enemyToSummon, self->x, self->y);
351
352 e->x = self->x;
353
354 e->y = self->y;
355
356 e->targetX = self->x + (i == 0 ? -64 : 64);
357
358 e->targetY = self->y;
359
360 calculatePath(e->x, e->y, e->targetX, e->targetY, &e->dirX, &e->dirY);
361
362 e->flags |= (NO_DRAW|HELPLESS|TELEPORTING);
363 }
364 }
365
creditsMove()366 static void creditsMove()
367 {
368 setEntityAnimation(self, "STAND");
369
370 self->dirX = self->speed;
371
372 checkToMap(self);
373
374 if (self->dirX == 0)
375 {
376 self->inUse = FALSE;
377 }
378 }
379
blueCreditsMove()380 static void blueCreditsMove()
381 {
382 setEntityAnimation(self, "ATTACK_1");
383
384 self->dirX = self->speed;
385
386 checkToMap(self);
387
388 if (self->dirX == 0)
389 {
390 self->inUse = FALSE;
391 }
392 }
393