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