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