1 /*
2 Copyright (C) 2003 Parallel Realities
3 Copyright (C) 2011, 2012, 2013 Guus Sliepen
4 Copyright (C) 2015-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 <errno.h>
21 #include <stdio.h>
22 #include <sys/stat.h>
23 #include <unistd.h>
24
25 #ifdef __HAIKU__
26 #include <FindDirectory.h>
27 #elif !defined(_WIN32)
28 #include <pwd.h>
29 #endif
30
31 #include "SDL.h"
32
33 #ifndef NOFONT
34 #include "SDL_ttf.h"
35 #endif
36
37 #ifndef NOSOUND
38 #include "SDL_mixer.h"
39 #endif
40
41 #include "colors.h"
42 #include "defs.h"
43 #include "structs.h"
44
45 #include "audio.h"
46 #include "collectable.h"
47 #include "engine.h"
48 #include "game.h"
49 #include "gfx.h"
50 #include "player.h"
51 #include "renderer.h"
52 #include "screen.h"
53 #include "window.h"
54
55 Engine engine;
56
engine_init()57 void engine_init()
58 {
59 engine.musicVolume = 100;
60 engine.useAudio = 1;
61
62 engine.maxAliens = 9;
63
64 engine.ssx = 0;
65 engine.ssy = 0;
66 engine.smx = 0;
67 engine.smy = 0;
68
69 engine.bulletHead = malloc(sizeof(*engine.bulletHead));
70 if (engine.bulletHead == NULL)
71 {
72 engine_error("Failed to allocate memory for bullet head.");
73 }
74 engine.bulletHead->next = NULL;
75 engine.bulletTail = engine.bulletHead;
76
77 engine.explosionHead = malloc(sizeof(*engine.explosionHead));
78 if (engine.explosionHead == NULL)
79 {
80 engine_error("Failed to allocate memory for explosion head.");
81 }
82 engine.explosionHead->next = NULL;
83 engine.explosionTail = engine.explosionHead;
84
85 engine.collectableHead = malloc(sizeof(*engine.collectableHead));
86 if (engine.collectableHead == NULL)
87 {
88 engine_error("Failed to allocate memory for collectable head.");
89 }
90 engine.collectableHead->next = NULL;
91 engine.collectableTail = engine.collectableHead;
92
93 engine.debrisHead = malloc(sizeof(*engine.debrisHead));
94 if (engine.debrisHead == NULL)
95 {
96 engine_error("Failed to allocate memory for debris head.");
97 }
98 engine.debrisHead->next = NULL;
99 engine.debrisTail = engine.debrisHead;
100
101 engine.commsSection = 0;
102
103 for (int i = 0; i < KEY_LAST; i++)
104 engine.keyState[i] = 0;
105
106 engine.xaxis = 0;
107 engine.yaxis = 0;
108
109 engine.eventTimer = 0;
110 engine.counter2 = 0;
111 engine.timeTaken = 0;
112 engine.timeMission = 0;
113 engine.counter = 0;
114 engine.seconds = 0;
115 engine.minutes = 0;
116 engine.paused = 0;
117 engine.gameSection = SECTION_TITLE;
118
119 engine.cheat = 0;
120 engine.cheatShield = 0;
121 engine.cheatAmmo = 0;
122 engine.cheatCash = 0;
123 }
124
125 /*
126 Something went wrong. This stops the game, present the error message and
127 prompts the user to press space or ctrl to exit the game. This is unlikely to
128 be seen by people unless something really stoopid happens!
129 */
engine_showError(int errorId,const char * name)130 void engine_showError(int errorId, const char *name)
131 {
132 screen_clear(black);
133
134 if (errorId != 2)
135 {
136 screen_renderString("A file error has occurred", -1, 200, FONT_RED);
137 }
138 else
139 {
140 printf("Couldn't create or write to directory '%s'\n", name);
141 exit(1);
142 }
143
144 char string[STRMAX];
145
146 switch(errorId)
147 {
148 case 0:
149 snprintf(string, STRMAX, "%s was not found in the Starfighter data package", name);
150 screen_renderString(string, -1, 250, FONT_WHITE);
151 screen_renderString("Please try again. If this error persists, contact the authors", -1, 275, FONT_WHITE);
152 screen_renderString("or reinstall the game", -1, 300, FONT_WHITE);
153 break;
154 case 1:
155 screen_renderString("Project: Starfighter encountered an error whilst", -1, 250, FONT_WHITE);
156 screen_renderString("attempting to load game data. Please try running", -1, 275, FONT_WHITE);
157 screen_renderString("the game again. If the errors persist, reinstall the game", -1, 300, FONT_WHITE);
158 break;
159 case 2:
160 screen_renderString("Project: Starfighter encountered a critical error", -1, 250, FONT_WHITE);
161 screen_renderString("while attempting to perform a required program function.", -1, 275, FONT_WHITE);
162 screen_renderString("Please contact the authors with details.", -1, 300, FONT_WHITE);
163 break;
164 }
165
166 screen_renderString("Project: Starfighter will now exit", -1, 450, FONT_WHITE);
167 screen_renderString("Press Space to continue", -1, 475, FONT_WHITE);
168
169 renderer_update();
170
171 engine.keyState[KEY_ALTFIRE] = 0;
172
173 while (!engine.keyState[KEY_ALTFIRE])
174 {
175 player_getInput();
176 game_delayFrame();
177 }
178
179 exit(1);
180 }
181
182 /*
183 Show a warning. Used when non-fatal things go wrong.
184 */
engine_warn(const char * msg)185 void engine_warn(const char *msg)
186 {
187 printf("WARNING: %s\n", msg);
188 }
189
190 /*
191 Show an error and exit. Used for critical errors that should definitely
192 never happen.
193 */
engine_error(const char * msg)194 void engine_error(const char *msg)
195 {
196 printf("ERROR: %s\nAborting\n", msg);
197 exit(1);
198 }
199
200 /*
201 This gets the user's home directory, then creates the config directory.
202 */
engine_setupConfigDirectory()203 void engine_setupConfigDirectory()
204 {
205 const char *userHome;
206 const char *subdir;
207 char dir[PATH_MAX];
208
209 #ifdef _WIN32
210 subdir = "pr-starfighter-config";
211
212 if ((userHome = getenv("APPDATA")) == NULL)
213 userHome = ".";
214
215 snprintf(dir, PATH_MAX, "%s/%s", userHome, subdir);
216 if ((mkdir(dir) != 0) && (errno != EEXIST))
217 engine_showError(2, dir);
218
219 snprintf(engine.configDirectory, PATH_MAX, "%s/", dir);
220 #elif defined(__HAIKU__)
221 subdir = "starfighter";
222
223 char path[PATH_MAX];
224 if (find_directory(B_USER_SETTINGS_DIRECTORY, 0, false, path, PATH_MAX) == B_OK)
225 snprintf(dir, PATH_MAX, "%s/%s", path, subdir);
226
227 if ((mkdir(dir, S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH) != 0) && (errno != EEXIST))
228 engine_showError(2, dir);
229
230 snprintf(engine.configDirectory, PATH_MAX, "%s/", dir);
231 #else
232 subdir = "starfighter";
233
234 if ((userHome = getenv("XDG_CONFIG_HOME")) != NULL)
235 {
236 snprintf(dir, PATH_MAX, "%s/%s", userHome, subdir);
237 }
238 {
239 if ((userHome = getenv("HOME")) == NULL)
240 userHome = getpwuid(getuid())->pw_dir;
241
242 snprintf(dir, PATH_MAX, "%s/.config", userHome);
243 if ((mkdir(dir, S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH) != 0) && (errno != EEXIST))
244 engine_showError(2, dir);
245
246 snprintf(dir, PATH_MAX, "%s/.config/%s", userHome, subdir);
247 }
248
249 if ((mkdir(dir, S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH) != 0) && (errno != EEXIST))
250 engine_showError(2, dir);
251
252 snprintf(engine.configDirectory, PATH_MAX, "%s/", dir);
253 #endif
254 }
255
256 /*
257 This sets up the video and sound system, and creates Starfighter's window.
258 */
engine_setMode()259 void engine_setMode()
260 {
261 char filename[PATH_MAX];
262 int fullScreen = 0;
263 int useSound = 1;
264 int useMusic = 1;
265 int autoPause = 0;
266 int radioLife = DEFAULT_RADIO_LIFE;
267 char lang[STRMAX_SHORT];
268 int i;
269
270 strcpy(lang, "default");
271
272 strcpy(engine.configDirectory, "");
273
274 engine_setupConfigDirectory();
275
276 /* Initialize the SDL library */
277 if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_JOYSTICK|SDL_INIT_GAMECONTROLLER) < 0)
278 {
279 printf("Couldn't initialize SDL: %s\n", SDL_GetError());
280 exit(1);
281 }
282
283 FILE *fp;
284 snprintf(filename, PATH_MAX, "%sconf", engine.configDirectory);
285 fp = fopen(filename, "r");
286
287 if (fp != NULL)
288 {
289 if (fscanf(fp, "%d %d %d %d %d%*c%[^\n]",
290 &fullScreen, &useSound, &useMusic, &autoPause,
291 &radioLife, lang) < 6)
292 printf("Warning: Config file \"%s\" is not correctly formatted\n", filename);
293 fclose(fp);
294 }
295
296 engine.fullScreen = fullScreen;
297 engine.useSound = useSound;
298 engine.useMusic = useMusic;
299 engine.autoPause = autoPause;
300 engine.radioLife = radioLife;
301 strcpy(engine.lang, lang);
302
303 screen_adjustDimensions(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT);
304
305 window = SDL_CreateWindow("Project: Starfighter",
306 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
307 screen->w, screen->h, SDL_WINDOW_RESIZABLE);
308 if (window == NULL)
309 {
310 printf("Could not create window: %s\n", SDL_GetError());
311 exit(1);
312 }
313
314 SDL_SetWindowIcon(window, gfx_loadImage("gfx/alienDevice.png"));
315 SDL_SetWindowFullscreen(window, engine.fullScreen ? FULLSCREEN : 0);
316
317 renderer_reset();
318
319 #ifndef NOSOUND
320 if (engine.useAudio)
321 {
322 if (Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 1024) < 0)
323 {
324 printf("Warning: Couldn't set 44100 Hz 16-bit stereo audio - Reason:\n%s\n", Mix_GetError());
325 printf("Sound and Music will be disabled\n");
326 engine.useAudio = 0;
327 }
328 }
329 #endif
330
331 SDL_ShowCursor(SDL_DISABLE);
332 SDL_EventState(SDL_MOUSEMOTION, SDL_DISABLE);
333
334 // Determine if the GameController API can be used
335 engine.useController = 1;
336 for (i=0; i<SDL_NumJoysticks(); i++) {
337 if (!SDL_IsGameController(i)) {
338 engine.useController = 0;
339 break;
340 }
341 }
342
343 if (engine.useController) {
344 SDL_GameControllerEventState(SDL_ENABLE);
345 }
346 else {
347 SDL_JoystickEventState(SDL_ENABLE);
348 }
349
350 // Open controllers
351 for (i=0; i<SDL_NumJoysticks(); i++) {
352 if (engine.useController)
353 SDL_GameControllerOpen(i);
354 else
355 SDL_JoystickOpen(i);
356 }
357 }
358
engine_setFullscreen(int value)359 void engine_setFullscreen(int value)
360 {
361 engine.fullScreen = value;
362
363 // Clear the screen (prevents artifacts)
364 screen_clear(black);
365 renderer_update();
366 screen_clear(black);
367 screen_addBuffer(0, 0, screen->w, screen->h);
368
369 SDL_SetWindowFullscreen(window, engine.fullScreen ? FULLSCREEN : 0);
370 }
371
engine_resetLists()372 void engine_resetLists()
373 {
374 Object *ob, *ob2;
375 Collectable *c1, *c2;
376 LinkedRect *r1, *r2;
377
378 ob = engine.bulletHead->next;
379 while (ob != NULL)
380 {
381 ob2 = ob;
382 ob = ob->next;
383 free(ob2);
384 }
385 engine.bulletHead->next = NULL;
386 engine.bulletTail = engine.bulletHead;
387
388 ob = engine.explosionHead->next;
389 while (ob != NULL)
390 {
391 ob2 = ob;
392 ob = ob->next;
393 free(ob2);
394 }
395 engine.explosionHead->next = NULL;
396 engine.explosionTail = engine.explosionHead;
397
398 c1 = engine.collectableHead->next;
399 while (c1 != NULL)
400 {
401 c2 = c1;
402 c1 = c1->next;
403 free(c2);
404 }
405
406 engine.collectableHead->next = NULL;
407 engine.collectableTail = engine.collectableHead;
408
409 r1 = screen_bufferHead->next;
410 while (r1 != NULL)
411 {
412 r2 = r1;
413 r1 = r1->next;
414 free(r2);
415 }
416
417 screen_bufferHead->next = NULL;
418 screen_bufferTail = screen_bufferHead;
419
420 ob = engine.debrisHead->next;
421 while (ob != NULL)
422 {
423 ob2 = ob;
424 ob = ob->next;
425 free(ob2);
426 }
427 engine.debrisHead->next = NULL;
428 engine.debrisTail = engine.debrisHead;
429 }
430
431 /*
432 Removes [hopefully] all the resources that has been
433 loaded and created during the game. This is called by
434 atexit();
435 */
engine_cleanup()436 void engine_cleanup()
437 {
438 int i;
439
440 gfx_free();
441 SDL_FreeSurface(gfx_background);
442 SDL_FreeSurface(gfx_unscaledBackground);
443 audio_free();
444 engine_resetLists();
445 free(engine.bulletHead);
446 free(engine.explosionHead);
447 free(engine.collectableHead);
448 free(screen_bufferHead);
449
450 for (i=0 ; i<FONT_MAX ; i++)
451 {
452 if (gfx_fontSprites[i] != NULL)
453 SDL_FreeSurface(gfx_fontSprites[i]);
454 }
455
456 char filename[PATH_MAX];
457 strcpy(filename, "");
458
459 #ifndef NOFONT
460 if (gfx_unicodeFont != NULL)
461 {
462 TTF_CloseFont(gfx_unicodeFont);
463 gfx_unicodeFont = NULL;
464 }
465 if (TTF_WasInit())
466 {
467 TTF_Quit();
468 }
469 #endif
470
471 #ifndef NOSOUND
472 if (engine.useAudio)
473 {
474 Mix_CloseAudio();
475 }
476 #endif
477
478 // Save the config using current settings
479 FILE *fp;
480 snprintf(filename, PATH_MAX, "%sconf", engine.configDirectory);
481 fp = fopen(filename, "w");
482 if (fp != NULL)
483 {
484 fprintf(fp,
485 "%d %d %d %d %d\n"
486 "%s\n",
487 engine.fullScreen, engine.useSound, engine.useMusic,
488 engine.autoPause, engine.radioLife, engine.lang);
489 fclose(fp);
490 }
491 else
492 {
493 printf("Error saving config\n");
494 }
495
496 SDL_Quit();
497 printf("Thank You for playing Starfighter\n");
498 }
499