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