1 /*
2 Copyright (C) 2003 Parallel Realities
3 Copyright (C) 2011, 2012, 2013 Guus Sliepen
4 Copyright (C) 2012, 2014-2020 The Diligent Circle <diligentcircle@riseup.net>
5 
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 3
9 of the License, or (at your option) any later version.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include <libintl.h>
21 #include <math.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 
25 #include "SDL.h"
26 
27 #include "colors.h"
28 #include "defs.h"
29 #include "structs.h"
30 
31 #include "alien.h"
32 #include "audio.h"
33 #include "bullet.h"
34 #include "cargo.h"
35 #include "collectable.h"
36 #include "cutscene.h"
37 #include "engine.h"
38 #include "event.h"
39 #include "explosion.h"
40 #include "game.h"
41 #include "gfx.h"
42 #include "info.h"
43 #include "intermission.h"
44 #include "mission.h"
45 #include "player.h"
46 #include "radio.h"
47 #include "renderer.h"
48 #include "save.h"
49 #include "screen.h"
50 #include "ship.h"
51 #include "title.h"
52 #include "weapons.h"
53 
54 typedef struct Star_ {
55 
56 	float x, y, dx, dy;
57 	int speed; // How fast the star moves
58 
59 } Star;
60 
61 Game game;
62 char game_systemNames[SYSTEM_MAX][STRMAX_SHORT];
63 
64 static Star stars[STARS_NUM];
65 static Uint32 frameLimit = 0;
66 static int thirds = 0;
67 
68 
game_init()69 void game_init()
70 {
71 	strcpy(game_systemNames[SYSTEM_SPIRIT], _("Spirit"));
72 	strcpy(game_systemNames[SYSTEM_EYANANTH], _("Eyananth"));
73 	strcpy(game_systemNames[SYSTEM_MORDOR], _("Mordor"));
74 	strcpy(game_systemNames[SYSTEM_SOL], _("Sol"));
75 
76 	game.system = SYSTEM_SPIRIT;
77 	game.area = MISN_START;
78 	game.sfxVolume = 0;
79 	game.musicVolume = 0;
80 
81 	if (!engine.useAudio)
82 	{
83 		engine.useSound = 0;
84 		engine.useMusic = 0;
85 	}
86 
87 	game.cash = 0;
88 	game.cashEarned = 0;
89 	game.shots = 0;
90 	game.hits = 0;
91 	game.accuracy = 0;
92 	game.totalKills = game.wingMate1Kills = game.wingMate2Kills = 0;
93 	game.totalOtherKills = 0;
94 	game.hasWingMate1 = game.hasWingMate2 = 0;
95 	game.wingMate1Ejects = game.wingMate2Ejects = 0;
96 	game.secondaryMissions = game.secondaryMissionsCompleted = 0;
97 	game.shieldPickups = game.rocketPickups = game.cellPickups = 0;
98 	game.powerups = game.minesKilled = game.cargoPickups = 0;
99 
100 	game.slavesRescued = 0;
101 	game.experimentalShield = 1000;
102 
103 	game.timeTaken = 0;
104 
105 	game.stationedPlanet = -1;
106 	game.destinationPlanet = -1;
107 	for (int i = 0 ; i < 10 ; i++)
108 		game.missionCompleted[i] = 0;
109 	game.distanceCovered = 0;
110 
111 	game.minPlasmaRate = 1;
112 	game.minPlasmaOutput = 1;
113 	game.minPlasmaDamage = 1;
114 	game.maxPlasmaRate = 2;
115 	game.maxPlasmaOutput = 2;
116 	game.maxPlasmaDamage = 2;
117 	game.maxPlasmaAmmo = 100;
118 	game.maxRocketAmmo = 10;
119 
120 	game.minPlasmaRateLimit = 2;
121 	game.minPlasmaDamageLimit = 2;
122 	game.minPlasmaOutputLimit = 2;
123 	game.maxPlasmaRateLimit = 3;
124 	game.maxPlasmaDamageLimit = 3;
125 	game.maxPlasmaOutputLimit = 3;
126 	game.maxPlasmaAmmoLimit = 250;
127 	game.maxRocketAmmoLimit = 50;
128 
129 	player.maxShield = 50;
130 
131 	switch (game.difficulty)
132 	{
133 		case DIFFICULTY_SUPEREASY:
134 		case DIFFICULTY_EASY:
135 			player.maxShield = 100;
136 
137 			game.minPlasmaRate = 2;
138 			game.minPlasmaOutput = 2;
139 			game.minPlasmaDamage = 2;
140 			game.maxPlasmaRate = 3;
141 			game.maxPlasmaOutput = 3;
142 			game.maxPlasmaDamage = 3;
143 			game.maxPlasmaAmmo = 150;
144 			game.maxRocketAmmo = 20;
145 
146 			game.minPlasmaRateLimit = 3;
147 			game.minPlasmaDamageLimit = 3;
148 			game.minPlasmaOutputLimit = 3;
149 			game.maxPlasmaRateLimit = 5;
150 			game.maxPlasmaDamageLimit = 5;
151 			game.maxPlasmaOutputLimit = 5;
152 			break;
153 		case DIFFICULTY_HARD:
154 			player.maxShield = 25;
155 			break;
156 		case DIFFICULTY_NIGHTMARE:
157 			player.maxShield = 1;
158 			game.maxRocketAmmo = 5;
159 			break;
160 		case DIFFICULTY_ORIGINAL:
161 			player.maxShield = 25;
162 
163 			game.minPlasmaRateLimit = 3;
164 			game.minPlasmaDamageLimit = 3;
165 			game.minPlasmaOutputLimit = 3;
166 			game.maxPlasmaRateLimit = 5;
167 			game.maxPlasmaDamageLimit = 5;
168 			game.maxPlasmaOutputLimit = 5;
169 			break;
170 	}
171 
172 	player.shield = player.maxShield;
173 	player.ammo[0] = 0;
174 	player.ammo[1] = game.maxRocketAmmo / 2;
175 	player.weaponType[0] = W_PLAYER_WEAPON;
176 	player.weaponType[1] = W_ROCKETS;
177 
178 	game_setStars();
179 
180 	weapons_init();
181 	mission_init();
182 	intermission_initPlanets(game.system);
183 }
184 
game_addDebris(int x,int y,int amount)185 static void game_addDebris(int x, int y, int amount)
186 {
187 	if ((rand() % 2) == 0)
188 		audio_playSound(SFX_DEBRIS, x, y);
189 	else
190 		audio_playSound(SFX_DEBRIS2, x, y);
191 
192 	Object *debris;
193 
194 	amount = RANDRANGE(3, amount);
195 	LIMIT(amount, 3, 8);
196 
197 	for (int i = 0 ; i < amount ; i++)
198 	{
199 		debris = malloc(sizeof(*debris));
200 		if (debris == NULL)
201 		{
202 			engine_warn("Failed to allocate memory for debris");
203 			return;
204 		}
205 
206 		debris->next = NULL;
207 		debris->x = x;
208 		debris->y = y;
209 
210 		debris->thinktime = RANDRANGE(60, 180);
211 
212 		debris->dx = RANDRANGE(-500, 500);
213 		debris->dy = RANDRANGE(-500, 500);
214 
215 		if (debris->dx != 0)
216 			debris->dx /= 100;
217 
218 		if (debris->dy != 0)
219 			debris->dy /= 100;
220 
221 		engine.debrisTail->next = debris;
222 		engine.debrisTail = debris;
223 	}
224 }
225 
226 /*
227 Sets star positions. Must do this any time the window size changes.
228 */
game_setStars()229 void game_setStars()
230 {
231 	for (int i = 0 ; i < STARS_NUM ; i++)
232 	{
233 		stars[i].x = rand() % screen->w;
234 		stars[i].y = rand() % screen->h;
235 		stars[i].speed = 1 + (rand() % 3);
236 	}
237 }
238 
239 /*
240 Simply draws the stars in their positions on screen and moves
241 them around.
242 */
game_doStars()243 void game_doStars()
244 {
245 	/* Lock the screen for direct access to the pixels */
246 	if (SDL_MUSTLOCK(screen))
247 	{
248 		if (SDL_LockSurface(screen) < 0)
249 			engine_showError(2, "");
250 	}
251 
252 	int color = 0;
253 
254 	SDL_Rect r;
255 
256 	for (int i = 0 ; i < STARS_NUM ; i++)
257 	{
258 		if (stars[i].speed == 3)
259 			color = white;
260 		else if (stars[i].speed == 2)
261 			color = lightGrey;
262 		else if (stars[i].speed == 1)
263 			color = darkGrey;
264 
265 		WRAP_ADD(stars[i].x, (engine.ssx + engine.smx) * stars[i].speed, 0,
266 			screen->w - 1);
267 		WRAP_ADD(stars[i].y, (engine.ssy + engine.smy) * stars[i].speed, 0,
268 			screen->h - 1);
269 
270 		gfx_putPixel(screen, (int)stars[i].x, (int)stars[i].y, color);
271 		r.x = (int)stars[i].x;
272 		r.y = (int)stars[i].y;
273 		r.w = 1;
274 		r.h = 1;
275 
276 		screen_addBuffer(r.x, r.y, r.w, r.h);
277 	}
278 
279 	if (SDL_MUSTLOCK(screen))
280 	{
281 		SDL_UnlockSurface(screen);
282 	}
283 }
284 
285 /*
286 Loops through the currently active collectables (in a linked list). The collectable
287 will travel in the direction that was defined when it was made. Its life will decreased
288 whilst it remains active. It will be removed if the player touches it or if its life
289 reaches 0. When it is picked up, depending on the type of collectable it is, mission requirements
290 will be updated. Information will be displayed and appropriate player variables altered.
291 */
game_doCollectables()292 static void game_doCollectables()
293 {
294 	Collectable *collectable = engine.collectableHead;
295 	Collectable *prevCollectable = engine.collectableHead;
296 	engine.collectableTail = engine.collectableHead;
297 	char temp[STRMAX_SHORT];
298 
299 	while (collectable->next != NULL)
300 	{
301 		collectable = collectable->next;
302 
303 		if (collectable->active)
304 		{
305 			if ((collectable->x + collectable->image->w > 0)
306 					&& (collectable->x < screen->w)
307 					&& (collectable->y + collectable->image->h > 0)
308 					&& (collectable->y < screen->h))
309 				screen_blit(collectable->image, (int)collectable->x, (int)collectable->y);
310 
311 			collectable->x += engine.ssx + engine.smx;
312 			collectable->y += engine.ssy + engine.smy;
313 			collectable->x += collectable->dx;
314 			collectable->y += collectable->dy;
315 
316 			collectable->life--;
317 
318 			if ((player.shield > 0) && (collectable_collision(collectable, &player)))
319 			{
320 				switch(collectable->type)
321 				{
322 					case P_CASH:
323 						game.cash += collectable->value;
324 						game.cashEarned += collectable->value;
325 						snprintf(temp, STRMAX_SHORT, ngettext(
326 							/// "%d" must be retained. It is replaced with the amount of money that
327 							/// was picked up.
328 							"Got $%d",
329 							"Got $%d",
330 							collectable->value), collectable->value);
331 						break;
332 
333 					case P_ROCKET:
334 						LIMIT_ADD(player.ammo[1], collectable->value, 0,
335 							game.maxRocketAmmo);
336 						if (player.ammo[1] == game.maxRocketAmmo)
337 							strcpy(temp, _("Rocket Ammo at Maximum"));
338 						else
339 						{
340 							snprintf(temp, STRMAX_SHORT, ngettext(
341 								/// "%d" must be retained. It is replaced with the number of rockets
342 								/// picked up.
343 								"Got %d rocket",
344 								"Got %d rockets",
345 								collectable->value), collectable->value);
346 						}
347 						game.rocketPickups += collectable->value;
348 						break;
349 
350 					case P_SHIELD:
351 						LIMIT_ADD(player.shield, 10, 0, player.maxShield);
352 						game.shieldPickups ++;
353 						strcpy(temp, _("Restored 10 shield points"));
354 						break;
355 
356 					case P_PLASMA_RATE:
357 						game.powerups++;
358 						if (game.difficulty == DIFFICULTY_ORIGINAL)
359 						{
360 							player.ammo[0] = MAX(player.ammo[0], 50);
361 							weapons[W_PLAYER_WEAPON].reload[0] = MAX(
362 								rate2reload[game.maxPlasmaRate],
363 								weapons[W_PLAYER_WEAPON].reload[0] - 2);
364 
365 							if (weapons[W_PLAYER_WEAPON].reload[0] <= rate2reload[game.maxPlasmaRate])
366 								strcpy(temp, _("Firing rate already at maximum"));
367 							else
368 							{
369 								weapons[W_PLAYER_WEAPON].reload[0] -= 2;
370 								strcpy(temp, _("Firing rate increased"));
371 							}
372 						}
373 						else if ((game.difficulty == DIFFICULTY_SUPEREASY)
374 							|| (game.area != MISN_INTERCEPTION)
375 							|| (player.ammo[0] > 0))
376 						{
377 							if ((game.difficulty == DIFFICULTY_SUPEREASY)
378 								|| (game.area != MISN_INTERCEPTION))
379 							{
380 								LIMIT_ADD(player.ammo[0], collectable->value,
381 									0, game.maxPlasmaAmmo);
382 							}
383 
384 							if (weapons[W_PLAYER_WEAPON].reload[0] <= rate2reload[game.maxPlasmaRate])
385 								strcpy(temp, _("Firing rate already at maximum"));
386 							else
387 							{
388 								weapons[W_PLAYER_WEAPON].reload[0] -= 2;
389 								strcpy(temp, _("Firing rate increased"));
390 							}
391 						}
392 						else
393 						{
394 							strcpy(temp, _("Upgrade failed (no plasma ammo)"));
395 						}
396 						break;
397 
398 					case P_PLASMA_SHOT:
399 						game.powerups++;
400 						if (game.difficulty == DIFFICULTY_ORIGINAL)
401 						{
402 							player.ammo[0] = MAX(player.ammo[0], 50);
403 							weapons[W_PLAYER_WEAPON].ammo[0] = MIN(
404 								game.maxPlasmaOutput, weapons[W_PLAYER_WEAPON].ammo[0] + 1);
405 
406 							if (weapons[W_PLAYER_WEAPON].ammo[0] >= game.maxPlasmaOutput)
407 								strcpy(temp, _("Plasma output already at maximum"));
408 							else
409 							{
410 								weapons[W_PLAYER_WEAPON].ammo[0]++;
411 								strcpy(temp, _("Plasma output increased"));
412 							}
413 						}
414 						else if ((game.difficulty == DIFFICULTY_SUPEREASY)
415 							|| (game.area != MISN_INTERCEPTION)
416 							|| (player.ammo[0] > 0))
417 						{
418 							if ((game.difficulty == DIFFICULTY_SUPEREASY)
419 								|| (game.area != MISN_INTERCEPTION))
420 							{
421 								LIMIT_ADD(player.ammo[0], collectable->value,
422 									0, game.maxPlasmaAmmo);
423 							}
424 
425 							if (weapons[W_PLAYER_WEAPON].ammo[0] >= game.maxPlasmaOutput)
426 								strcpy(temp, _("Plasma output already at maximum"));
427 							else
428 							{
429 								weapons[W_PLAYER_WEAPON].ammo[0]++;
430 								strcpy(temp, _("Plasma output increased"));
431 							}
432 						}
433 						else
434 						{
435 							strcpy(temp, _("Upgrade failed (no plasma ammo)"));
436 						}
437 						break;
438 
439 					case P_PLASMA_DAMAGE:
440 						game.powerups++;
441 						if (game.difficulty == DIFFICULTY_ORIGINAL)
442 						{
443 							player.ammo[0] = MAX(player.ammo[0], 50);
444 							weapons[W_PLAYER_WEAPON].damage = MIN(
445 								game.maxPlasmaDamage, weapons[W_PLAYER_WEAPON].damage + 1);
446 
447 							if (weapons[W_PLAYER_WEAPON].damage >= game.maxPlasmaDamage)
448 								strcpy(temp, _("Plasma damage already at maximum"));
449 							else
450 							{
451 								weapons[W_PLAYER_WEAPON].damage++;
452 								strcpy(temp, _("Plasma damage increased"));
453 							}
454 						}
455 						else if ((game.difficulty == DIFFICULTY_SUPEREASY)
456 							|| (game.area != MISN_INTERCEPTION)
457 							|| (player.ammo[0] > 0))
458 						{
459 							if ((game.difficulty == DIFFICULTY_SUPEREASY)
460 								|| (game.area != MISN_INTERCEPTION))
461 							{
462 								LIMIT_ADD(player.ammo[0], collectable->value,
463 									0, game.maxPlasmaAmmo);
464 							}
465 
466 							if (weapons[W_PLAYER_WEAPON].damage >= game.maxPlasmaDamage)
467 								strcpy(temp, _("Plasma damage already at maximum"));
468 							else
469 							{
470 								weapons[W_PLAYER_WEAPON].damage++;
471 								strcpy(temp, _("Plasma damage increased"));
472 							}
473 						}
474 						else
475 						{
476 							strcpy(temp, _("Upgrade failed (no plasma ammo)"));
477 						}
478 						break;
479 
480 					case P_SUPER:
481 						game.powerups++;
482 						if ((game.difficulty == DIFFICULTY_ORIGINAL)
483 							|| (game.difficulty == DIFFICULTY_SUPEREASY)
484 							|| (game.area != MISN_INTERCEPTION)
485 							|| (player.ammo[0] > 0))
486 						{
487 							if (game.difficulty == DIFFICULTY_ORIGINAL)
488 								player.ammo[0] = MAX(player.ammo[0], 50);
489 							else if ((game.difficulty == DIFFICULTY_SUPEREASY)
490 								|| (game.area != MISN_INTERCEPTION))
491 							{
492 								LIMIT_ADD(player.ammo[0], collectable->value,
493 									0, game.maxPlasmaAmmo);
494 							}
495 
496 							weapons[W_PLAYER_WEAPON].ammo[0] = 5;
497 							weapons[W_PLAYER_WEAPON].damage = 5;
498 							weapons[W_PLAYER_WEAPON].reload[0] = rate2reload[5];
499 							weapons[W_PLAYER_WEAPON].flags |= WF_SPREAD;
500 
501 							strcpy(temp, _("Picked up a Super Charge!"));
502 						}
503 						else
504 						{
505 							/// Rare case of grabbing the super charge when you have no ammo at an
506 							/// interception (which means it won't take effect). The "Damn!" serves
507 							/// as a little sympathetic easter egg for players unfortunate enough to
508 							/// have this happen to them.
509 							strcpy(temp, _("Damn! Upgrade failed (no plasma ammo)"));
510 						}
511 						break;
512 
513 					case P_PLASMA_AMMO:
514 						if (player.ammo[0] >= game.maxPlasmaAmmo)
515 							strcpy(temp, _("Plasma cells already at Maximum"));
516 						else
517 						{
518 							LIMIT_ADD(player.ammo[0], collectable->value,
519 								0, game.maxPlasmaAmmo);
520 							snprintf(temp, STRMAX_SHORT, ngettext(
521 								/// "%d" must be retained. It is replaced with the number of plasma
522 								/// cells picked up.
523 								"Got %d plasma cell",
524 								"Got %d plasma cells",
525 								collectable->value), collectable->value);
526 						}
527 						game.cellPickups += collectable->value;
528 						break;
529 
530 					case P_CARGO:
531 						strcpy(temp, _("Picked up some Cargo"));
532 						game.cargoPickups++;
533 						break;
534 
535 					case P_SLAVES:
536 						snprintf(temp, STRMAX_SHORT, ngettext(
537 							"Rescued %d slave",
538 							"Rescued %d slaves",
539 							collectable->value), collectable->value);
540 						game.slavesRescued += collectable->value;
541 						break;
542 
543 					case P_ESCAPEPOD:
544 						strcpy(temp, _("Picked up an Escape Pod"));
545 						break;
546 
547 					case P_ORE:
548 						strcpy(temp, _("Picked up some Ore"));
549 						break;
550 				}
551 
552 				mission_updateRequirements(M_COLLECT, collectable->type,
553 					collectable->value);
554 
555 				collectable->active = 0;
556 				if (collectable->type != P_MINE)
557 				{
558 					info_setLine(temp, FONT_WHITE);
559 					if (collectable->type == P_SHIELD)
560 						audio_playSound(SFX_SHIELDUP, player.x, player.y);
561 					else
562 						audio_playSound(SFX_PICKUP, player.x, player.y);
563 				}
564 			}
565 
566 			// stop people from exploiting a weapon check condition
567 			if (player.ammo[0] == 0)
568 			{
569 				weapons[W_PLAYER_WEAPON].ammo[0] = game.minPlasmaOutput;
570 				weapons[W_PLAYER_WEAPON].damage = game.minPlasmaDamage;
571 				weapons[W_PLAYER_WEAPON].reload[0] = rate2reload[game.minPlasmaRate];
572 			}
573 		}
574 
575 		if (collectable->life < 1)
576 		{
577 			collectable->active = 0;
578 			if ((collectable->type == P_CARGO)
579 					|| (collectable->type == P_ESCAPEPOD)
580 					|| (collectable->type == P_SLAVES))
581 				mission_updateRequirements(M_PROTECT_PICKUP, collectable->type, 1);
582 		}
583 
584 		if (collectable->active)
585 		{
586 			prevCollectable = collectable;
587 			engine.collectableTail = collectable;
588 		}
589 		else
590 		{
591 			if ((collectable->type == P_MINE) && (collectable->x >= 0)
592 					&& (collectable->x <= screen->w) && (collectable->y >= 0)
593 					&& (collectable->y <= screen->h))
594 				collectable_explode(collectable);
595 			prevCollectable->next = collectable->next;
596 			free(collectable);
597 			collectable = prevCollectable;
598 		}
599 	}
600 }
601 
602 /*
603 This handles active bullets in a linked list. The current bullet and
604 previous bullet pointers are first assigned to the main header bullet
605 and each successive bullet is pulled out. Bullets are moved in their
606 delta coordinates, with rockets having fire trails added to them. Seperate
607 collision checks are done for a bullet that belongs to the enemy and one
608 that belongs to a player. However rockets can hit anyone. Upon an enemy
609 being killed, mission requirements are checked and collectables are randomly
610 spawned.
611 */
game_doBullets()612 static void game_doBullets()
613 {
614 	Object *bullet = engine.bulletHead;
615 	Object *prevBullet = engine.bulletHead;
616 
617 	Collectable *collectable;
618 	Collectable *prevCollectable;
619 
620 	int okayToHit = 0;
621 	int old_shield;
622 	float homingMissileSpeed = 0;
623 	int charger_num;
624 
625 	char msg[STRMAX];
626 
627 	bullet = engine.bulletHead;
628 	prevBullet = engine.bulletHead;
629 	engine.bulletTail = engine.bulletHead;
630 
631 	while (bullet->next != NULL)
632 	{
633 		bullet = bullet->next;
634 
635 		if (bullet->active)
636 		{
637 			if (bullet->flags & WF_HOMING)
638 			{
639 				if (bullet->target == NULL)
640 					bullet->target = bullet_getTarget(bullet);
641 
642 				if (bullet->owner->flags & FL_FRIEND)
643 					homingMissileSpeed = 0.25;
644 				else
645 					homingMissileSpeed = 0.05;
646 			}
647 
648 			if (bullet->id == WT_ROCKET)
649 			{
650 				explosion_add(bullet->x, bullet->y, SP_SMALL_EXPLOSION);
651 			}
652 			else if (bullet->id == WT_MICROROCKET)
653 			{
654 				explosion_add(bullet->x, bullet->y, SP_TINY_EXPLOSION);
655 			}
656 
657 			if ((bullet->flags & WF_AIMED))
658 			{
659 				screen_blit(bullet->image[0], (int)(bullet->x - bullet->dx),
660 					(int)(bullet->y - bullet->dy));
661 			}
662 
663 			if (bullet->id == WT_CHARGER)
664 			{
665 				if (game.difficulty == DIFFICULTY_ORIGINAL)
666 					charger_num = bullet->damage;
667 				else
668 					charger_num = bullet->damage * 2;
669 
670 				for (int i = 0 ; i < charger_num ; i++)
671 					screen_blit(bullet->image[0],
672 						(int)(bullet->x - RANDRANGE(
673 							-(charger_num / 6), charger_num / 6)),
674 						(int)(bullet->y + RANDRANGE(-3, 3)));
675 			}
676 
677 			screen_blit(bullet->image[0], (int)bullet->x, (int)bullet->y);
678 			bullet->x += bullet->dx;
679 			bullet->y += bullet->dy;
680 
681 			if (bullet->target != NULL)
682 			{
683 				if (bullet->x < bullet->target->x)
684 					LIMIT_ADD(bullet->dx, homingMissileSpeed, -15, 15);
685 				else if (bullet->x > bullet->target->x)
686 					LIMIT_ADD(bullet->dx, -homingMissileSpeed, -15, 15);
687 
688 				//Rocket is (more or less) in line with target. Fly straight
689 				if ((bullet->x > bullet->target->x - 1) && (bullet->x < bullet->target->x + 5))
690 					bullet->dx = 0;
691 
692 				if (bullet->y < bullet->target->y)
693 					LIMIT_ADD(bullet->dy, homingMissileSpeed, -15, 15);
694 				else if (bullet->y > bullet->target->y)
695 					LIMIT_ADD(bullet->dy, -homingMissileSpeed, -15, 15);
696 
697 				//Rocket is (more or less) in line with target. Fly straight
698 				if ((bullet->y > bullet->target->y - 1) && (bullet->y < bullet->target->y + 5))
699 					bullet->dy = 0;
700 
701 				if ((bullet->target->shield < 1) || (bullet->target->flags & FL_ISCLOAKED))
702 					bullet->target = NULL;
703 			}
704 
705 			bullet->x += engine.ssx + engine.smx;
706 			bullet->y += engine.ssy + engine.smy;
707 
708 			for (int i = 0 ; i < ALIEN_MAX ; i++)
709 			{
710 				if ((aliens[i].shield < 1) || (!aliens[i].active))
711 					continue;
712 
713 				okayToHit = 0;
714 
715 				if ((bullet->flags & WF_FRIEND) && (aliens[i].flags & FL_WEAPCO))
716 					okayToHit = 1;
717 				if ((bullet->flags & WF_WEAPCO) && (aliens[i].flags & FL_FRIEND))
718 					okayToHit = 1;
719 				if ((bullet->id == WT_ROCKET) || (bullet->id == WT_LASER)
720 						|| (bullet->id == WT_CHARGER))
721 					okayToHit = 1;
722 
723 				if (bullet->owner == aliens[i].owner)
724 					okayToHit = 0;
725 
726 				if (okayToHit)
727 				{
728 					if ((bullet->active) && (bullet_collision(bullet, &aliens[i])))
729 					{
730 						old_shield = aliens[i].shield;
731 
732 						if (bullet->owner == &player)
733 						{
734 							game.hits++;
735 							if (aliens[i].classDef == CD_PHOEBE)
736 							{
737 								radio_getRandomMessage(msg, _(
738 									/// Chris: Phoebe Hit Messages
739 									/// This is a list of messages separated by "\n".  They are randomly
740 									/// broadcast by Phoebe when Chris (the player) damages her.
741 									/// Instead of directly translating these, please populate the list
742 									/// with messages that work well in the target language,
743 									/// following the English version only as a general guideline.  Any
744 									/// number of messages is permitted.
745 									"OW! I hope that was an accident!\n"
746 									"Chris, please be more careful!\n"
747 									"Ouch! What are you shooting at me for?"
748 								));
749 								radio_setMessage(FS_PHOEBE, msg, 0);
750 							}
751 							else if (aliens[i].classDef == CD_URSULA)
752 							{
753 								radio_getRandomMessage(msg, _(
754 									/// Chris: Ursula Hit Messages
755 									/// This is a list of messages separated by "\n".  They are randomly
756 									/// broadcast by Phoebe when Chris (the player) damages her.
757 									/// Instead of directly translating these, please populate the list
758 									/// with messages that work well in the target language,
759 									/// following the English version only as a general guideline.  Any
760 									/// number of messages is permitted.
761 									"I am NOT your enemy!\n"
762 									"Hey! Watch it!\n"
763 									"What are you doing?! Shoot THEM!\n"
764 									"Pay some damn attention!"
765 								));
766 								radio_setMessage(FS_URSULA, msg, 0);
767 							}
768 						}
769 
770 						if (!(aliens[i].flags & FL_IMMORTAL))
771 						{
772 							alien_hurt(&aliens[i], bullet->owner,
773 								bullet->damage, (bullet->flags & WF_DISABLE));
774 
775 							aliens[i].hit = 5;
776 						}
777 						else if (aliens[i].flags & FL_DAMAGEOWNER)
778 						{
779 							alien_hurt(aliens[i].owner, bullet->owner,
780 								bullet->damage, (bullet->flags & WF_DISABLE));
781 
782 							aliens[i].owner->hit = 5;
783 						}
784 
785 						if (bullet->id == WT_CHARGER)
786 						{
787 							if (game.difficulty == DIFFICULTY_ORIGINAL)
788 							{
789 								bullet->shield -= old_shield;
790 								if (bullet->shield < 0)
791 									bullet->active = 0;
792 							}
793 							else
794 							{
795 								bullet->damage -= old_shield;
796 								if (bullet->damage <= 0)
797 								{
798 									bullet->active = 0;
799 									bullet->shield = 0;
800 								}
801 							}
802 							if (!bullet->active)
803 							{
804 								audio_playSound(SFX_EXPLOSION, bullet->x, bullet->y);
805 								for (int i = 0 ; i < 10 ; i++)
806 									explosion_add(bullet->x + RANDRANGE(-35, 35),
807 										bullet->y + RANDRANGE(-35, 35),
808 										SP_BIG_EXPLOSION);
809 							}
810 						}
811 						else
812 						{
813 							bullet->active = 0;
814 							bullet->shield = 0;
815 						}
816 
817 						if (bullet->id == WT_ROCKET)
818 							explosion_add(bullet->x, bullet->y, SP_BIG_EXPLOSION);
819 						else
820 							explosion_add(bullet->x, bullet->y, SP_SMALL_EXPLOSION);
821 					}
822 				}
823 			}
824 
825 			// Check for bullets hitting player
826 			if ((bullet->flags & WF_WEAPCO) || (bullet->id == WT_ROCKET)
827 				|| (bullet->id == WT_LASER) || (bullet->id == WT_CHARGER))
828 			{
829 				if (bullet->active && (player.shield > 0)
830 						&& (bullet->owner != &player)
831 						&& bullet_collision(bullet, &player))
832 				{
833 					old_shield = player.shield;
834 
835 					player_damage(bullet->damage, 0);
836 
837 					if (player.shield > 0)
838 					{
839 						if (bullet->owner->classDef == CD_PHOEBE)
840 						{
841 							radio_getRandomMessage(msg, _(
842 								/// Phoebe: Player Hit Messages
843 								/// This is a list of messages separated by "\n".  They are randomly
844 								/// broadcast when Phoebe accidentally damages Chris (the player).
845 								/// Instead of directly translating these, please populate the list
846 								/// with messages that work well in the target language,
847 								/// following the English version only as a general guideline.  Any
848 								/// number of messages is permitted.
849 								"Oops! Sorry!\n"
850 								"Whoops! Are you OK, Chris?\n"
851 								"Oh, sorry! I didn't see you there!"
852 							));
853 							radio_setMessage(FS_PHOEBE, msg, 0);
854 						}
855 						else if (bullet->owner->classDef == CD_URSULA)
856 						{
857 							radio_getRandomMessage(msg, _(
858 								/// Ursula: Player Hit Messages
859 								/// This is a list of messages separated by "\n".  They are randomly
860 								/// broadcast when Ursula accidentally damages Chris (the player).
861 								/// Instead of directly translating these, please populate the list
862 								/// with messages that work well in the target language,
863 								/// following the English version only as a general guideline.  Any
864 								/// number of messages is permitted.
865 								"Get out of the way!\n"
866 								"Don't fly into my missiles!\n"
867 								"Dammit, Chris, you made me miss!"
868 							));
869 							radio_setMessage(FS_URSULA, msg, 0);
870 						}
871 					}
872 
873 					if (bullet->id == WT_CHARGER)
874 					{
875 						bullet->damage -= old_shield;
876 						if (bullet->damage <= 0)
877 						{
878 							bullet->active = 0;
879 							bullet->shield = 0;
880 							audio_playSound(SFX_EXPLOSION, bullet->x, bullet->y);
881 							for (int i = 0 ; i < 10 ; i++)
882 								explosion_add(bullet->x + RANDRANGE(-35, 35),
883 									bullet->y + RANDRANGE(-35, 35), SP_BIG_EXPLOSION);
884 						}
885 					}
886 					else
887 					{
888 						bullet->active = 0;
889 						bullet->shield = 0;
890 					}
891 
892 					if (bullet->id == WT_ROCKET)
893 						explosion_add(bullet->x, bullet->y, SP_BIG_EXPLOSION);
894 					else
895 						explosion_add(bullet->x, bullet->y, SP_SMALL_EXPLOSION);
896 				}
897 			}
898 		}
899 
900 		if ((game.difficulty != DIFFICULTY_SUPEREASY)
901 			&& (game.difficulty != DIFFICULTY_EASY)
902 			&& ((bullet->owner == &player) || (bullet->id == WT_ROCKET)))
903 		{
904 			for (int j = 0 ; j < 20 ; j++)
905 			{
906 				if (cargo[j].active)
907 				{
908 					if (bullet_collision(bullet, &cargo[j]))
909 					{
910 						bullet->active = 0;
911 						explosion_add(bullet->x, bullet->y, SP_SMALL_EXPLOSION);
912 						audio_playSound(SFX_HIT, cargo[j].x, cargo[j].y);
913 						if (cargo[j].collectType != P_PHOEBE)
914 						{
915 							cargo[j].active = 0;
916 							audio_playSound(SFX_EXPLOSION, cargo[j].x, cargo[j].y);
917 							for (int i = 0 ; i < 10 ; i++)
918 								explosion_add(cargo[j].x + RANDRANGE(-15, 15),
919 									cargo[j].y + RANDRANGE(-15, 15),
920 									SP_BIG_EXPLOSION);
921 							mission_updateRequirements(M_PROTECT_PICKUP,
922 								P_CARGO, 1);
923 						}
924 					}
925 				}
926 			}
927 		}
928 
929 		// check to see if a bullet (on any side) hits a mine
930 		collectable = engine.collectableHead;
931 		prevCollectable = engine.collectableHead;
932 		engine.collectableTail = engine.collectableHead;
933 		while (collectable->next != NULL)
934 		{
935 			collectable = collectable->next;
936 
937 			if (collectable->type == P_MINE)
938 			{
939 				if (collectable_collision(collectable, bullet))
940 				{
941 					collectable->active = 0;
942 
943 					if (bullet->id != WT_CHARGER)
944 					{
945 						bullet->active = 0;
946 					}
947 					else
948 					{
949 						bullet->shield--;
950 						if (bullet->shield < 0)
951 							bullet->active = 0;
952 					}
953 
954 					if (bullet->owner == &player)
955 					{
956 						game.minesKilled++;
957 						game.hits++;
958 					}
959 				}
960 			}
961 
962 			if (collectable->active)
963 			{
964 				prevCollectable = collectable;
965 				engine.collectableTail = collectable;
966 			}
967 			else
968 			{
969 				collectable_explode(collectable);
970 				prevCollectable->next = collectable->next;
971 				free(collectable);
972 				collectable = prevCollectable;
973 			}
974 		}
975 
976 		bullet->shield--;
977 
978 		if (bullet->shield < 1)
979 		{
980 			if (bullet->flags & WF_TIMEDEXPLOSION)
981 			{
982 				audio_playSound(SFX_EXPLOSION, bullet->x, bullet->y);
983 				for (int i = 0 ; i < 10 ; i++)
984 					explosion_add(bullet->x + RANDRANGE(-35, 35),
985 						bullet->y + RANDRANGE(-35, 35), SP_BIG_EXPLOSION);
986 
987 				player_checkShockDamage(bullet->x, bullet->y);
988 			}
989 			bullet->active = 0;
990 		}
991 
992 		if (bullet->active)
993 		{
994 			prevBullet = bullet;
995 			engine.bulletTail = bullet;
996 		}
997 		else
998 		{
999 			prevBullet->next = bullet->next;
1000 			free(bullet);
1001 			bullet = prevBullet;
1002 		}
1003 	}
1004 }
1005 
game_doAliens()1006 static void game_doAliens()
1007 {
1008 	static float barrierLoop = 0;
1009 
1010 	int shapeToUse;
1011 	int canFire;
1012 	int n;
1013 
1014 	barrierLoop += 0.2;
1015 
1016 	// A global variable for checking if all the aliens are dead
1017 	engine.allAliensDead = 1;
1018 
1019 	for (int i = 0 ; i < ALIEN_MAX ; i++)
1020 	{
1021 		if (aliens[i].active)
1022 		{
1023 			if (aliens[i].shield > 0)
1024 			{
1025 				if ((aliens[i].flags & FL_WEAPCO) && (!(aliens[i].flags & FL_DISABLED)))
1026 					engine.allAliensDead = 0;
1027 
1028 				// Set part attributes
1029 				if (aliens[i].owner != &aliens[i])
1030 				{
1031 					aliens[i].face = aliens[i].owner->face;
1032 
1033 					if (aliens[i].face == 0)
1034 						aliens[i].x = aliens[i].owner->x - aliens[i].dx;
1035 					else
1036 						aliens[i].x = (aliens[i].owner->x + aliens[i].dx
1037 								+ aliens[i].owner->image[0]->w
1038 								- aliens[i].image[0]->w);
1039 
1040 					aliens[i].y = (aliens[i].owner->y + aliens[i].dy);
1041 
1042 					if (aliens[i].owner->shield < 1)
1043 					{
1044 						if ((aliens[i].classDef != CD_URANUSBOSSWING1)
1045 								&& (aliens[i].classDef != CD_URANUSBOSSWING2))
1046 						{
1047 							aliens[i].shield = 0;
1048 						}
1049 						else
1050 						{
1051 							aliens[i].flags &= ~FL_IMMORTAL;
1052 							aliens[i].owner = &aliens[i];
1053 							aliens[i].chance[0] = 25;
1054 						}
1055 					}
1056 				}
1057 
1058 				canFire = 1; // The alien is allowed to fire
1059 
1060 				LIMIT_ADD(aliens[i].thinktime, -1, 0, 250);
1061 
1062 				if (aliens[i].target->shield < 1)
1063 					aliens[i].target = &aliens[i];
1064 
1065 				// Specific to Sid to stop him pissing about(!)
1066 				if ((aliens[i].classDef == CD_SID)
1067 						&& (aliens[i].target->flags & FL_DISABLED))
1068 					aliens[i].target = &aliens[i];
1069 
1070  				if (aliens[i].target == &aliens[i])
1071 				{
1072 					if (engine.missionCompleteTimer == 0)
1073 					{
1074 						alien_searchForTarget(&aliens[i]);
1075 					}
1076 					else
1077 					{
1078 						if (aliens[i].flags & FL_FRIEND)
1079 						{
1080 							aliens[i].target = &player;
1081 							aliens[i].thinktime = 1;
1082 						}
1083 					}
1084 				}
1085 
1086 				if ((!(aliens[i].flags & FL_DISABLED))
1087 					&& (aliens[i].thinktime == 0) && (aliens[i].target != &aliens[i])
1088 					&& (aliens[i].owner == &aliens[i]))
1089 				{
1090 					if (aliens[i].classDef == CD_KLINE)
1091 						alien_setKlineAI(&aliens[i]);
1092 					else
1093 						alien_setAI(&aliens[i]);
1094 
1095 					aliens[i].thinktime = (rand() % 25) * 10;
1096 
1097 					// Face direction of movement unless you always face
1098 					// your target(!)
1099 					if (!(aliens[i].flags & FL_ALWAYSFACE))
1100 					{
1101 						aliens[i].face = (aliens[i].dx > 0);
1102 					}
1103 
1104 					LIMIT(aliens[i].dx, -aliens[i].speed, aliens[i].speed);
1105 					LIMIT(aliens[i].dy, -aliens[i].speed, aliens[i].speed);
1106 
1107 				}
1108 
1109 				if (aliens[i].flags & FL_ALWAYSFACE)
1110 				{
1111 					aliens[i].face = 0;
1112 					if (aliens[i].x > aliens[i].target->x) aliens[i].face = 1;
1113 				}
1114 
1115 				if ((game.area == MISN_ELLESH)
1116 						&& ((aliens[i].classDef == CD_BOSS)
1117 						    || (game.difficulty != DIFFICULTY_ORIGINAL)))
1118 					aliens[i].face = 0;
1119 
1120 				if ((aliens[i].flags & FL_DEPLOYDRONES) && ((rand() % 300) == 0))
1121 					alien_addDrone(&aliens[i]);
1122 
1123 				if (aliens[i].flags & FL_LEAVESECTOR)
1124 				{
1125 					// Note: The original version of this line incorrectly
1126 					// specified -15 as the *maximum* instead of the
1127 					// *minimum*, which at the time was equivalent to
1128 					// ``aliens[i].dx = -15``.
1129 					LIMIT_ADD(aliens[i].dx, ALIEN_WARP_ACCEL, ALIEN_WARP_SPEED, 0);
1130 					aliens[i].dy = 0;
1131 					aliens[i].thinktime = 999;
1132 					aliens[i].face = 0;
1133 
1134 					if (aliens[i].x >= 5000)
1135 					{
1136 						aliens[i].flags &= ~FL_LEAVESECTOR;
1137 						aliens[i].flags |= FL_ESCAPED;
1138 						aliens[i].active = 0;
1139 
1140 						if (aliens[i].classDef == CD_CLOAKFIGHTER)
1141 						{
1142 							game.experimentalShield = aliens[i].shield;
1143 							info_setLine("Experimental Fighter has fled",
1144 								FONT_CYAN);
1145 						}
1146 
1147 						aliens[i].shield = 0;
1148 						mission_updateRequirements(M_ESCAPE_TARGET,
1149 							aliens[i].classDef, 1);
1150 
1151 						if (aliens[i].classDef != CD_CLOAKFIGHTER)
1152 							mission_updateRequirements(M_DESTROY_TARGET_TYPE,
1153 								aliens[i].classDef, 1);
1154 					}
1155 				}
1156 
1157 				// This deals with the Experimental Fighter in Mordor
1158 				// (and Kline on the final mission)
1159 				// It can cloak and uncloak at random. When cloaked,
1160 				// its sprite is not displayed. However the engine
1161 				// trail is still visible!
1162 				if ((aliens[i].flags & FL_CANCLOAK) && ((rand() % 500) == 0))
1163 				{
1164 					if (aliens[i].flags & FL_ISCLOAKED)
1165 						aliens[i].flags -= FL_ISCLOAKED;
1166 					else
1167 						aliens[i].flags += FL_ISCLOAKED;
1168 					audio_playSound(SFX_CLOAK, aliens[i].x, aliens[i].y);
1169 				}
1170 
1171 				if (aliens[i].classDef == CD_BARRIER)
1172 				{
1173 					aliens[i].dx = -10 + (sinf(barrierLoop + aliens[i].speed) * 60);
1174 					aliens[i].dy = 20 + (cosf(barrierLoop + aliens[i].speed) * 40);
1175 				}
1176 
1177 				if ((aliens[i].classDef == CD_MOBILESHIELD)
1178 						&& (aliens[ALIEN_BOSS].active)
1179 						&& (aliens[ALIEN_BOSS].shield > 0))
1180 				{
1181 					LIMIT_ADD(aliens[ALIEN_BOSS].shield, 1, 0,
1182 						aliens[ALIEN_BOSS].maxShield);
1183 				}
1184 
1185 				LIMIT_ADD(aliens[i].reload[0], -1, 0, 999);
1186 				LIMIT_ADD(aliens[i].reload[1], -1, 0, 999);
1187 
1188 				if ((!(aliens[i].flags & FL_DISABLED))
1189 						&& (!(aliens[i].flags & FL_NOFIRE)))
1190 				{
1191 					if ((aliens[i].target->shield > 0))
1192 						canFire = alien_checkTarget(&aliens[i]);
1193 
1194 					if (((aliens[i].thinktime % 2) == 0)
1195 							&& (aliens[i].flags & FL_FRIEND))
1196 						canFire = alien_enemiesInFront(&aliens[i]);
1197 				}
1198 				else
1199 				{
1200 					canFire = 0;
1201 				}
1202 
1203 				if (canFire)
1204 				{
1205 					for (int j = 0 ; j < 2 ; j++)
1206 					{
1207 						if ((aliens[i].reload[j] == 0)
1208 								&& ((rand() % 1000 < aliens[i].chance[j])
1209 									|| (aliens[i].flags & FL_CONTINUOUS_FIRE)))
1210 						{
1211 							if ((aliens[i].weaponType[j] != W_ENERGYRAY)
1212 									&& (aliens[i].weaponType[j] != W_LASER))
1213 							{
1214 								if (aliens[i].weaponType[j] == W_CHARGER)
1215 									aliens[i].ammo[j] = 50 + rand() % 150;
1216 								ship_fireBullet(&aliens[i], j);
1217 							}
1218 							else if (aliens[i].weaponType[j] == W_LASER)
1219 							{
1220 								aliens[i].flags += FL_FIRELASER;
1221 							}
1222 							// Note: ammo[0] is required whether the ray is primary
1223 							// or secondary because futher below, ammo[0] increases on
1224 							// any alien that isn't currently firing a ray.
1225 							else if ((aliens[i].weaponType[j] == W_ENERGYRAY)
1226 									&& (aliens[i].ammo[0] >= RAY_INTERVAL))
1227 							{
1228 								aliens[i].flags += FL_FIRERAY;
1229 								audio_playSound(SFX_ENERGYRAY, aliens[i].x, aliens[i].y);
1230 							}
1231 						}
1232 					}
1233 				}
1234 
1235 				if (aliens[i].flags & FL_FIRERAY)
1236 				{
1237 					ship_fireRay(&aliens[i]);
1238 				}
1239 				else
1240 				{
1241 					LIMIT_ADD(aliens[i].ammo[0], 1, 0, RAY_INTERVAL);
1242 				}
1243 
1244 				if (aliens[i].flags & FL_FIRELASER)
1245 				{
1246 					ship_fireBullet(&aliens[i], 1);
1247 					if ((rand() % 25) == 0)
1248 						aliens[i].flags -= FL_FIRELASER;
1249 				}
1250 
1251 				if (aliens[i].flags & FL_DROPMINES)
1252 				{
1253 					if ((rand() % 150) == 0)
1254 						collectable_add(aliens[i].x, aliens[i].y, P_MINE, 25,
1255 							600 + rand() % 2400);
1256 
1257 					// Kline drops mines a lot more often
1258 					if ((&aliens[i] == &aliens[ALIEN_KLINE]))
1259 					{
1260 						if ((rand() % 10) == 0)
1261 							collectable_add(aliens[i].x, aliens[i].y, P_MINE, 25,
1262 								600 + rand() % 2400);
1263 					}
1264 				}
1265 
1266 				shapeToUse = aliens[i].imageIndex[aliens[i].face];
1267 
1268 				if (aliens[i].hit)
1269 					shapeToUse += SS_HIT_INDEX;
1270 
1271 				LIMIT_ADD(aliens[i].hit, -1, 0, 100);
1272 
1273 				if ((aliens[i].x + aliens[i].image[0]->w > 0)
1274 						&& (aliens[i].x < screen->w)
1275 						&& (aliens[i].y + aliens[i].image[0]->h > 0)
1276 						&& (aliens[i].y < screen->h))
1277 				{
1278 					if ((!(aliens[i].flags & FL_DISABLED))
1279 							&& (aliens[i].classDef != CD_ASTEROID)
1280 							&& (aliens[i].classDef != CD_ASTEROID2))
1281 						explosion_addEngine(&aliens[i]);
1282 					if ((!(aliens[i].flags & FL_ISCLOAKED))
1283 							|| (aliens[i].hit > 0))
1284 						screen_blit(gfx_shipSprites[shapeToUse], (int)aliens[i].x,
1285 							(int)aliens[i].y);
1286 					if (aliens[i].flags & FL_DISABLED)
1287 					{
1288 						if ((rand() % 10) == 0)
1289 							explosion_add(aliens[i].x + (rand() % aliens[i].image[0]->w),
1290 								aliens[i].y + (rand() % aliens[i].image[0]->h),
1291 								SP_ELECTRICAL);
1292 					}
1293 				}
1294 
1295 				if ((game.area == MISN_MARS) && (aliens[i].x < -60))
1296 					aliens[i].active = 0;
1297 			}
1298 			else
1299 			{
1300 				aliens[i].shield--;
1301 				if ((aliens[i].x > 0) && (aliens[i].x < screen->w)
1302 					&& (aliens[i].y > 0) && (aliens[i].y < screen->h))
1303 				{
1304 					screen_blit(aliens[i].image[aliens[i].face], (int)aliens[i].x,
1305 						(int)aliens[i].y);
1306 					explosion_add(aliens[i].x + (rand() % aliens[i].image[0]->w),
1307 						aliens[i].y + (rand() % aliens[i].image[0]->h),
1308 						SP_BIG_EXPLOSION);
1309 				}
1310 				if (aliens[i].shield < aliens[i].deathCounter)
1311 				{
1312 					aliens[i].active = 0;
1313 					if ((aliens[i].classDef == CD_BOSS)
1314 						|| (aliens[i].owner == &aliens[ALIEN_BOSS])
1315 						|| (aliens[i].flags & FL_FRIEND)
1316 						|| (aliens[i].classDef == CD_ASTEROID)
1317 						|| (aliens[i].classDef == CD_KLINE))
1318 					{
1319 						game_addDebris((int)aliens[i].x, (int)aliens[i].y,
1320 							aliens[i].maxShield);
1321 					}
1322 
1323 					if (aliens[i].classDef == CD_ASTEROID)
1324 					{
1325 						n = 1 + (rand() % 3);
1326 						for (int j = 0 ; j < n ; j++)
1327 						{
1328 							alien_addSmallAsteroid(&aliens[i]);
1329 						}
1330 					}
1331 				}
1332 			}
1333 
1334 			// Adjust the movement even whilst exploding
1335 			if ((!(aliens[i].flags & FL_NOMOVE))
1336 					&& (!(aliens[i].flags & FL_DISABLED)))
1337 				alien_move(&aliens[i]);
1338 
1339 			if ((game.area != MISN_ELLESH) || (aliens[i].shield < 0))
1340 				aliens[i].x += engine.ssx;
1341 
1342 			aliens[i].x += engine.smx;
1343 			aliens[i].y += engine.ssy + engine.smy;
1344 		}
1345 	}
1346 }
1347 
game_doPlayer()1348 static void game_doPlayer()
1349 {
1350 	int shapeToUse;
1351 	float cd;
1352 	float cc;
1353 	double xm = 0;
1354 	double ym = 0;
1355 	int xmoved = 0;
1356 	int ymoved = 0;
1357 	char msg[STRMAX];
1358 
1359 	// This causes the motion to slow
1360 	engine.ssx *= 0.99;
1361 	engine.ssy *= 0.99;
1362 
1363 	engine.smx = 0;
1364 	engine.smy = 0;
1365 
1366 	if (player.shield > -100)
1367 	{
1368 		if (player.shield > 0)
1369 		{
1370 			if ((engine.keyState[KEY_FIRE]))
1371 				ship_fireBullet(&player, 0);
1372 
1373 			if ((engine.keyState[KEY_ALTFIRE]) && (player.weaponType[1] != W_NONE))
1374 			{
1375 				if ((player.weaponType[1] != W_CHARGER)
1376 						&& (player.weaponType[1] != W_LASER)
1377 						&& (player.ammo[1] > 0))
1378 				{
1379 					ship_fireBullet(&player, 1);
1380 				}
1381 
1382 				if (player.weaponType[1] == W_LASER)
1383 				{
1384 					if (player.ammo[1] < 100)
1385 					{
1386 						ship_fireBullet(&player, 1);
1387 
1388 						if (!engine.cheatAmmo)
1389 							player.ammo[1] += 1;
1390 
1391 						if (player.ammo[1] >= 100)
1392 						{
1393 							player.ammo[1] = 200;
1394 							info_setLine("Laser Overheat!", FONT_WHITE);
1395 						}
1396 					}
1397 				}
1398 			}
1399 
1400 			if (player.weaponType[1] == W_CHARGER)
1401 			{
1402 				if (engine.keyState[KEY_ALTFIRE]
1403 						&& ((game.difficulty == DIFFICULTY_ORIGINAL)
1404 							|| !(engine.keyState[KEY_FIRE])))
1405 				{
1406 					if (!player_chargerFired)
1407 					{
1408 						// With ammo cheat, cause the charge cannon to
1409 						// fire at full blast immediately.
1410 						if (engine.cheatAmmo)
1411 							player.ammo[1] = 200;
1412 
1413 						if (game.difficulty == DIFFICULTY_ORIGINAL)
1414 						{
1415 							LIMIT_ADD(player.ammo[1], 1, 0, 200);
1416 						}
1417 						else
1418 						{
1419 							LIMIT_ADD(player.ammo[1], 1, 0, 150);
1420 							if (player.ammo[1] >= 150)
1421 							{
1422 								ship_fireBullet(&player, 1);
1423 								player.ammo[1] = 0;
1424 								player_chargerFired = 1;
1425 							}
1426 						}
1427 					}
1428 				}
1429 				else
1430 				{
1431 					if (player.ammo[1] > 0)
1432 						ship_fireBullet(&player, 1);
1433 					player.ammo[1] = 0;
1434 					player_chargerFired = 0;
1435 				}
1436 			}
1437 
1438 			if ((engine.keyState[KEY_SWITCH]))
1439 			{
1440 				if ((weapons[W_PLAYER_WEAPON].ammo[0] >= 3)
1441 					&& ((weapons[W_PLAYER_WEAPON].ammo[0] <= game.maxPlasmaOutput)
1442 						|| (game.difficulty == DIFFICULTY_ORIGINAL)))
1443 				{
1444 					weapons[W_PLAYER_WEAPON].flags ^= WF_SPREAD;
1445 
1446 					if (weapons[W_PLAYER_WEAPON].flags & WF_SPREAD)
1447 					{
1448 						info_setLine("Weapon set to Spread", FONT_WHITE);
1449 					}
1450 					else
1451 					{
1452 						info_setLine("Weapon set to Concentrate", FONT_WHITE);
1453 					}
1454 				}
1455 
1456 				engine.keyState[KEY_SWITCH] = 0;
1457 			}
1458 
1459 			LIMIT_ADD(player.reload[0], -1, 0, 999);
1460 			LIMIT_ADD(player.reload[1], -1, 0, 999);
1461 
1462 			xm = engine.keyState[KEY_RIGHT] - engine.keyState[KEY_LEFT];
1463 			if (!xm)
1464 				xm = engine.xaxis;
1465 
1466 			if (xm)
1467 			{
1468 				player.x += xm * player.speed;
1469 				engine.ssx -= xm * 0.1;
1470 				player.face = (xm < 0 ? 1 : 0);
1471 				xmoved = 1;
1472 			}
1473 
1474 			ym = engine.keyState[KEY_DOWN] - engine.keyState[KEY_UP];
1475 			if (!ym)
1476 				ym = engine.yaxis;
1477 
1478 			if (ym)
1479 			{
1480 				player.y += ym * player.speed;
1481 				engine.ssy -= ym * 0.1;
1482 				ymoved = 1;
1483 			}
1484 
1485 			if (engine.keyState[KEY_ESCAPE])
1486 			{
1487 				if ((engine.done == ENGINE_RUNNING)
1488 					&& (engine.gameSection == SECTION_GAME)
1489 					&& (mission.remainingObjectives1 == 0))
1490 				{
1491 					audio_playSound(SFX_FLY, screen->w / 2, screen->h / 2);
1492 					engine.done = ENGINE_SYSEXIT;
1493 					engine.missionCompleteTimer = (SDL_GetTicks() - 1);
1494 				}
1495 			}
1496 
1497 			if (engine.keyState[KEY_PAUSE])
1498 			{
1499 				engine.paused = 1;
1500 				engine.keyState[KEY_PAUSE] = 0;
1501 			}
1502 
1503 			if ((game.area == MISN_ELLESH)
1504 					|| (game.area == MISN_MARS))
1505 			{
1506 				player.face = 0;
1507 				xmoved = 1;
1508 				ymoved = 1;
1509 			}
1510 
1511 			if (engine.done == ENGINE_RUNNING)
1512 			{
1513 				if (game.difficulty == DIFFICULTY_ORIGINAL)
1514 				{
1515 					LIMIT(player.x, X_VIEW_BORDER, screen->w - X_VIEW_BORDER);
1516 					LIMIT(player.y, Y_VIEW_BORDER, screen->h - Y_VIEW_BORDER);
1517 				}
1518 				else
1519 				{
1520 					if (xmoved)
1521 					{
1522 						if (player.x < X_VIEW_BORDER)
1523 						{
1524 							engine.smx += X_VIEW_BORDER - player.x;
1525 							player.x = X_VIEW_BORDER;
1526 						}
1527 						else if (player.x > screen->w - X_VIEW_BORDER)
1528 						{
1529 							engine.smx += (screen->w - X_VIEW_BORDER) - player.x;
1530 							player.x = screen->w - X_VIEW_BORDER;
1531 						}
1532 					}
1533 					else if (game.difficulty != DIFFICULTY_ORIGINAL)
1534 					{
1535 						cd = player.x - screen->w / 2;
1536 						if (cd < 0)
1537 						{
1538 							cc = MAX(cd / 10, MAX(0, engine.ssx) - CAMERA_MAX_SPEED);
1539 							player.x -= cc;
1540 							engine.smx -= cc;
1541 						}
1542 						else if (cd > 0)
1543 						{
1544 							cc = MIN(cd / 10, CAMERA_MAX_SPEED + MIN(0, engine.ssx));
1545 							player.x -= cc;
1546 							engine.smx -= cc;
1547 						}
1548 					}
1549 
1550 					if (ymoved)
1551 					{
1552 						if (player.y < Y_VIEW_BORDER)
1553 						{
1554 							engine.smy += Y_VIEW_BORDER - player.y;
1555 							player.y = Y_VIEW_BORDER;
1556 						}
1557 						else if (player.y > screen->h - Y_VIEW_BORDER)
1558 						{
1559 							engine.smy += (screen->h - Y_VIEW_BORDER) - player.y;
1560 							player.y = screen->h - Y_VIEW_BORDER;
1561 						}
1562 					}
1563 					else if (game.difficulty != DIFFICULTY_ORIGINAL)
1564 					{
1565 						cd = player.y - screen->h / 2;
1566 						if (cd < 0)
1567 						{
1568 							cc = MAX(cd / 10, MAX(0, engine.ssy) - CAMERA_MAX_SPEED);
1569 							player.y -= cc;
1570 							engine.smy -= cc;
1571 						}
1572 						else if (cd > 0)
1573 						{
1574 							cc = MIN(cd / 10, CAMERA_MAX_SPEED + MIN(0, engine.ssy));
1575 							player.y -= cc;
1576 							engine.smy -= cc;
1577 						}
1578 					}
1579 				}
1580 			}
1581 
1582 			explosion_addEngine(&player);
1583 
1584 			shapeToUse = player.face;
1585 
1586 			if (player.hit)
1587 				shapeToUse += SS_HIT_INDEX;
1588 
1589 			LIMIT_ADD(player.hit, -1, 0, 100);
1590 
1591 			screen_blit(gfx_shipSprites[shapeToUse], (int)player.x, (int)player.y);
1592 			if ((player.maxShield > 1) && (player.shield <= engine.lowShield)
1593 					&& CHANCE(player.shield > 1 ? 1. / 10 : 3. / 10))
1594 				explosion_add(player.x + RANDRANGE(-10, 10),
1595 					player.y + RANDRANGE(-10, 20), SP_SMOKE);
1596 		}
1597 		else
1598 		{
1599 			// Player is dead. At this point, the shield counts down to
1600 			// -100 and does death and explosion stuff along the way.
1601 			player.active = 0;
1602 			player.shield--;
1603 			if (player.shield == -1)
1604 			{
1605 				if (aliens[ALIEN_KLINE].active)
1606 				{
1607 					if (game.area == MISN_VENUS)
1608 					{
1609 						radio_getRandomMessage(msg, _(
1610 							/// Kline Venus insult messages
1611 							/// This is a list of insults separated by "\n".  They are randomly
1612 							/// broadcast when the player dies in the Venus mission.
1613 							/// Instead of directly translating these, please populate the list
1614 							/// with insults that work well in the target language,
1615 							/// following the English version only as a general guideline.  Any
1616 							/// number of insults is permitted.
1617 							"Fool.\n"
1618 							"And now you're nothing but a DEAD hero."
1619 						));
1620 						radio_setMessage(FS_KLINE, msg, 1);
1621 					}
1622 					else
1623 					{
1624 						radio_getRandomMessage(msg, _(
1625 							/// Kline insult messages
1626 							/// This is a list of insults separated by "\n".  They are randomly
1627 							/// broadcast when the player dies in a mission Kline is in (except Venus).
1628 							/// Instead of directly translating these, please populate the list
1629 							/// with insults that work well in the target language,
1630 							/// following the English version only as a general guideline.  Any
1631 							/// number of insults is permitted.
1632 							"Pathetic.\n"
1633 							"How very disappointing...\n"
1634 							"Heroic. And foolish."
1635 						));
1636 						radio_setMessage(FS_KLINE, msg, 1);
1637 					}
1638 				}
1639 				else if ((aliens[ALIEN_BOSS].active) && (aliens[ALIEN_BOSS].classDef == CD_KRASS))
1640 				{
1641 					/// Dialog: Krass Tyler
1642 					/// Used when the player is killed in the Jupiter mission.
1643 					radio_setMessage(FS_KRASS, _("That was the easiest $90,000,000 I've ever earned! Bwah! Ha! Ha! Ha!"), 1);
1644 				}
1645 
1646 				// Make it look like the ships are all still moving...
1647 				if (game.area == MISN_ELLESH)
1648 				{
1649 					for (int i = 0 ; i < ALIEN_MAX ; i++)
1650 						aliens[i].flags |= FL_LEAVESECTOR;
1651 				}
1652 
1653 				audio_playSound(SFX_DEATH, player.x, player.y);
1654 				audio_playSound(SFX_EXPLOSION, player.x, player.y);
1655 			}
1656 
1657 			engine.keyState[KEY_UP] = 0;
1658 			engine.keyState[KEY_DOWN] = 0;
1659 			engine.keyState[KEY_LEFT] = 0;
1660 			engine.keyState[KEY_RIGHT] = 0;
1661 			engine.xaxis = 0;
1662 			engine.yaxis = 0;
1663 			if (CHANCE(1. / 3.))
1664 				explosion_add(player.x + RANDRANGE(-10, 10),
1665 					player.y + RANDRANGE(-10, 10), SP_BIG_EXPLOSION);
1666 			if (player.shield == -99)
1667 				game_addDebris((int)player.x, (int)player.y, player.maxShield);
1668 		}
1669 	}
1670 
1671 	LIMIT(engine.ssx, -CAMERA_MAX_SPEED, CAMERA_MAX_SPEED);
1672 	LIMIT(engine.ssy, -CAMERA_MAX_SPEED, CAMERA_MAX_SPEED);
1673 
1674 	// Specific for the mission were you have to chase the Executive Transport
1675 	if (((game.area == MISN_ELLESH) && (player.shield > 0))
1676 			|| (game.area == MISN_MARS))
1677 	{
1678 		engine.ssx = -6;
1679 		engine.ssy = 0;
1680 	}
1681 
1682 	player.dx = engine.ssx;
1683 	player.dy = engine.ssy;
1684 }
1685 
game_doCargo()1686 static void game_doCargo()
1687 {
1688 	float dx, dy, chainX, chainY;
1689 
1690 	for (int i = 0 ; i < MAX_CARGO ; i++)
1691 	{
1692 		if (cargo[i].active)
1693 		{
1694 			if (!cargo[i].owner->active)
1695 			{
1696 				cargo_becomeCollectable(i);
1697 				continue;
1698 			}
1699 
1700 			screen_blit(cargo[i].image[0], (int)cargo[i].x, (int)cargo[i].y);
1701 
1702 			cargo[i].x += engine.ssx + engine.smx;
1703 			cargo[i].y += engine.ssy + engine.smy;
1704 
1705 			LIMIT(cargo[i].x, cargo[i].owner->x - 50, cargo[i].owner->x + 50);
1706 			LIMIT(cargo[i].y, cargo[i].owner->y - 50, cargo[i].owner->y + 50);
1707 
1708 			dx = (cargo[i].x - cargo[i].owner->x) / 10;
1709 			dy = (cargo[i].y - cargo[i].owner->y) / 10;
1710 			chainX = cargo[i].x - cargo[i].dx;
1711 			chainY = cargo[i].y - cargo[i].dy;
1712 
1713 			// draw the chain link line
1714 			for (int j = 0 ; j < 10 ; j++)
1715 			{
1716 				screen_blit(gfx_sprites[SP_CHAIN_LINK], (int)chainX, (int)chainY);
1717 				chainX -= dx;
1718 				chainY -= dy;
1719 			}
1720 		}
1721 	}
1722 }
1723 
game_doDebris()1724 static void game_doDebris()
1725 {
1726 	Object *prevDebris = engine.debrisHead;
1727 	Object *debris = engine.debrisHead;
1728 	engine.debrisTail = engine.debrisHead;
1729 
1730 	while (debris->next != NULL)
1731 	{
1732 		debris = debris->next;
1733 
1734 		if (debris->thinktime > 0)
1735 		{
1736 			debris->thinktime--;
1737 
1738 			debris->x += engine.ssx + engine.smx;
1739 			debris->y += engine.ssy + engine.smy;
1740 			debris->x += debris->dx;
1741 			debris->y += debris->dy;
1742 
1743 			explosion_add(debris->x + RANDRANGE(-10, 10), debris->y + RANDRANGE(-10, 10), SP_BIG_EXPLOSION);
1744 		}
1745 
1746 		if (debris->thinktime < 1)
1747 		{
1748 			prevDebris->next = debris->next;
1749 			free(debris);
1750 			debris = prevDebris;
1751 		}
1752 		else
1753 		{
1754 			prevDebris = debris;
1755 			engine.debrisTail = debris;
1756 		}
1757 
1758 	}
1759 }
1760 
1761 /*
1762 Loops through active explosions and decrements their think time.
1763 If their thinktime is divisable by 5, then the frame is changed to
1764 the next one up (for example 0->1->2-3). When their think time is 0,
1765 the explosion is killed off.
1766 */
game_doExplosions()1767 void game_doExplosions()
1768 {
1769 	Object *prevExplosion = engine.explosionHead;
1770 	Object *explosion = engine.explosionHead;
1771 	engine.explosionTail = engine.explosionHead;
1772 
1773 	while (explosion->next != NULL)
1774 	{
1775 		explosion = explosion->next;
1776 
1777 		if (explosion->active)
1778 		{
1779 			explosion->x += engine.ssx + engine.smx;
1780 			explosion->y += engine.ssy + engine.smy;
1781 
1782 			screen_blit(explosion->image[0], (int)explosion->x, (int)explosion->y);
1783 
1784 			if(CHANCE(1. / 7.))
1785 			{
1786 				explosion->thinktime -= 7;
1787 
1788 				if(explosion->thinktime < 1)
1789 				{
1790 					explosion->active = 0;
1791 				}
1792 				else
1793 				{
1794 					explosion->face++;
1795 					explosion->image[0] = gfx_sprites[explosion->face];
1796 				}
1797 			}
1798 		}
1799 
1800 		if (explosion->active)
1801 		{
1802 			prevExplosion = explosion;
1803 			engine.explosionTail = explosion;
1804 		}
1805 		else
1806 		{
1807 			prevExplosion->next = explosion->next;
1808 			free(explosion);
1809 			explosion = prevExplosion;
1810 		}
1811 	}
1812 }
1813 
1814 /*
1815 Draw an arrow at the edge of the screen for each enemy ship that is not visible.
1816 */
game_doArrow(int i)1817 static void game_doArrow(int i)
1818 {
1819 	int arrow = -1;
1820 	int arrowX;
1821 	int arrowY;
1822 
1823 	int indicator = -1;
1824 	int indicatorX;
1825 	int indicatorY;
1826 
1827 	if (i < 0 || !aliens[i].active || aliens[i].shield <= 0 || aliens[i].flags & FL_ISCLOAKED)
1828 		return;
1829 
1830 	if (aliens[i].x + aliens[i].image[0]->w < 0)
1831 	{
1832 		if (aliens[i].y + aliens[i].image[0]->h < 0)
1833 			arrow = (((game.difficulty != DIFFICULTY_ORIGINAL)
1834 					&& (aliens[i].flags & FL_FRIEND)) ?
1835 				SP_ARROW_FRIEND_NORTHWEST : SP_ARROW_NORTHWEST);
1836 		else if (aliens[i].y > screen->h)
1837 			arrow = (((game.difficulty != DIFFICULTY_ORIGINAL)
1838 					&& (aliens[i].flags & FL_FRIEND)) ?
1839 				SP_ARROW_FRIEND_SOUTHWEST : SP_ARROW_SOUTHWEST);
1840 		else
1841 			arrow = (((game.difficulty != DIFFICULTY_ORIGINAL)
1842 					&& (aliens[i].flags & FL_FRIEND)) ?
1843 				SP_ARROW_FRIEND_WEST : SP_ARROW_WEST);
1844 	}
1845 	else if (aliens[i].x > screen->w)
1846 	{
1847 		if (aliens[i].y + aliens[i].image[0]->h < 0)
1848 			arrow = (((game.difficulty != DIFFICULTY_ORIGINAL)
1849 					&& (aliens[i].flags & FL_FRIEND)) ?
1850 				SP_ARROW_FRIEND_NORTHEAST : SP_ARROW_NORTHEAST);
1851 		else if (aliens[i].y > screen->h)
1852 			arrow = (((game.difficulty != DIFFICULTY_ORIGINAL)
1853 					&& (aliens[i].flags & FL_FRIEND)) ?
1854 				SP_ARROW_FRIEND_SOUTHEAST : SP_ARROW_SOUTHEAST);
1855 		else
1856 			arrow = (((game.difficulty != DIFFICULTY_ORIGINAL)
1857 					&& (aliens[i].flags & FL_FRIEND)) ?
1858 				SP_ARROW_FRIEND_EAST : SP_ARROW_EAST);
1859 	}
1860 	else if (aliens[i].y + aliens[i].image[0]->h < 0)
1861 		arrow = (((game.difficulty != DIFFICULTY_ORIGINAL)
1862 				&& (aliens[i].flags & FL_FRIEND)) ?
1863 			SP_ARROW_FRIEND_NORTH : SP_ARROW_NORTH);
1864 	else if (aliens[i].y > screen->h)
1865 		arrow = (((game.difficulty != DIFFICULTY_ORIGINAL)
1866 				&& (aliens[i].flags & FL_FRIEND)) ?
1867 			SP_ARROW_FRIEND_SOUTH : SP_ARROW_SOUTH);
1868 
1869 	if (arrow != -1)
1870 	{
1871 		if (game.difficulty == DIFFICULTY_ORIGINAL)
1872 		{
1873 			indicator = SP_INDICATOR_TARGET;
1874 			indicatorX = (screen->w / 2) - (gfx_sprites[indicator]->w / 2);
1875 			indicatorY = 120;
1876 			arrowX = indicatorX + gfx_sprites[indicator]->w / 2 - (gfx_sprites[arrow]->w / 2);
1877 			arrowY = indicatorY + gfx_sprites[indicator]->h + 5;
1878 			screen_blit(gfx_sprites[arrow], arrowX, arrowY);
1879 			screen_blit(gfx_sprites[indicator], indicatorX, indicatorY);
1880 		}
1881 		else
1882 		{
1883 			arrowX = aliens[i].x + aliens[i].image[0]->w / 2 - gfx_sprites[arrow]->w;
1884 			arrowX = MAX(0, MIN(arrowX, screen->w - gfx_sprites[arrow]->w));
1885 			arrowY = aliens[i].y + aliens[i].image[0]->h / 2 - gfx_sprites[arrow]->h;
1886 			arrowY = MAX(0, MIN(arrowY, screen->h - gfx_sprites[arrow]->h));
1887 			screen_blit(gfx_sprites[arrow], arrowX, arrowY);
1888 
1889 			if (i == ALIEN_SID)
1890 				indicator = SP_INDICATOR_SID;
1891 			else if (i == ALIEN_PHOEBE)
1892 				indicator = SP_INDICATOR_PHOEBE;
1893 			else if (i == ALIEN_URSULA)
1894 				indicator = SP_INDICATOR_URSULA;
1895 			else if (i == ALIEN_KLINE)
1896 				indicator = SP_INDICATOR_KLINE;
1897 			else if (i == engine.targetIndex)
1898 				indicator = SP_INDICATOR_TARGET;
1899 
1900 			if (indicator != -1)
1901 			{
1902 				indicatorX = arrowX + gfx_sprites[arrow]->w / 2 - gfx_sprites[indicator]->w / 2;
1903 				indicatorX = MAX(indicatorX, gfx_sprites[arrow]->w + 5);
1904 				indicatorX = MIN(indicatorX, screen->w - gfx_sprites[arrow]->w - gfx_sprites[indicator]->w - 5);
1905 				indicatorY = arrowY + gfx_sprites[arrow]->h / 2 - gfx_sprites[indicator]->h / 2;
1906 				indicatorY = MAX(indicatorY, gfx_sprites[arrow]->h + 5);
1907 				indicatorY = MIN(indicatorY, screen->h - gfx_sprites[arrow]->h - gfx_sprites[indicator]->h - 5);
1908 				screen_blit(gfx_sprites[indicator], indicatorX, indicatorY);
1909 			}
1910 		}
1911 	}
1912 }
1913 
game_doHud()1914 static void game_doHud()
1915 {
1916 	static int last_arrow = -1;
1917 	int shieldColor = 0;
1918 	SDL_Rect bar;
1919 	SDL_Rect bar_foutline;
1920 	SDL_Rect bar_fcolor;
1921 	int fontColor;
1922 	int tTextIndex;
1923 	char text[STRMAX_SHORT];
1924 	float nbars; // A float for the sake of float division
1925 	float min_shield, max_shield;
1926 	float shield_pct;
1927 	int i;
1928 	int c;
1929 
1930 	screen_addBuffer(0, 20, screen->w, 25);
1931 	screen_addBuffer(0, screen->h - 50, screen->w, 34);
1932 
1933 	if (engine.minutes > -1)
1934 	{
1935 		if (game.area == MISN_MARS)
1936 		{
1937 			fontColor = FONT_WHITE;
1938 		}
1939 		else
1940 		{
1941 			if ((engine.minutes == 0) && (engine.seconds <= 29))
1942 				fontColor = FONT_RED;
1943 			else if ((engine.minutes == 0) && (engine.seconds > 29))
1944 				fontColor = FONT_YELLOW;
1945 			else
1946 				fontColor = FONT_WHITE;
1947 		}
1948 
1949 		/// Each "%.2d" must be retained.  They are replaced with the minutes and seconds left
1950 		/// to complete the mission, respectively (or, in the case of the Mars mission, the
1951 		/// minutes and seconds, respectively, until the mission is completed).
1952 		/// If you are familiar with C string formatting, they can be modified as long as
1953 		/// the "d" type remains.  For example, you can replace "%.2d" with "%d" to allow the
1954 		/// timer to use single-digit numbers.
1955 		/// The ":" can also be replaced just like any text.  For example, this would be fine:
1956 		///     "Time Remaining - %d minutes and %d seconds"
1957 		snprintf(text, STRMAX_SHORT, _("Time Remaining - %.2d:%.2d"), engine.minutes, engine.seconds);
1958 		gfx_createTextObject(TS_TIME, text, 0, 0, fontColor);
1959 		screen_blitText(TS_TIME, screen->w / 2 - gfx_textSprites[TS_TIME].image->w / 2, 20);
1960 	}
1961 
1962 	if (game.area != MISN_INTERCEPTION)
1963 	{
1964 		/// "%d" must be retained.  It is replaced with the number of mission objectives remaining.
1965 		snprintf(text, STRMAX_SHORT, _("Objectives Remaining: %d"), (mission.remainingObjectives1 + mission.remainingObjectives2));
1966 		gfx_createTextObject(TS_OBJECTIVES, text, 0, 0, FONT_WHITE);
1967 		screen_blitText(TS_OBJECTIVES, screen->w - gfx_textSprites[TS_OBJECTIVES].image->w - 25, 20);
1968 	}
1969 
1970 	/// "%d" must be retained.  It is replaced with the player's current total cash.
1971 	snprintf(text, STRMAX_SHORT, _("Cash: $%d"), game.cash);
1972 	gfx_createTextObject(TS_CASH, text, 0, 0, FONT_WHITE);
1973 	screen_blitText(TS_CASH, 25, 20);
1974 
1975 	if (game.difficulty == DIFFICULTY_ORIGINAL)
1976 	{
1977 		i = engine.targetIndex;
1978 		if ((i >= 0) && aliens[i].active && (aliens[i].shield > 0) && (!(aliens[i].flags & FL_ISCLOAKED)))
1979 			game_doArrow(i);
1980 		else
1981 		{
1982 			i = last_arrow;
1983 			if ((i >= 0) && aliens[i].active && (aliens[i].shield > 0) && (!(aliens[i].flags & FL_ISCLOAKED)))
1984 				game_doArrow(i);
1985 			else
1986 			{
1987 				last_arrow = rand() % ALIEN_MAX;
1988 				if (aliens[last_arrow].flags & FL_FRIEND)
1989 					last_arrow = 0;
1990 
1991 				game_doArrow(last_arrow);
1992 			}
1993 		}
1994 	}
1995 	else
1996 	{
1997 		for (i = 0; i < ALIEN_MAX; i++)
1998 			game_doArrow(i);
1999 	}
2000 
2001 	fontColor = FONT_WHITE;
2002 	if (player.ammo[0] > 0)
2003 	{
2004 		if (player.ammo[0] <= 25) fontColor = FONT_YELLOW;
2005 		if (player.ammo[0] <= 10) fontColor = FONT_RED;
2006 	}
2007 	/// "%.3d" must be retained.  It is replaced with the amount of plasma ammo.
2008 	snprintf(text, STRMAX_SHORT, _("Plasma: %.3d"), player.ammo[0]);
2009 	gfx_createTextObject(TS_PLASMA, text, 0, 0, fontColor);
2010 	screen_blitText(TS_PLASMA, screen->w * 5 / 16, screen->h - 50);
2011 
2012 	if (player.weaponType[1] == W_CHARGER)
2013 	{
2014 		/// Used to indicate the charge meter for the charger cannon in the HUD.
2015 		gfx_createTextObject(TS_AMMO, _("Charge"), 0, 0, FONT_WHITE);
2016 	}
2017 	else if (player.weaponType[1] == W_LASER)
2018 	{
2019 		/// Used to indicate the heat meter for the laser cannon in the HUD.
2020 		gfx_createTextObject(TS_AMMO, _("Heat"), 0, 0, FONT_WHITE);
2021 	}
2022 	else
2023 	{
2024 		/// "%.2d" must be retained.  It is replaced with the amount of rocket ammo.
2025 		snprintf(text, STRMAX_SHORT, _("Rockets: %.2d"), player.ammo[1]);
2026 		gfx_createTextObject(TS_AMMO, text, 0, 0, FONT_WHITE);
2027 	}
2028 	screen_blitText(TS_AMMO, screen->w / 2, screen->h - 50);
2029 
2030 	if (((player.weaponType[1] == W_CHARGER) || (player.weaponType[1] == W_LASER)) && (player.ammo[1] > 0))
2031 	{
2032 		c = white;
2033 		if (player.ammo[1] > 100)
2034 			c = red;
2035 
2036 		bar.x = screen->w / 2 + gfx_textSprites[TS_AMMO].image->w + 10;
2037 		bar.y = screen->h - 50;
2038 		bar.h = 12;
2039 
2040 		for (i = 0 ; i < (player.ammo[1] / 5) ; i++)
2041 		{
2042 			bar.w = MAX(screen->w / 800, 1);
2043 			SDL_FillRect(screen, &bar, c);
2044 			bar.x += bar.w + (screen->w / 800);
2045 		}
2046 	}
2047 
2048 	if ((!mission_checkCompleted()) && (SDL_GetTicks() >= engine.counter2))
2049 	{
2050 		engine.timeTaken++;
2051 		engine.counter2 = SDL_GetTicks() + 1000;
2052 		if (engine.missionCompleteTimer == 0)
2053 			events_check();
2054 	}
2055 
2056 	if ((engine.timeMission) && (player.shield > 0)
2057 			&& ((!engine.cheatTime) || (game.area == MISN_MARS)))
2058 	{
2059 		if (SDL_GetTicks() >= engine.counter)
2060 		{
2061 			if ((engine.seconds > 1) && (engine.seconds <= 11) && (engine.minutes == 0))
2062 			{
2063 				audio_playSound(SFX_CLOCK, screen->w / 2, screen->h / 2);
2064 			}
2065 
2066 			if (engine.seconds > 0)
2067 			{
2068 				engine.seconds--;
2069 				engine.counter = (SDL_GetTicks() + 1000);
2070 			}
2071 			else if ((engine.seconds == 0) && (engine.minutes > 0))
2072 			{
2073 				engine.minutes--;
2074 				engine.seconds = 59;
2075 				engine.counter = (SDL_GetTicks() + 1000);
2076 				for (int i = 0 ; i < 3 ; i++)
2077 				{
2078 					if (mission.timeLimit1[i] > -1)
2079 						mission.timeLimit1[i]--;
2080 					if (mission.timeLimit2[i] > -1)
2081 						mission.timeLimit2[i]--;
2082 				}
2083 				mission_checkTimer();
2084 				events_check();
2085 			}
2086 
2087 			if ((engine.seconds == 0) && (engine.minutes == 0))
2088 			{
2089 				for (int i = 0 ; i < 3 ; i++)
2090 				{
2091 					if (mission.timeLimit1[i] > -1)
2092 						mission.timeLimit1[i]--;
2093 					if (mission.timeLimit2[i] > -1)
2094 						mission.timeLimit2[i]--;
2095 				}
2096 				mission_checkTimer();
2097 				events_check();
2098 				engine.counter = (SDL_GetTicks() + 1000);
2099 			}
2100 		}
2101 	}
2102 
2103 	for (i = 0 ; i < MAX_INFOLINES ; i++)
2104 	{
2105 		if (gfx_textSprites[i].life > 0)
2106 		{
2107 			screen_blitText(i, -1, screen->h - 75 - (i * MENU_SPACING));
2108 			gfx_textSprites[i].life--;
2109 
2110 			if (gfx_textSprites[i].life == 0)
2111 			{
2112 				for (int j = i ; j < MAX_INFOLINES - 1 ; j++)
2113 				{
2114 					info_copyLine(j + 1, j);
2115 				}
2116 				gfx_textSprites[MAX_INFOLINES - 1].life = 0;
2117 			}
2118 		}
2119 	}
2120 
2121 	// Show the radio message if there is one
2122 	if ((gfx_textSprites[TS_RADIO].life > 0) && (gfx_messageBox != NULL))
2123 	{
2124 		screen_blit(gfx_messageBox, (screen->w - gfx_messageBox->w) / 2, 50);
2125 		gfx_textSprites[TS_RADIO].life--;
2126 	}
2127 
2128 	// Do the target's remaining shield (if required)
2129 	if (game.area != MISN_DORIM)
2130 	{
2131 		if ((engine.targetIndex > -1) && (aliens[engine.targetIndex].shield > 0)
2132 				&& (engine.targetIndex > engine.maxAliens))
2133 		{
2134 			if (game.difficulty == DIFFICULTY_ORIGINAL)
2135 			{
2136 				tTextIndex = TS_TARGET;
2137 			}
2138 			else
2139 			{
2140 				if (engine.targetIndex == ALIEN_SID)
2141 					tTextIndex = TS_TARGET_SID;
2142 				else if (engine.targetIndex == ALIEN_PHOEBE)
2143 					tTextIndex = TS_TARGET_PHOEBE;
2144 				else if (engine.targetIndex == ALIEN_KLINE)
2145 					tTextIndex = TS_TARGET_KLINE;
2146 				else
2147 					tTextIndex = TS_TARGET;
2148 			}
2149 
2150 			screen_blitText(tTextIndex, screen->w * 11 / 16, screen->h - 50);
2151 
2152 			bar.w = MAX(screen->w / 800, 1);
2153 			bar.h = 12;
2154 			bar.x = screen->w * 11 / 16 + gfx_textSprites[tTextIndex].image->w + 10;
2155 			bar.y = screen->h - 50;
2156 			nbars = 85.;
2157 			if (engine.targetIndex == ALIEN_KLINE
2158 					&& game.difficulty == DIFFICULTY_ORIGINAL)
2159 			{
2160 				if (game.area == MISN_ELAMALE)
2161 				{
2162 					max_shield = aliens[engine.targetIndex].maxShield;
2163 					min_shield = max_shield - KLINE_SHIELD_MEDIUM;
2164 				}
2165 				else if (game.area == MISN_EARTH)
2166 				{
2167 					max_shield = aliens[engine.targetIndex].maxShield;
2168 					min_shield = max_shield - KLINE_SHIELD_SMALL;
2169 				}
2170 				else if (game.area == MISN_VENUS)
2171 				{
2172 					if (aliens[ALIEN_KLINE].shield > KLINE_STAGE1_SHIELD)
2173 					{
2174 						max_shield = aliens[engine.targetIndex].maxShield;
2175 						min_shield = KLINE_STAGE1_SHIELD;
2176 					}
2177 					else if (aliens[ALIEN_KLINE].shield > KLINE_STAGE2_SHIELD)
2178 					{
2179 						max_shield = KLINE_STAGE1_SHIELD;
2180 						min_shield = KLINE_STAGE2_SHIELD;
2181 					}
2182 					else if (aliens[ALIEN_KLINE].shield > KLINE_STAGE3_SHIELD)
2183 					{
2184 						max_shield = KLINE_STAGE2_SHIELD;
2185 						min_shield = KLINE_STAGE3_SHIELD;
2186 					}
2187 					else
2188 					{
2189 						max_shield = KLINE_STAGE3_SHIELD;
2190 						min_shield = 0;
2191 					}
2192 				}
2193 				else
2194 				{
2195 					max_shield = aliens[engine.targetIndex].maxShield;
2196 					min_shield = max_shield - KLINE_SHIELD_TINY;
2197 				}
2198 
2199 				if (min_shield > 0)
2200 					shield_pct = (
2201 						MAX((float)aliens[engine.targetIndex].shield - min_shield, 1.)
2202 							/ (max_shield-min_shield));
2203 				else
2204 					shield_pct = (
2205 						((float)aliens[engine.targetIndex].shield - min_shield)
2206 							/ (max_shield-min_shield));
2207 			}
2208 			else
2209 			{
2210 				shield_pct = ((float)aliens[engine.targetIndex].shield
2211 					/ (float)aliens[engine.targetIndex].maxShield);
2212 			}
2213 
2214 			for (i = 0 ; i < nbars ; i++)
2215 			{
2216 				if (i / nbars > 2. / 3)
2217 					shieldColor = green;
2218 				else if (i / nbars > 1. / 3)
2219 					shieldColor = yellow;
2220 				else
2221 					shieldColor = red;
2222 
2223 				if (i / nbars <= shield_pct)
2224 				{
2225 					SDL_FillRect(screen, &bar, shieldColor);
2226 					bar.x += bar.w + (screen->w / 800);
2227 				}
2228 				else
2229 				{
2230 					break;
2231 				}
2232 			}
2233 		}
2234 	}
2235 
2236 	screen_blitText(TS_POWER, screen->w / 32, screen->h - 30);
2237 
2238 	bar.w = screen->w / 32;
2239 	bar.h = 12;
2240 	bar.x = screen->w / 32 + gfx_textSprites[TS_POWER].image->w + 10;
2241 	bar.y = screen->h - 29;
2242 
2243 	bar_foutline.x = bar.x + 2;
2244 	bar_foutline.y = bar.y + 2;
2245 	bar_foutline.w = bar.w - 4;
2246 	bar_foutline.h = bar.h - 4;
2247 
2248 	bar_fcolor.x = bar_foutline.x + 2;
2249 	bar_fcolor.y = bar_foutline.y + 2;
2250 	bar_fcolor.w = bar_foutline.w - 4;
2251 	bar_fcolor.h = bar_foutline.h - 4;
2252 
2253 	for (int i = 1 ; i <= 5 ; i++)
2254 	{
2255 		if (weapons[W_PLAYER_WEAPON].damage >= i) {
2256 			if(i <= game.maxPlasmaDamage || (SDL_GetTicks() % 1000 > (unsigned)i * 100))
2257 			{
2258 				SDL_FillRect(screen, &bar, darkGreen);
2259 				SDL_FillRect(screen, &bar_foutline, green);
2260 				SDL_FillRect(screen, &bar_fcolor, lightGreen);
2261 			}
2262 		} else if (i <= game.maxPlasmaDamage)
2263 			SDL_FillRect(screen, &bar, darkGreen);
2264 
2265 		bar.x += screen->w * 3 / 80;
2266 		bar_foutline.x = bar.x + 2;
2267 		bar_fcolor.x = bar_foutline.x + 2;
2268 	}
2269 
2270 	screen_blitText(TS_OUTPUT, screen->w * 5 / 16, screen->h - 30);
2271 
2272 	bar.w = screen->w / 32;
2273 	bar.h = 12;
2274 	bar.x = screen->w * 5 / 16 + gfx_textSprites[TS_OUTPUT].image->w + 10;
2275 	bar.y = screen->h - 29;
2276 
2277 	bar_foutline.x = bar.x + 2;
2278 	bar_foutline.y = bar.y + 2;
2279 	bar_foutline.w = bar.w - 4;
2280 	bar_foutline.h = bar.h - 4;
2281 
2282 	bar_fcolor.x = bar_foutline.x + 2;
2283 	bar_fcolor.y = bar_foutline.y + 2;
2284 	bar_fcolor.w = bar_foutline.w - 4;
2285 	bar_fcolor.h = bar_foutline.h - 4;
2286 
2287 	for (int i = 1 ; i <= 5 ; i++)
2288 	{
2289 		if (weapons[W_PLAYER_WEAPON].ammo[0] >= i)
2290 		{
2291 			if (i <= game.maxPlasmaOutput || (SDL_GetTicks() % 1000 > (unsigned)i * 100))
2292 			{
2293 				SDL_FillRect(screen, &bar, darkYellow);
2294 				SDL_FillRect(screen, &bar_foutline, yellow);
2295 				SDL_FillRect(screen, &bar_fcolor, lightYellow);
2296 			}
2297 		}
2298 		else if (i <= game.maxPlasmaOutput)
2299 			SDL_FillRect(screen, &bar, darkYellow);
2300 
2301 		bar.x += screen->w * 3 / 80;
2302 		bar_foutline.x = bar.x + 2;
2303 		bar_fcolor.x = bar_foutline.x + 2;
2304 	}
2305 
2306 	screen_blitText(TS_COOLER, screen->w * 97 / 160, screen->h - 30);
2307 
2308 	bar.w = screen->w / 32;
2309 	bar.h = 12;
2310 	bar.x = screen->w * 97 / 160 + gfx_textSprites[TS_COOLER].image->w + 10;
2311 	bar.y = screen->h - 29;
2312 
2313 	bar_foutline.x = bar.x + 2;
2314 	bar_foutline.y = bar.y + 2;
2315 	bar_foutline.w = bar.w - 4;
2316 	bar_foutline.h = bar.h - 4;
2317 
2318 	bar_fcolor.x = bar_foutline.x + 2;
2319 	bar_fcolor.y = bar_foutline.y + 2;
2320 	bar_fcolor.w = bar_foutline.w - 4;
2321 	bar_fcolor.h = bar_foutline.h - 4;
2322 
2323 	for (int i = 1 ; i <= 5 ; i++)
2324 	{
2325 		if (weapons[W_PLAYER_WEAPON].reload[0] <= rate2reload[i])
2326 		{
2327 			if (i <= game.maxPlasmaRate || (SDL_GetTicks() % 1000 > (unsigned)i * 100))
2328 			{
2329 				SDL_FillRect(screen, &bar, darkBlue);
2330 				SDL_FillRect(screen, &bar_foutline, lightBlue);
2331 				SDL_FillRect(screen, &bar_fcolor, lighterBlue);
2332 			}
2333 		}
2334 		else if (i <= game.maxPlasmaRate)
2335 			SDL_FillRect(screen, &bar, darkBlue);
2336 
2337 		bar.x += screen->w * 3 / 80;
2338 		bar_foutline.x = bar.x + 2;
2339 		bar_fcolor.x = bar_foutline.x + 2;
2340 	}
2341 
2342 	screen_blitText(TS_SHIELD, screen->w / 32, screen->h - 50);
2343 	if (player.shield < 1)
2344 		return;
2345 
2346 	if ((player.weaponType[1] == W_LASER) && (engine.eventTimer % 8 == 1))
2347 		LIMIT_ADD(player.ammo[1], -1, 0, 200);
2348 
2349 	if ((engine.eventTimer < 30) && (player.shield <= engine.lowShield))
2350 		return;
2351 
2352 	int blockSize = MAX(screen->w / 800, 1);
2353 
2354 	bar.w = blockSize;
2355 	bar.h = 12;
2356 	bar.x = screen->w / 32 + gfx_textSprites[TS_SHIELD].image->w + 10;
2357 	bar.y = screen->h - 50;
2358 
2359 	for (int i = 0 ; i < player.shield ; i += blockSize)
2360 	{
2361 		if (i >= engine.averageShield)
2362 			shieldColor = green;
2363 		else if ((i >= engine.lowShield) && (i < engine.averageShield))
2364 			shieldColor = yellow;
2365 		else
2366 			shieldColor = red;
2367 		SDL_FillRect(screen, &bar, shieldColor);
2368 		bar.x += blockSize;
2369 		if (player.maxShield <= 75 || screen->w >= 1200)
2370 			bar.x += screen->w / 800;
2371 	}
2372 }
2373 
2374 /*
2375  * Delay until the next 60 Hz frame
2376  */
game_delayFrame()2377 void game_delayFrame()
2378 {
2379 	Uint32 now = SDL_GetTicks();
2380 
2381 	// Add 16 2/3 (= 1000 / 60) to frameLimit
2382 	frameLimit += 16;
2383 	thirds += 2;
2384 	while (thirds >= 3)
2385 	{
2386 		thirds -= 3;
2387 		frameLimit++;
2388 	}
2389 
2390 	if(now < frameLimit)
2391 		SDL_Delay(frameLimit - now);
2392 	else
2393 		frameLimit = now;
2394 }
2395 
2396 /*
2397 Checked during the main game loop. When the game is paused
2398 it goes into a constant loop checking this routine. If escape is
2399 pressed, the game automatically ends and goes back to the title screen
2400 */
game_checkPauseRequest()2401 static int game_checkPauseRequest()
2402 {
2403 	player_getInput();
2404 
2405 	if (engine.keyState[KEY_ESCAPE])
2406 	{
2407 		engine.paused = 0;
2408 		player.shield = 0;
2409 		return 1;
2410 	}
2411 
2412 	if (engine.keyState[KEY_PAUSE])
2413 	{
2414 		engine.paused = 0;
2415 		engine.keyState[KEY_PAUSE] = 0;
2416 	}
2417 
2418 	return 0;
2419 }
2420 
game_collision(float x0,float y0,int w0,int h0,float x2,float y2,int w1,int h1)2421 int game_collision(float x0, float y0, int w0, int h0, float x2, float y2, int w1, int h1)
2422 {
2423 	float x1 = x0 + w0;
2424 	float y1 = y0 + h0;
2425 
2426 	float x3 = x2 + w1;
2427 	float y3 = y2 + h1;
2428 
2429 	return !(x1<x2 || x3<x0 || y1<y2 || y3<y0);
2430 }
2431 
2432 /*
2433 The game over screen :(
2434 */
game_showGameOver()2435 static void game_showGameOver()
2436 {
2437 	screen_flushBuffer();
2438 	gfx_free();
2439 	SDL_FillRect(gfx_background, NULL, black);
2440 
2441 	engine.keyState[KEY_FIRE] = engine.keyState[KEY_ALTFIRE] = 0;
2442 	engine.gameSection = SECTION_INTERMISSION;
2443 
2444 	SDL_Surface *gameover = gfx_loadImage("gfx/gameover.png");
2445 
2446 	screen_clear(black);
2447 	renderer_update();
2448 	screen_clear(black);
2449 	SDL_Delay(1000);
2450 
2451 #ifdef OLD_MUSIC
2452 	audio_playMusic("music/Wybierak.mod", -1);
2453 #else
2454 	audio_playMusic("music/death.ogg", -1);
2455 #endif
2456 
2457 	int x = (screen->w - gameover->w) / 2;
2458 	int y = (screen->h - gameover->h) / 2;
2459 
2460 	renderer_update();
2461 
2462 	player_flushInput();
2463 	engine.keyState[KEY_FIRE] = engine.keyState[KEY_ALTFIRE] = 0;
2464 
2465 	while (1)
2466 	{
2467 		player_getInput();
2468 
2469 		if (engine.keyState[KEY_FIRE] || engine.keyState[KEY_ALTFIRE])
2470 			break;
2471 
2472 		renderer_update();
2473 
2474 		screen_unBuffer();
2475 		x = ((screen->w - gameover->w) / 2) - RANDRANGE(-2, 2);
2476 		y = ((screen->h - gameover->h) / 2)  - RANDRANGE(-2, 2);
2477 		screen_blit(gameover, x,  y);
2478 
2479 		game_delayFrame();
2480 	}
2481 
2482 	SDL_FreeSurface(gameover);
2483 	audio_haltMusic();
2484 	screen_flushBuffer();
2485 }
2486 
game_getDifficultyText(char * dest,int difficulty)2487 void game_getDifficultyText(char *dest, int difficulty)
2488 {
2489 	switch (difficulty)
2490 	{
2491 		case DIFFICULTY_SUPEREASY:
2492 			/// DIFFICULTY_SUPEREASY
2493 			strcpy(dest, _("Super-Easy"));
2494 			break;
2495 		case DIFFICULTY_EASY:
2496 			/// DIFFICULTY_EASY
2497 			strcpy(dest, _("Easy"));
2498 			break;
2499 		case DIFFICULTY_NORMAL:
2500 			/// DIFFICULTY_NORMAL
2501 			strcpy(dest, _("Normal"));
2502 			break;
2503 		case DIFFICULTY_HARD:
2504 			/// DIFFICULTY_HARD
2505 			strcpy(dest, _("Hard"));
2506 			break;
2507 		case DIFFICULTY_NIGHTMARE:
2508 			/// DIFFICULTY_NIGHTMARE
2509 			strcpy(dest, _("Nightmare!"));
2510 			break;
2511 		case DIFFICULTY_ORIGINAL:
2512 			/// DIFFICULTY_ORIGINAL (Classic)
2513 			strcpy(dest, _("Classic"));
2514 			break;
2515 		default:
2516 			strcpy(dest, "???");
2517 	}
2518 }
2519 
game_mainLoop()2520 int game_mainLoop()
2521 {
2522 	float chance;
2523 
2524 	engine_resetLists();
2525 
2526 	mission_set(game.area);
2527 	mission_showStartScreen();
2528 
2529 	cargo_init();
2530 	player_init();
2531 	aliens_init();
2532 
2533 	// specific for Phoebe being captured!
2534 	if (game.area == MISN_NEROD)
2535 		game.hasWingMate1 = 1;
2536 
2537 	if (game.area == MISN_ELAMALE)
2538 		aliens[ALIEN_KLINE].active = 0;
2539 
2540 	for (int i = 0 ; i < engine.maxAliens ; i++)
2541 		alien_add();
2542 
2543 	if (game.hasWingMate1)
2544 		alien_addFriendly(ALIEN_PHOEBE);
2545 
2546 	if (game.hasWingMate2)
2547 		alien_addFriendly(ALIEN_URSULA);
2548 
2549 	if ((game.area == MISN_URUSOR)
2550 			|| (game.area == MISN_POSWIC)
2551 			|| (game.area == MISN_EARTH))
2552 		alien_addFriendly(ALIEN_SID);
2553 
2554 	// Disable Wingmates for certain missions
2555 	switch (game.area)
2556 	{
2557 		case MISN_NEROD:
2558 		case MISN_URUSOR:
2559 		case MISN_DORIM:
2560 		case MISN_SIVEDI:
2561 		case MISN_ALMARTHA:
2562 		case MISN_ELLESH:
2563 		case MISN_MARS:
2564 		case MISN_VENUS:
2565 			aliens[ALIEN_PHOEBE].active = 0;
2566 			aliens[ALIEN_URSULA].active = 0;
2567 			break;
2568 	}
2569 
2570 	if (game.area == MISN_DORIM)
2571 	{
2572 		aliens[0].collectChance = 100;
2573 		aliens[0].collectType = P_ESCAPEPOD;
2574 		aliens[0].collectTypeOriginal = P_ESCAPEPOD;
2575 	}
2576 
2577 	// Some specifics for interception missions
2578 	if (game.area == MISN_INTERCEPTION)
2579 	{
2580 		if ((game.system > SYSTEM_EYANANTH) && ((rand() % 5) == 0))
2581 		{
2582 			aliens[ALIEN_KLINE] = alien_defs[CD_KLINE];
2583 			aliens[ALIEN_KLINE].owner = &aliens[ALIEN_KLINE];
2584 			aliens[ALIEN_KLINE].target = &player;
2585 			aliens[ALIEN_KLINE].active = 1;
2586 			aliens[ALIEN_KLINE].x = player.x + 1000;
2587 			aliens[ALIEN_KLINE].y = player.y;
2588 			player_setTarget(ALIEN_KLINE);
2589 		}
2590 
2591 		if ((game.system == SYSTEM_MORDOR) && (game.experimentalShield > 0))
2592 		{
2593 			if ((rand() % 5) > 0)
2594 			{
2595 				aliens[ALIEN_BOSS] = alien_defs[CD_CLOAKFIGHTER];
2596 				aliens[ALIEN_BOSS].owner = &aliens[ALIEN_BOSS];
2597 				aliens[ALIEN_BOSS].target = &aliens[ALIEN_BOSS];
2598 				aliens[ALIEN_BOSS].shield = 1000;
2599 				aliens[ALIEN_BOSS].active = 1;
2600 				aliens[ALIEN_BOSS].x = player.x - 1000;
2601 				aliens[ALIEN_BOSS].y = player.y;
2602 				player_setTarget(ALIEN_BOSS);
2603 				aliens[ALIEN_BOSS].shield = game.experimentalShield;
2604 			}
2605 		}
2606 	}
2607 
2608 	if (game.area == MISN_VENUS)
2609 	{
2610 		if (game.difficulty == DIFFICULTY_ORIGINAL)
2611 		{
2612 			aliens[ALIEN_KLINE].flags |= FL_IMMORTAL | FL_NOFIRE | FL_NOMOVE;
2613 			aliens[ALIEN_KLINE].x = screen->w * 3 / 4;
2614 			aliens[ALIEN_KLINE].y = screen->h / 2;
2615 		}
2616 		else
2617 		{
2618 			aliens[ALIEN_KLINE].x = player.x + 1000;
2619 			aliens[ALIEN_KLINE].y = player.y;
2620 		}
2621 	}
2622 
2623 	for (int i = 0 ; i < ALIEN_MAX ; i++)
2624 	{
2625 		aliens[i].systemPower = aliens[i].maxShield;
2626 		aliens[i].deathCounter = 0 - (aliens[i].maxShield * 3);
2627 		LIMIT(aliens[i].deathCounter, -350, 0);
2628 		alien_nerf(i);
2629 	}
2630 
2631 	// Set target energy meter
2632 	switch (game.area)
2633 	{
2634 		case MISN_MOEBO:
2635 		case MISN_ELAMALE:
2636 		case MISN_ODEON:
2637 		case MISN_FELLON:
2638 		case MISN_ELLESH:
2639 		case MISN_PLUTO:
2640 		case MISN_NEPTUNE:
2641 		case MISN_URANUS:
2642 		case MISN_JUPITER:
2643 			player_setTarget(ALIEN_BOSS);
2644 			break;
2645 		case MISN_NEROD:
2646 			player_setTarget(ALIEN_PHOEBE);
2647 			break;
2648 		case MISN_ALLEZ:
2649 			player_setTarget(ALIEN_FRIEND1);
2650 			break;
2651 		case MISN_URUSOR:
2652 			player_setTarget(ALIEN_SID);
2653 			break;
2654 		case MISN_POSWIC:
2655 			if (game.difficulty == DIFFICULTY_ORIGINAL)
2656 				player_setTarget(ALIEN_BOSS);
2657 			else
2658 				player_setTarget(ALIEN_SID);
2659 			break;
2660 		case MISN_EARTH:
2661 		case MISN_VENUS:
2662 			player_setTarget(ALIEN_KLINE);
2663 			break;
2664 	}
2665 
2666 	info_clearLines();
2667 
2668 	events_init();
2669 
2670 	engine.ssx = 0;
2671 	engine.ssy = 0;
2672 	engine.smx = 0;
2673 	engine.smy = 0;
2674 
2675 	engine.done = ENGINE_RUNNING;
2676 
2677 	engine.counter = (SDL_GetTicks() + 1000);
2678 	engine.counter2 = (SDL_GetTicks() + 1000);
2679 
2680 	engine.missionCompleteTimer = 0;
2681 	engine.musicVolume = 100;
2682 
2683 	int rtn = 0;
2684 
2685 	int allowableAliens = 999999999;
2686 
2687 	for (int i = 0 ; i < 3 ; i++)
2688 	{
2689 		if ((mission.primaryType[i] == M_DESTROY_TARGET_TYPE)
2690 				&& (mission.target1[i] == CD_ANY))
2691 			allowableAliens = mission.targetValue1[i];
2692 
2693 		if (mission.primaryType[i] == M_DESTROY_ALL_TARGETS)
2694 			allowableAliens = 999999999;
2695 	}
2696 
2697 	for (int i = 0 ; i < ALIEN_MAX ; i++)
2698 	{
2699 		if ((aliens[i].active) && (aliens[i].flags & FL_WEAPCO))
2700 		{
2701 			allowableAliens--;
2702 		}
2703 	}
2704 
2705 	screen_drawBackground();
2706 	screen_flushBuffer();
2707 
2708 	// Default to no aliens dead...
2709 	engine.allAliensDead = 0;
2710 
2711 	engine.keyState[KEY_FIRE] = 0;
2712 	engine.keyState[KEY_ALTFIRE] = 0;
2713 	player_flushInput();
2714 
2715 	while (engine.done != ENGINE_CLOSING)
2716 	{
2717 		renderer_update();
2718 
2719 		if ((mission_checkCompleted()) && (engine.missionCompleteTimer == 0))
2720 		{
2721 			engine.missionCompleteTimer = SDL_GetTicks() + 4000;
2722 		}
2723 
2724 		if ((mission_checkFailed()) && (engine.missionCompleteTimer == 0))
2725 		{
2726 			if (game.area != MISN_MOEBO)
2727 				engine.missionCompleteTimer = SDL_GetTicks() + 4000;
2728 		}
2729 
2730 		if (engine.missionCompleteTimer != 0)
2731 		{
2732 			engine.gameSection = SECTION_INTERMISSION;
2733 			if (player.shield > 0)
2734 			{
2735 				if ((SDL_GetTicks() >= engine.missionCompleteTimer)
2736 						&& ((game.difficulty == DIFFICULTY_ORIGINAL)
2737 							|| (game.difficulty == DIFFICULTY_NIGHTMARE)
2738 							|| (game.area == MISN_INTERCEPTION)
2739 							|| (game.area == MISN_ELLESH)
2740 							|| (game.area == MISN_MARS)
2741 							|| (mission_checkFailed())
2742 							|| (collectable_numGood() <= 0)
2743 							|| (engine.done == ENGINE_SYSEXIT)))
2744 				{
2745 					if ((!mission_checkFailed()) && (game.area != MISN_VENUS))
2746 					{
2747 						player_leaveSector();
2748 						if ((engine.done == ENGINE_SYSEXIT)
2749 								&& (game.area != MISN_DORIM)
2750 								&& (game.area != MISN_SIVEDI))
2751 						{
2752 							if ((aliens[ALIEN_PHOEBE].shield > 0)
2753 									&& (game.area != MISN_EARTH))
2754 							{
2755 								aliens[ALIEN_PHOEBE].x = player.x - 40;
2756 								aliens[ALIEN_PHOEBE].y = player.y - 35;
2757 								aliens[ALIEN_PHOEBE].face = 0;
2758 							}
2759 
2760 							if ((aliens[ALIEN_URSULA].shield > 0)
2761 									&& (game.area != MISN_EARTH))
2762 							{
2763 								aliens[ALIEN_URSULA].x = player.x - 40;
2764 								aliens[ALIEN_URSULA].y = player.y + 45;
2765 								aliens[ALIEN_URSULA].face = 0;
2766 							}
2767 
2768 							if ((game.area == MISN_URUSOR)
2769 									|| (game.area == MISN_POSWIC))
2770 							{
2771 								aliens[ALIEN_SID].x = player.x - 100;
2772 								aliens[ALIEN_SID].y = player.y;
2773 								aliens[ALIEN_SID].face = 0;
2774 							}
2775 						}
2776 					}
2777 					else if ((game.area == MISN_VENUS)
2778 							&& (engine.musicVolume > 0))
2779 					{
2780 						player_getInput();
2781 						engine.keyState[KEY_UP] = 0;
2782 						engine.keyState[KEY_DOWN] = 0;
2783 						engine.keyState[KEY_LEFT] = 0;
2784 						engine.keyState[KEY_RIGHT] = 0;
2785 						engine.keyState[KEY_FIRE] = 0;
2786 						engine.keyState[KEY_ALTFIRE] = 0;
2787 						engine.xaxis = 0;
2788 						engine.yaxis = 0;
2789 						LIMIT_ADD(engine.musicVolume, -0.2, 0, 100);
2790 						audio_setMusicVolume(engine.musicVolume);
2791 					}
2792 					else
2793 					{
2794 						engine.done = ENGINE_CLOSING;
2795 					}
2796 				}
2797 				else
2798 				{
2799 					player_getInput();
2800 				}
2801 			}
2802 			else
2803 			{
2804 				player_getInput();
2805 				LIMIT_ADD(engine.musicVolume, -0.2, 0, 100);
2806 				audio_setMusicVolume(engine.musicVolume);
2807 				if (SDL_GetTicks() >= engine.missionCompleteTimer)
2808 				{
2809 					engine.done = ENGINE_CLOSING;
2810 				}
2811 			}
2812 		}
2813 		else
2814 		{
2815 			player_getInput();
2816 		}
2817 
2818 		screen_unBuffer();
2819 		game_doStars();
2820 		game_doCollectables();
2821 		game_doBullets();
2822 		game_doAliens();
2823 		game_doPlayer();
2824 		game_doCargo();
2825 		game_doDebris();
2826 		game_doExplosions();
2827 		game_doHud();
2828 
2829 		// Start delaying damage again gradually
2830 		if (player_damageDelay > 0)
2831 		{
2832 			if (player_resetDamageDelay)
2833 			{
2834 				player_damageDelay--;
2835 			}
2836 			else
2837 			{
2838 				player_resetDamageDelay = 1;
2839 			}
2840 		}
2841 
2842 		WRAP_ADD(engine.eventTimer, -1, 0, 60);
2843 
2844 		if (engine.paused)
2845 		{
2846 			gfx_createTextObject(TS_PAUSED, "PAUSED", 0, 0, FONT_WHITE);
2847 			audio_pauseMusic();
2848 
2849 			while (engine.paused)
2850 			{
2851 				screen_blitText(TS_PAUSED, -1, screen->h / 2);
2852 				renderer_update();
2853 				engine.done = game_checkPauseRequest();
2854 				game_delayFrame();
2855 			}
2856 
2857 			if (engine.done == ENGINE_RUNNING)
2858 				audio_resumeMusic();
2859 		}
2860 
2861 		if ((game.area == MISN_MARS) && (engine.addAliens > -1))
2862 		{
2863 			if (game.difficulty == DIFFICULTY_SUPEREASY)
2864 				chance = 1. / 20.;
2865 			else
2866 				chance = 1. / 5.;
2867 
2868 			if (CHANCE(chance))
2869 				// Note: The originally specified range for x was [800, 100],
2870 				// which with the original rrand function caused the result
2871 				// returned to be `800 + rand() % -699`. Clearly a mistake,
2872 				// but I'm not entirely sure what the original intention was.
2873 				// I've set the range to [800, 1500] (modified for screen
2874 				// width), which approximately replicates the original's results.
2875 				collectable_add(screen->w + RANDRANGE(0, 700),
2876 					RANDRANGE(-screen->h / 3, (4 * screen->h) / 3), P_MINE, 25,
2877 					180 * screen->w / 800 + RANDRANGE(0, 60));
2878 		}
2879 
2880 		if (engine.addAliens > -1)
2881 		{
2882 			WRAP_ADD(engine.addAliens, -1, 0, mission.addAliens);
2883 			if ((engine.addAliens == 0) && (allowableAliens > 0))
2884 			{
2885 				allowableAliens -= alien_add();
2886 			}
2887 		}
2888 
2889 		if ((player.shield <= 0) && (engine.missionCompleteTimer == 0))
2890 			engine.missionCompleteTimer = SDL_GetTicks() + 7000;
2891 
2892 		// specific to Boss 1
2893 		if ((game.area == MISN_MOEBO)
2894 				&& (aliens[ALIEN_BOSS].flags & FL_ESCAPED))
2895 		{
2896 			audio_playSound(SFX_DEATH, aliens[ALIEN_BOSS].x, aliens[ALIEN_BOSS].y);
2897 			screen_clear(white);
2898 			renderer_update();
2899 			for (int i = 0 ; i < 300 ; i++)
2900 			{
2901 				SDL_Delay(10);
2902 				if ((rand() % 25) == 0)
2903 					audio_playSound(SFX_EXPLOSION, aliens[ALIEN_BOSS].x, aliens[ALIEN_BOSS].y);
2904 			}
2905 			SDL_Delay(1000);
2906 			break;
2907 		}
2908 
2909 		game_delayFrame();
2910 	}
2911 
2912 	screen_flushBuffer();
2913 
2914 	if ((player.shield > 0) && (!mission_checkFailed()))
2915 	{
2916 		if (game.area < MISN_VENUS)
2917 			mission_showFinishedScreen();
2918 
2919 		switch (game.area)
2920 		{
2921 			case MISN_MOEBO:
2922 				cutscene_init(1);
2923 				cutscene_init(2);
2924 				break;
2925 			case MISN_NEROD:
2926 				cutscene_init(3);
2927 				break;
2928 			case MISN_ELAMALE:
2929 				cutscene_init(4);
2930 				break;
2931 			case MISN_ODEON:
2932 				cutscene_init(5);
2933 				break;
2934 			case MISN_ELLESH:
2935 				cutscene_init(6);
2936 				break;
2937 			case MISN_VENUS:
2938 				title_showCredits();
2939 				break;
2940 		}
2941 
2942 		if (game.area < MISN_VENUS)
2943 		{
2944 			intermission_updateSystemStatus();
2945 			save(0);
2946 		}
2947 
2948 		rtn = 1;
2949 
2950 		if (game.area == MISN_VENUS)
2951 			rtn = 0;
2952 	}
2953 	else
2954 	{
2955 		game_showGameOver();
2956 		rtn = 0;
2957 	}
2958 
2959 	player_exit();
2960 
2961 	return rtn;
2962 }
2963