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