]> git.jsancho.org Git - lugaru.git/blob - Source/main.cpp
Added braces to all statements with clang-tidy and ran clang-format again
[lugaru.git] / Source / main.cpp
1 /*
2 Copyright (C) 2003, 2010 - Wolfire Games
3 Copyright (C) 2010-2016 - Lugaru contributors (see AUTHORS file)
4
5 This file is part of Lugaru.
6
7 Lugaru is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 Lugaru is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Lugaru.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "Game.hpp"
22
23 #include "Audio/openal_wrapper.hpp"
24 #include "Graphic/gamegl.hpp"
25 #include "MacCompatibility.hpp"
26 #include "User/Settings.hpp"
27
28 #include <fstream>
29 #include <iostream>
30 #include <math.h>
31 #include <set>
32 #include <stdio.h>
33 #include <string.h>
34 #include <zlib.h>
35
36 using namespace Game;
37
38 #ifdef WIN32
39 #include "win-res/resource.hpp"
40 #include <shellapi.h>
41 #include <windows.h>
42 #endif
43
44 extern float multiplier;
45 extern float realmultiplier;
46 extern int slomo;
47 extern bool cellophane;
48 extern float texdetail;
49
50 extern bool freeze;
51 extern bool stillloading;
52 extern int mainmenu;
53
54 extern float slomospeed;
55 extern float slomofreq;
56
57 extern int difficulty;
58
59 extern SDL_Window* sdlwindow;
60
61 using namespace std;
62
63 set<pair<int, int>> resolutions;
64
65 // statics/globals (internal only) ------------------------------------------
66
67 // Menu defs
68
69 int kContextWidth;
70 int kContextHeight;
71
72 //-----------------------------------------------------------------------------------------------------------------------
73
74 // OpenGL Drawing
75
76 void initGL()
77 {
78     glClear(GL_COLOR_BUFFER_BIT);
79     swap_gl_buffers();
80
81     // clear all states
82     glDisable(GL_ALPHA_TEST);
83     glDisable(GL_BLEND);
84     glDisable(GL_DEPTH_TEST);
85     glDisable(GL_FOG);
86     glDisable(GL_LIGHTING);
87     glDisable(GL_LOGIC_OP);
88     glDisable(GL_TEXTURE_1D);
89     glDisable(GL_TEXTURE_2D);
90     glPixelTransferi(GL_MAP_COLOR, GL_FALSE);
91     glPixelTransferi(GL_RED_SCALE, 1);
92     glPixelTransferi(GL_RED_BIAS, 0);
93     glPixelTransferi(GL_GREEN_SCALE, 1);
94     glPixelTransferi(GL_GREEN_BIAS, 0);
95     glPixelTransferi(GL_BLUE_SCALE, 1);
96     glPixelTransferi(GL_BLUE_BIAS, 0);
97     glPixelTransferi(GL_ALPHA_SCALE, 1);
98     glPixelTransferi(GL_ALPHA_BIAS, 0);
99
100     // set initial rendering states
101     glShadeModel(GL_SMOOTH);
102     glClearDepth(1.0f);
103     glDepthFunc(GL_LEQUAL);
104     glDepthMask(GL_TRUE);
105     glEnable(GL_DEPTH_TEST);
106     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
107     glCullFace(GL_FRONT);
108     glEnable(GL_CULL_FACE);
109     glEnable(GL_LIGHTING);
110     glEnable(GL_DITHER);
111     glEnable(GL_COLOR_MATERIAL);
112     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
113     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
114     glAlphaFunc(GL_GREATER, 0.5f);
115
116     if (CanInitStereo(stereomode)) {
117         InitStereo(stereomode);
118     } else {
119         fprintf(stderr, "Failed to initialize stereo, disabling.\n");
120         stereomode = stereoNone;
121     }
122 }
123
124 void toggleFullscreen()
125 {
126     fullscreen = !fullscreen;
127     Uint32 flags = SDL_GetWindowFlags(sdlwindow);
128     if (flags & SDL_WINDOW_FULLSCREEN) {
129         flags &= ~SDL_WINDOW_FULLSCREEN;
130     } else {
131         flags |= SDL_WINDOW_FULLSCREEN;
132     }
133     SDL_SetWindowFullscreen(sdlwindow, flags);
134 }
135
136 SDL_bool sdlEventProc(const SDL_Event& e)
137 {
138     switch (e.type) {
139         case SDL_QUIT:
140             return SDL_FALSE;
141
142         case SDL_WINDOWEVENT:
143             if (e.window.event == SDL_WINDOWEVENT_CLOSE) {
144                 return SDL_FALSE;
145             }
146             break;
147
148         case SDL_MOUSEMOTION:
149             deltah += e.motion.xrel;
150             deltav += e.motion.yrel;
151             break;
152
153         case SDL_KEYDOWN:
154             if ((e.key.keysym.scancode == SDL_SCANCODE_G) &&
155                 (e.key.keysym.mod & KMOD_CTRL)) {
156                 SDL_bool mode = SDL_TRUE;
157                 if ((SDL_GetWindowFlags(sdlwindow) & SDL_WINDOW_FULLSCREEN) == 0) {
158                     mode = (SDL_GetWindowGrab(sdlwindow) ? SDL_FALSE : SDL_TRUE);
159                 }
160                 SDL_SetWindowGrab(sdlwindow, mode);
161                 SDL_SetRelativeMouseMode(mode);
162             } else if ((e.key.keysym.scancode == SDL_SCANCODE_RETURN) && (e.key.keysym.mod & KMOD_ALT)) {
163                 toggleFullscreen();
164             }
165             break;
166     }
167     return SDL_TRUE;
168 }
169
170 // --------------------------------------------------------------------------
171
172 static Point gMidPoint;
173
174 bool SetUp()
175 {
176     LOGFUNC;
177
178     cellophane = 0;
179     texdetail = 4;
180     slomospeed = 0.25;
181     slomofreq = 8012;
182
183     DefaultSettings();
184
185     if (!SDL_WasInit(SDL_INIT_VIDEO)) {
186         if (SDL_Init(SDL_INIT_VIDEO) == -1) {
187             fprintf(stderr, "SDL_Init() failed: %s\n", SDL_GetError());
188             return false;
189         }
190     }
191     if (!LoadSettings()) {
192         fprintf(stderr, "Failed to load config, creating default\n");
193         SaveSettings();
194     }
195
196     if (SDL_GL_LoadLibrary(NULL) == -1) {
197         fprintf(stderr, "SDL_GL_LoadLibrary() failed: %s\n", SDL_GetError());
198         SDL_Quit();
199         return false;
200     }
201
202     for (int displayIdx = 0; displayIdx < SDL_GetNumVideoDisplays(); ++displayIdx) {
203         for (int i = 0; i < SDL_GetNumDisplayModes(displayIdx); ++i) {
204             SDL_DisplayMode mode;
205             if (SDL_GetDisplayMode(displayIdx, i, &mode) == -1) {
206                 continue;
207             }
208             if ((mode.w < 640) || (mode.h < 480)) {
209                 continue; // sane lower limit.
210             }
211             pair<int, int> resolution(mode.w, mode.h);
212             resolutions.insert(resolution);
213         }
214     }
215
216     if (resolutions.empty()) {
217         const std::string error = "No suitable video resolutions found.";
218         cerr << error << endl;
219         SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Lugaru init failed!", error.c_str(), NULL);
220         SDL_Quit();
221         return false;
222     }
223
224     if (commandLineOptions[SHOWRESOLUTIONS]) {
225         printf("Available resolutions:\n");
226         for (auto resolution = resolutions.begin(); resolution != resolutions.end(); resolution++) {
227             printf("  %d x %d\n", (int)resolution->first, (int)resolution->second);
228         }
229     }
230
231     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
232     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
233
234     Uint32 sdlflags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
235     if (commandLineOptions[FULLSCREEN]) {
236         fullscreen = commandLineOptions[FULLSCREEN].last()->type();
237     }
238     if (fullscreen) {
239         sdlflags |= SDL_WINDOW_FULLSCREEN;
240     }
241     if (!commandLineOptions[NOMOUSEGRAB].last()->type()) {
242         sdlflags |= SDL_WINDOW_INPUT_GRABBED;
243     }
244
245     sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
246                                  kContextWidth, kContextHeight, sdlflags);
247
248     if (!sdlwindow) {
249         fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
250         fprintf(stderr, "forcing 640x480...\n");
251         kContextWidth = 640;
252         kContextHeight = 480;
253         sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
254                                      kContextWidth, kContextHeight, sdlflags);
255         if (!sdlwindow) {
256             fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
257             fprintf(stderr, "forcing 640x480 windowed mode...\n");
258             sdlflags &= ~SDL_WINDOW_FULLSCREEN;
259             sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
260                                          kContextWidth, kContextHeight, sdlflags);
261
262             if (!sdlwindow) {
263                 fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
264                 return false;
265             }
266         }
267     }
268
269     SDL_GLContext glctx = SDL_GL_CreateContext(sdlwindow);
270     if (!glctx) {
271         fprintf(stderr, "SDL_GL_CreateContext() failed: %s\n", SDL_GetError());
272         SDL_Quit();
273         return false;
274     }
275
276     SDL_GL_MakeCurrent(sdlwindow, glctx);
277
278     int dblbuf = 0;
279     if ((SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &dblbuf) == -1) || (!dblbuf)) {
280         fprintf(stderr, "Failed to get a double-buffered context.\n");
281         SDL_Quit();
282         return false;
283     }
284
285     if (SDL_GL_SetSwapInterval(-1) == -1) { // try swap_tear first.
286         SDL_GL_SetSwapInterval(1);
287     }
288
289     SDL_ShowCursor(0);
290     if (!commandLineOptions[NOMOUSEGRAB].last()->type()) {
291         SDL_SetRelativeMouseMode(SDL_TRUE);
292     }
293
294     initGL();
295
296     GLint width = kContextWidth;
297     GLint height = kContextHeight;
298     gMidPoint.h = width / 2;
299     gMidPoint.v = height / 2;
300     screenwidth = width;
301     screenheight = height;
302
303     newdetail = detail;
304     newscreenwidth = screenwidth;
305     newscreenheight = screenheight;
306
307     /* If saved resolution is not in the list, add it to the list (so that it’s selectable in the options) */
308     pair<int, int> startresolution(width, height);
309     if (resolutions.find(startresolution) == resolutions.end()) {
310         resolutions.insert(startresolution);
311     }
312
313     InitGame();
314
315     return true;
316 }
317
318 static void DoMouse()
319 {
320
321     if (mainmenu || ((abs(deltah) < 10 * realmultiplier * 1000) && (abs(deltav) < 10 * realmultiplier * 1000))) {
322         deltah *= usermousesensitivity;
323         deltav *= usermousesensitivity;
324         mousecoordh += deltah;
325         mousecoordv += deltav;
326         if (mousecoordh < 0) {
327             mousecoordh = 0;
328         } else if (mousecoordh >= kContextWidth) {
329             mousecoordh = kContextWidth - 1;
330         }
331         if (mousecoordv < 0) {
332             mousecoordv = 0;
333         } else if (mousecoordv >= kContextHeight) {
334             mousecoordv = kContextHeight - 1;
335         }
336     }
337 }
338
339 void DoFrameRate(int update)
340 {
341     static long frames = 0;
342
343     static AbsoluteTime time = { 0, 0 };
344     static AbsoluteTime frametime = { 0, 0 };
345     AbsoluteTime currTime = UpTime();
346     double deltaTime = (float)AbsoluteDeltaToDuration(currTime, frametime);
347
348     if (0 > deltaTime) { // if negative microseconds
349         deltaTime /= -1000000.0;
350     } else { // else milliseconds
351         deltaTime /= 1000.0;
352     }
353
354     multiplier = deltaTime;
355     if (multiplier < .001) {
356         multiplier = .001;
357     }
358     if (multiplier > 10) {
359         multiplier = 10;
360     }
361     if (update) {
362         frametime = currTime; // reset for next time interval
363     }
364
365     deltaTime = (float)AbsoluteDeltaToDuration(currTime, time);
366
367     if (0 > deltaTime) { // if negative microseconds
368         deltaTime /= -1000000.0;
369     } else { // else milliseconds
370         deltaTime /= 1000.0;
371     }
372     frames++;
373     if (0.001 <= deltaTime) { // has update interval passed
374         if (update) {
375             time = currTime; // reset for next time interval
376             frames = 0;
377         }
378     }
379 }
380
381 void DoUpdate()
382 {
383     static float sps = 200;
384     static int count;
385     static float oldmult;
386
387     DoFrameRate(1);
388     if (multiplier > .6) {
389         multiplier = .6;
390     }
391
392     fps = 1 / multiplier;
393
394     count = multiplier * sps;
395     if (count < 2) {
396         count = 2;
397     }
398
399     realmultiplier = multiplier;
400     multiplier *= gamespeed;
401     if (difficulty == 1) {
402         multiplier *= .9;
403     }
404     if (difficulty == 0) {
405         multiplier *= .8;
406     }
407
408     if (loading == 4) {
409         multiplier *= .00001;
410     }
411     if (slomo && !mainmenu) {
412         multiplier *= slomospeed;
413     }
414     oldmult = multiplier;
415     multiplier /= (float)count;
416
417     DoMouse();
418
419     TickOnce();
420
421     for (int i = 0; i < count; i++) {
422         Tick();
423     }
424     multiplier = oldmult;
425
426     TickOnceAfter();
427     /* - Debug code to test how many channels were active on average per frame
428         static long frames = 0;
429
430         static AbsoluteTime start = {0,0};
431         AbsoluteTime currTime = UpTime ();
432         static int num_channels = 0;
433
434         num_channels += OPENAL_GetChannelsPlaying();
435         double deltaTime = (float) AbsoluteDeltaToDuration (currTime, start);
436
437         if (0 > deltaTime)  // if negative microseconds
438             deltaTime /= -1000000.0;
439         else                // else milliseconds
440             deltaTime /= 1000.0;
441
442         ++frames;
443
444         if (deltaTime >= 1)
445         {
446             start = currTime;
447             float avg_channels = (float)num_channels / (float)frames;
448
449             ofstream opstream("log.txt",ios::app);
450             opstream << "Average frame count: ";
451             opstream << frames;
452             opstream << " frames - ";
453             opstream << avg_channels;
454             opstream << " per frame.\n";
455             opstream.close();
456
457             frames = 0;
458             num_channels = 0;
459         }
460     */
461     if (stereomode == stereoNone) {
462         DrawGLScene(stereoCenter);
463     } else {
464         DrawGLScene(stereoLeft);
465         DrawGLScene(stereoRight);
466     }
467 }
468
469 // --------------------------------------------------------------------------
470
471 void CleanUp(void)
472 {
473     LOGFUNC;
474
475     delete[] commandLineOptionsBuffer;
476
477     SDL_Quit();
478 }
479
480 // --------------------------------------------------------------------------
481
482 static bool IsFocused()
483 {
484     return ((SDL_GetWindowFlags(sdlwindow) & SDL_WINDOW_INPUT_FOCUS) != 0);
485 }
486
487 #ifndef WIN32
488 // (code lifted from physfs: http://icculus.org/physfs/ ... zlib license.)
489 static char* findBinaryInPath(const char* bin, char* envr)
490 {
491     size_t alloc_size = 0;
492     char* exe = NULL;
493     char* start = envr;
494     char* ptr;
495
496     do {
497         size_t size;
498         ptr = strchr(start, ':'); /* find next $PATH separator. */
499         if (ptr) {
500             *ptr = '\0';
501         }
502         size = strlen(start) + strlen(bin) + 2;
503         if (size > alloc_size) {
504             char* x = (char*)realloc(exe, size);
505             if (x == NULL) {
506                 if (exe != NULL) {
507                     free(exe);
508                 }
509                 return (NULL);
510             } /* if */
511
512             alloc_size = size;
513             exe = x;
514         } /* if */
515
516         /* build full binary path... */
517         strcpy(exe, start);
518         if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/')) {
519             strcat(exe, "/");
520         }
521         strcat(exe, bin);
522
523         if (access(exe, X_OK) == 0) { /* Exists as executable? We're done. */
524             strcpy(exe, start);       /* i'm lazy. piss off. */
525             return (exe);
526         } /* if */
527
528         start = ptr + 1; /* start points to beginning of next element. */
529     } while (ptr != NULL);
530
531     if (exe != NULL) {
532         free(exe);
533     }
534
535     return (NULL); /* doesn't exist in path. */
536 } /* findBinaryInPath */
537
538 char* calcBaseDir(const char* argv0)
539 {
540     /* If there isn't a path on argv0, then look through the $PATH for it. */
541     char* retval;
542     char* envr;
543
544     if (strchr(argv0, '/')) {
545         retval = strdup(argv0);
546         if (retval) {
547             *((char*)strrchr(retval, '/')) = '\0';
548         }
549         return (retval);
550     }
551
552     envr = getenv("PATH");
553     if (!envr) {
554         return NULL;
555     }
556     envr = strdup(envr);
557     if (!envr) {
558         return NULL;
559     }
560     retval = findBinaryInPath(argv0, envr);
561     free(envr);
562     return (retval);
563 }
564
565 static inline void chdirToAppPath(const char* argv0)
566 {
567     char* dir = calcBaseDir(argv0);
568     if (dir) {
569 #if (defined(__APPLE__) && defined(__MACH__))
570         // Chop off /Contents/MacOS if it's at the end of the string, so we
571         //  land in the base of the app bundle.
572         const size_t len = strlen(dir);
573         const char* bundledirs = "/Contents/MacOS";
574         const size_t bundledirslen = strlen(bundledirs);
575         if (len > bundledirslen) {
576             char* ptr = (dir + len) - bundledirslen;
577             if (strcasecmp(ptr, bundledirs) == 0)
578                 *ptr = '\0';
579         }
580 #endif
581         chdir(dir);
582         free(dir);
583     }
584 }
585 #endif
586
587 const option::Descriptor usage[] =
588     {
589       { UNKNOWN, 0, "", "", option::Arg::None, "USAGE: lugaru [options]\n\n"
590                                                "Options:" },
591       { HELP, 0, "h", "help", option::Arg::None, " -h, --help        Print usage and exit." },
592       { FULLSCREEN, 1, "f", "fullscreen", option::Arg::None, " -f, --fullscreen  Start the game in fullscreen mode." },
593       { FULLSCREEN, 0, "w", "windowed", option::Arg::None, " -w, --windowed    Start the game in windowed mode (default)." },
594       { NOMOUSEGRAB, 1, "", "nomousegrab", option::Arg::None, " --nomousegrab     Disable mousegrab." },
595       { NOMOUSEGRAB, 0, "", "mousegrab", option::Arg::None, " --mousegrab       Enable mousegrab (default)." },
596       { SOUND, 1, "", "nosound", option::Arg::None, " --nosound         Disable sound." },
597       { OPENALINFO, 0, "", "openal-info", option::Arg::None, " --openal-info     Print info about OpenAL at launch." },
598       { SHOWRESOLUTIONS, 0, "", "showresolutions", option::Arg::None, " --showresolutions List the resolutions found by SDL at launch." },
599       { DEVTOOLS, 0, "d", "devtools", option::Arg::None, " -d, --devtools    Enable dev tools: console, level editor and debug info." },
600       { 0, 0, 0, 0, 0, 0 }
601     };
602
603 option::Option commandLineOptions[commandLineOptionsNumber];
604 option::Option* commandLineOptionsBuffer;
605
606 int main(int argc, char** argv)
607 {
608     argc -= (argc > 0);
609     argv += (argc > 0); // skip program name argv[0] if present
610     option::Stats stats(true, usage, argc, argv);
611     if (commandLineOptionsNumber != stats.options_max) {
612         std::cerr << "Found incorrect command line option number" << std::endl;
613         return 1;
614     }
615     commandLineOptionsBuffer = new option::Option[stats.buffer_max];
616     option::Parser parse(true, usage, argc, argv, commandLineOptions, commandLineOptionsBuffer);
617
618     if (parse.error()) {
619         delete[] commandLineOptionsBuffer;
620         return 1;
621     }
622
623     if (commandLineOptions[HELP]) {
624         option::printUsage(std::cout, usage);
625         delete[] commandLineOptionsBuffer;
626         return 0;
627     }
628
629     if (option::Option* opt = commandLineOptions[UNKNOWN]) {
630         std::cerr << "Unknown option: " << opt->name << "\n";
631         option::printUsage(std::cerr, usage);
632         delete[] commandLineOptionsBuffer;
633         return 1;
634     }
635
636 // !!! FIXME: we could use a Win32 API for this.  --ryan.
637 #ifndef WIN32
638     chdirToAppPath(argv[0]);
639 #endif
640
641     LOGFUNC;
642
643 #ifdef NDEBUG
644     try {
645 #endif
646         {
647             newGame();
648
649             if (!SetUp()) {
650                 delete[] commandLineOptionsBuffer;
651                 return 42;
652             }
653
654             if (commandLineOptions[DEVTOOLS]) {
655                 devtools = true;
656             }
657
658             bool gameDone = false;
659             bool gameFocused = true;
660
661             while (!gameDone && !tryquit) {
662                 if (IsFocused()) {
663                     gameFocused = true;
664
665                     // check windows messages
666
667                     deltah = 0;
668                     deltav = 0;
669                     SDL_Event e;
670                     if (!waiting) {
671                         // message pump
672                         while (SDL_PollEvent(&e)) {
673                             if (!sdlEventProc(e)) {
674                                 gameDone = true;
675                                 break;
676                             }
677                         }
678                     }
679
680                     // game
681                     DoUpdate();
682                 } else {
683                     if (gameFocused) {
684                         // allow game chance to pause
685                         gameFocused = false;
686                         DoUpdate();
687                     }
688
689                     // game is not in focus, give CPU time to other apps by waiting for messages instead of 'peeking'
690                     SDL_WaitEvent(0);
691                 }
692             }
693
694             deleteGame();
695         }
696
697         CleanUp();
698
699         return 0;
700 #ifdef NDEBUG
701     } catch (const std::exception& error) {
702         CleanUp();
703
704         std::string e = "Caught exception: ";
705         e += error.what();
706
707         LOG(e);
708
709         SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Exception catched", error.what(), NULL);
710
711         return -1;
712     }
713 #endif
714 }