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