47a40f4c3cfa07c1450ae609caa954946031a0c8
[supertux.git] / src / main.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
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 2
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, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19 //  02111-1307, USA.
20 #include <config.h>
21 #include <version.h>
22 #include <assert.h>
23
24 #include "log.hpp"
25 #include "main.hpp"
26
27 #include <stdexcept>
28 #include <sstream>
29 #include <ctime>
30 #include <cstdlib>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34 #include <physfs.h>
35 #include <SDL.h>
36 #include <SDL_image.h>
37
38 #ifdef MACOSX
39 namespace supertux_apple {
40 #include <CoreFoundation/CoreFoundation.h>
41 }
42 #endif
43
44 #include "gameconfig.hpp"
45 #include "resources.hpp"
46 #include "gettext.hpp"
47 #include "audio/sound_manager.hpp"
48 #include "video/surface.hpp"
49 #include "video/texture_manager.hpp"
50 #include "video/drawing_context.hpp"
51 #include "video/glutil.hpp"
52 #include "control/joystickkeyboardcontroller.hpp"
53 #include "options_menu.hpp"
54 #include "mainloop.hpp"
55 #include "title.hpp"
56 #include "game_session.hpp"
57 #include "scripting/level.hpp"
58 #include "scripting/squirrel_util.hpp"
59 #include "file_system.hpp"
60 #include "physfs/physfs_sdl.hpp"
61 #include "random_generator.hpp"
62 #include "worldmap/worldmap.hpp"
63 #include "addon/addon_manager.hpp"
64 #include "binreloc/binreloc.h"
65
66 namespace { DrawingContext *context_pointer; }
67 SDL_Surface *screen;
68 JoystickKeyboardController* main_controller = 0;
69 TinyGetText::DictionaryManager dictionary_manager;
70
71 int SCREEN_WIDTH;
72 int SCREEN_HEIGHT;
73
74 static void init_config()
75 {
76   config = new Config();
77   try {
78     config->load();
79   } catch(std::exception& e) {
80     log_info << "Couldn't load config file: " << e.what() << ", using default settings" << std::endl;
81   }
82 }
83
84 static void init_tinygettext()
85 {
86   dictionary_manager.add_directory("locale");
87   dictionary_manager.set_charset("UTF-8");
88
89   // Config setting "locale" overrides language detection
90   if (config->locale != "") {
91     dictionary_manager.set_language( config->locale );
92   }
93 }
94
95 static void init_physfs(const char* argv0)
96 {
97   if(!PHYSFS_init(argv0)) {
98     std::stringstream msg;
99     msg << "Couldn't initialize physfs: " << PHYSFS_getLastError();
100     throw std::runtime_error(msg.str());
101   }
102
103   // allow symbolic links
104   PHYSFS_permitSymbolicLinks(1);
105
106   // Initialize physfs (this is a slightly modified version of
107   // PHYSFS_setSaneConfig
108   const char* application = "supertux2"; //instead of PACKAGE_NAME so we can coexist with MS1
109   const char* userdir = PHYSFS_getUserDir();
110   char* writedir = new char[strlen(userdir) + strlen(application) + 2];
111
112   // Set configuration directory
113   sprintf(writedir, "%s.%s", userdir, application);
114   if(!PHYSFS_setWriteDir(writedir)) {
115     // try to create the directory
116     char* mkdir = new char[strlen(application) + 2];
117     sprintf(mkdir, ".%s", application);
118     if(!PHYSFS_setWriteDir(userdir) || !PHYSFS_mkdir(mkdir)) {
119       std::ostringstream msg;
120       msg << "Failed creating configuration directory '"
121           << writedir << "': " << PHYSFS_getLastError();
122       delete[] writedir;
123       delete[] mkdir;
124       throw std::runtime_error(msg.str());
125     }
126     delete[] mkdir;
127
128     if(!PHYSFS_setWriteDir(writedir)) {
129       std::ostringstream msg;
130       msg << "Failed to use configuration directory '"
131           <<  writedir << "': " << PHYSFS_getLastError();
132       delete[] writedir;
133       throw std::runtime_error(msg.str());
134     }
135   }
136   PHYSFS_addToSearchPath(writedir, 0);
137   delete[] writedir;
138
139   // when started from source dir...
140   std::string dir = PHYSFS_getBaseDir();
141   dir += "/data";
142   std::string testfname = dir;
143   testfname += "/credits.txt";
144   bool sourcedir = false;
145   FILE* f = fopen(testfname.c_str(), "r");
146   if(f) {
147     fclose(f);
148     if(!PHYSFS_addToSearchPath(dir.c_str(), 1)) {
149       log_warning << "Couldn't add '" << dir << "' to physfs searchpath: " << PHYSFS_getLastError() << std::endl;
150     } else {
151       sourcedir = true;
152     }
153   }
154
155 #ifdef MACOSX
156 {
157   using namespace supertux_apple;
158
159   // when started from Application file on Mac OS X...
160   char path[PATH_MAX];
161   CFBundleRef mainBundle = CFBundleGetMainBundle();
162   assert(mainBundle != 0);
163   CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
164   assert(mainBundleURL != 0);
165   CFStringRef pathStr = CFURLCopyFileSystemPath(mainBundleURL, kCFURLPOSIXPathStyle);
166   assert(pathStr != 0);
167   CFStringGetCString(pathStr, path, PATH_MAX, kCFStringEncodingUTF8);
168   CFRelease(mainBundleURL);
169   CFRelease(pathStr);
170
171   dir = std::string(path) + "/Contents/Resources/data";
172   testfname = dir + "/credits.txt";
173   sourcedir = false;
174   f = fopen(testfname.c_str(), "r");
175   if(f) {
176     fclose(f);
177     if(!PHYSFS_addToSearchPath(dir.c_str(), 1)) {
178       log_warning << "Couldn't add '" << dir << "' to physfs searchpath: " << PHYSFS_getLastError() << std::endl;
179     } else {
180       sourcedir = true;
181     }
182   }
183 }
184 #endif
185
186 #ifdef _WIN32
187   PHYSFS_addToSearchPath(".\\data", 1);
188 #endif
189
190   if(!sourcedir) {
191 #if defined(APPDATADIR) || defined(ENABLE_BINRELOC)
192     std::string datadir;
193 #ifdef ENABLE_BINRELOC
194
195     char* dir;
196     br_init (NULL);
197     dir = br_find_data_dir(APPDATADIR);
198     datadir = dir;
199     free(dir);
200
201 #else
202     datadir = APPDATADIR;
203 #endif
204     if(!PHYSFS_addToSearchPath(datadir.c_str(), 1)) {
205       log_warning << "Couldn't add '" << datadir << "' to physfs searchpath: " << PHYSFS_getLastError() << std::endl;
206     }
207 #endif
208   }
209
210   //show search Path
211   char** searchpath = PHYSFS_getSearchPath();
212   for(char** i = searchpath; *i != NULL; i++)
213     log_info << "[" << *i << "] is in the search path" << std::endl;
214   PHYSFS_freeList(searchpath);
215 }
216
217 static void print_usage(const char* argv0)
218 {
219   fprintf(stderr, _("Usage: %s [OPTIONS] [LEVELFILE]\n\n"), argv0);
220   fprintf(stderr,
221           _("Options:\n"
222             "  -f, --fullscreen             Run in fullscreen mode\n"
223             "  -w, --window                 Run in window mode\n"
224             "  -g, --geometry WIDTHxHEIGHT  Run SuperTux in given resolution\n"
225             "  -a, --aspect WIDTH:HEIGHT    Run SuperTux with given aspect ratio\n"
226             "  -d, --default                Reset video settings to default values\n"
227             "  --renderer RENDERER          Use sdl, opengl, or auto to render\n"
228             "  --disable-sfx                Disable sound effects\n"
229             "  --disable-music              Disable music\n"
230             "  -h, --help                   Show this help message and quit\n"
231             "  -v, --version                Show SuperTux version and quit\n"
232             "  --console                    Enable ingame scripting console\n"
233             "  --noconsole                  Disable ingame scripting console\n"
234             "  --show-fps                   Display framerate in levels\n"
235             "  --no-show-fps                Do not display framerate in levels\n"
236             "  --record-demo FILE LEVEL     Record a demo to FILE\n"
237             "  --play-demo FILE LEVEL       Play a recorded demo\n"
238             "  -s, --debug-scripts          Enable script debugger.\n"
239             "%s\n"), "");
240 }
241
242 /**
243  * Options that should be evaluated prior to any initializations at all go here
244  */
245 static bool pre_parse_commandline(int argc, char** argv)
246 {
247   for(int i = 1; i < argc; ++i) {
248     std::string arg = argv[i];
249
250     if(arg == "--version" || arg == "-v") {
251       std::cout << PACKAGE_NAME << " " << PACKAGE_VERSION << std::endl;
252       return true;
253     }
254     if(arg == "--help" || arg == "-h") {
255       print_usage(argv[0]);
256       return true;
257     }
258   }
259
260   return false;
261 }
262
263 /**
264  * Options that should be evaluated after config is read go here
265  */
266 static bool parse_commandline(int argc, char** argv)
267 {
268   for(int i = 1; i < argc; ++i) {
269     std::string arg = argv[i];
270
271     if(arg == "--fullscreen" || arg == "-f") {
272       config->use_fullscreen = true;
273     } else if(arg == "--default" || arg == "-d") {
274       config->use_fullscreen = false;
275       
276       config->window_width  = 800;
277       config->window_height = 600;
278
279       config->fullscreen_width  = 800;
280       config->fullscreen_height = 600;
281
282       config->aspect_width  = 0;  // auto detect
283       config->aspect_height = 0;
284       
285     } else if(arg == "--window" || arg == "-w") {
286       config->use_fullscreen = false;
287     } else if(arg == "--geometry" || arg == "-g") {
288       i += 1;
289       if(i >= argc) 
290         {
291           print_usage(argv[0]);
292           throw std::runtime_error("Need to specify a size (WIDTHxHEIGHT) for geometry argument");
293         } 
294       else 
295         {
296           int width, height;
297           if (sscanf(argv[i], "%dx%d", &width, &height) != 2)
298             {
299               print_usage(argv[0]);
300               throw std::runtime_error("Invalid geometry spec, should be WIDTHxHEIGHT");
301             }
302           else
303             {
304               config->window_width  = width;
305               config->window_height = height;
306
307               config->fullscreen_width  = width;
308               config->fullscreen_height = height;
309             }
310         }
311     } else if(arg == "--aspect" || arg == "-a") {
312       i += 1;
313       if(i >= argc) 
314         {
315           print_usage(argv[0]);
316           throw std::runtime_error("Need to specify a ratio (WIDTH:HEIGHT) for aspect ratio");
317         } 
318       else 
319         {
320           int aspect_width  = 0;
321           int aspect_height = 0;
322           if (strcmp(argv[i], "auto") == 0)
323             {
324               aspect_width  = 0;
325               aspect_height = 0;
326             }
327           else if (sscanf(argv[i], "%d:%d", &aspect_width, &aspect_height) != 2) 
328             {
329               print_usage(argv[0]);
330               throw std::runtime_error("Invalid aspect spec, should be WIDTH:HEIGHT or auto");
331             }
332           else 
333             {
334               float aspect_ratio = static_cast<double>(config->aspect_width) /
335                 static_cast<double>(config->aspect_height);
336
337               // use aspect ratio to calculate logical resolution
338               if (aspect_ratio > 1) {
339                 config->aspect_width  = static_cast<int> (600 * aspect_ratio + 0.5);
340                 config->aspect_height = 600;
341               } else {
342                 config->aspect_width  = 600;
343                 config->aspect_height = static_cast<int> (600 * 1/aspect_ratio + 0.5);
344               }
345             }
346         }
347     } else if(arg == "--renderer") {
348       i += 1;
349       if(i >= argc) 
350         {
351           print_usage(argv[0]);
352           throw std::runtime_error("Need to specify a renderer for renderer argument");
353         } 
354       else 
355         {
356           config->video = get_video_system(argv[i]);
357         }
358     } else if(arg == "--show-fps") {
359       config->show_fps = true;
360     } else if(arg == "--no-show-fps") {
361       config->show_fps = false;
362     } else if(arg == "--console") {
363       config->console_enabled = true;
364     } else if(arg == "--noconsole") {
365       config->console_enabled = false;
366     } else if(arg == "--disable-sfx") {
367       config->sound_enabled = false;
368     } else if(arg == "--disable-music") {
369       config->music_enabled = false;
370     } else if(arg == "--play-demo") {
371       if(i+1 >= argc) {
372         print_usage(argv[0]);
373         throw std::runtime_error("Need to specify a demo filename");
374       }
375       config->start_demo = argv[++i];
376     } else if(arg == "--record-demo") {
377       if(i+1 >= argc) {
378         print_usage(argv[0]);
379         throw std::runtime_error("Need to specify a demo filename");
380       }
381       config->record_demo = argv[++i];
382     } else if(arg == "--debug-scripts" || arg == "-s") {
383       config->enable_script_debugger = true;
384     } else if(arg[0] != '-') {
385       config->start_level = arg;
386     } else {
387       log_warning << "Unknown option '" << arg << "'. Use --help to see a list of options" << std::endl;
388       return true;
389     }
390   }
391
392   return false;
393 }
394
395 static void init_sdl()
396 {
397   if(SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
398     std::stringstream msg;
399     msg << "Couldn't initialize SDL: " << SDL_GetError();
400     throw std::runtime_error(msg.str());
401   }
402   // just to be sure
403   atexit(SDL_Quit);
404
405   SDL_EnableUNICODE(1);
406
407   // wait 100ms and clear SDL event queue because sometimes we have random
408   // joystick events in the queue on startup...
409   SDL_Delay(100);
410   SDL_Event dummy;
411   while(SDL_PollEvent(&dummy))
412       ;
413 }
414
415 static void init_rand()
416 {
417   config->random_seed = systemRandom.srand(config->random_seed);
418
419   //const char *how = config->random_seed? ", user fixed.": ", from time().";
420   //log_info << "Using random seed " << config->random_seed << how << std::endl;
421 }
422
423 void init_video()
424 {
425   // FIXME: Add something here
426   SCREEN_WIDTH  = 800;
427   SCREEN_HEIGHT = 600;
428
429   context_pointer->init_renderer();
430   screen = SDL_GetVideoSurface();
431
432   SDL_WM_SetCaption(PACKAGE_NAME " " PACKAGE_VERSION, 0);
433
434   // set icon
435 #ifdef MACOSX
436   const char* icon_fname = "images/engine/icons/supertux-256x256.png";
437 #else
438   const char* icon_fname = "images/engine/icons/supertux.xpm";
439 #endif
440   SDL_Surface* icon;
441   try {
442     icon = IMG_Load_RW(get_physfs_SDLRWops(icon_fname), true);
443   } catch (const std::runtime_error& err) {
444     icon = 0;
445     log_warning << "Couldn't load icon '" << icon_fname << "': " << err.what() << std::endl;
446   }
447   if(icon != 0) {
448     SDL_WM_SetIcon(icon, 0);
449     SDL_FreeSurface(icon);
450   }
451 #ifdef DEBUG
452   else {
453     log_warning << "Couldn't load icon '" << icon_fname << "'" << std::endl;
454   }
455 #endif
456
457   SDL_ShowCursor(0);
458
459   log_info << (config->use_fullscreen?"fullscreen ":"window ")
460            << " Window: "     << config->window_width     << "x" << config->window_height
461            << " Fullscreen: " << config->fullscreen_width << "x" << config->fullscreen_height
462            << " Area: "       << config->aspect_width     << "x" << config->aspect_height << std::endl;
463 }
464
465 static void init_audio()
466 {
467   sound_manager = new SoundManager();
468
469   sound_manager->enable_sound(config->sound_enabled);
470   sound_manager->enable_music(config->music_enabled);
471 }
472
473 static void quit_audio()
474 {
475   if(sound_manager != NULL) {
476     delete sound_manager;
477     sound_manager = NULL;
478   }
479 }
480
481 void wait_for_event(float min_delay, float max_delay)
482 {
483   assert(min_delay <= max_delay);
484
485   Uint32 min = (Uint32) (min_delay * 1000);
486   Uint32 max = (Uint32) (max_delay * 1000);
487
488   Uint32 ticks = SDL_GetTicks();
489   while(SDL_GetTicks() - ticks < min) {
490     SDL_Delay(10);
491     sound_manager->update();
492   }
493
494   // clear event queue
495   SDL_Event event;
496   while (SDL_PollEvent(&event))
497   {}
498
499   /* Handle events: */
500   bool running = false;
501   ticks = SDL_GetTicks();
502   while(running) {
503     while(SDL_PollEvent(&event)) {
504       switch(event.type) {
505         case SDL_QUIT:
506           main_loop->quit();
507           break;
508         case SDL_KEYDOWN:
509         case SDL_JOYBUTTONDOWN:
510         case SDL_MOUSEBUTTONDOWN:
511           running = false;
512       }
513     }
514     if(SDL_GetTicks() - ticks >= (max - min))
515       running = false;
516     sound_manager->update();
517     SDL_Delay(10);
518   }
519 }
520
521 #ifdef DEBUG
522 static Uint32 last_timelog_ticks = 0;
523 static const char* last_timelog_component = 0;
524
525 static inline void timelog(const char* component)
526 {
527   Uint32 current_ticks = SDL_GetTicks();
528
529   if(last_timelog_component != 0) {
530     log_info << "Component '" << last_timelog_component <<  "' finished after " << (current_ticks - last_timelog_ticks) / 1000.0 << " seconds" << std::endl;
531   }
532
533   last_timelog_ticks = current_ticks;
534   last_timelog_component = component;
535 }
536 #else
537 static inline void timelog(const char* )
538 {
539 }
540 #endif
541
542 int main(int argc, char** argv)
543 {
544   int result = 0;
545
546   try {
547
548     if(pre_parse_commandline(argc, argv))
549       return 0;
550
551     Console::instance = new Console();
552     init_physfs(argv[0]);
553     init_sdl();
554
555     timelog("controller");
556     main_controller = new JoystickKeyboardController();
557
558     timelog("config");
559     init_config();
560
561     timelog("addons");
562     AddonManager::get_instance().load_addons();
563
564     timelog("tinygettext");
565     init_tinygettext();
566
567     timelog("commandline");
568     if(parse_commandline(argc, argv))
569       return 0;
570
571     timelog("audio");
572     init_audio();
573
574     timelog("video");
575     DrawingContext context;
576     context_pointer = &context;
577     init_video();
578
579     Console::instance->init_graphics();
580
581     timelog("scripting");
582     Scripting::init_squirrel(config->enable_script_debugger);
583
584     timelog("resources");
585     load_shared();
586
587     timelog(0);
588
589     main_loop = new MainLoop();
590     if(config->start_level != "") {
591       // we have a normal path specified at commandline, not a physfs path.
592       // So we simply mount that path here...
593       std::string dir = FileSystem::dirname(config->start_level);
594       log_debug << "Adding dir: " << dir << std::endl;
595       PHYSFS_addToSearchPath(dir.c_str(), true);
596
597       if(config->start_level.size() > 4 &&
598               config->start_level.compare(config->start_level.size() - 5, 5, ".stwm") == 0) {
599           init_rand();
600           main_loop->push_screen(new WorldMapNS::WorldMap(
601                       FileSystem::basename(config->start_level)));
602       } else {
603         init_rand();//If level uses random eg. for
604         // rain particles before we do this:
605         std::auto_ptr<GameSession> session (
606                 new GameSession(FileSystem::basename(config->start_level)));
607
608         config->random_seed =session->get_demo_random_seed(config->start_demo);
609         init_rand();//initialise generator with seed from session
610
611         if(config->start_demo != "")
612           session->play_demo(config->start_demo);
613
614         if(config->record_demo != "")
615           session->record_demo(config->record_demo);
616         main_loop->push_screen(session.release());
617       }
618     } else {
619       init_rand();
620       main_loop->push_screen(new TitleScreen());
621     }
622
623     //init_rand(); PAK: this call might subsume the above 3, but I'm chicken!
624     main_loop->run(context);
625   } catch(std::exception& e) {
626     log_fatal << "Unexpected exception: " << e.what() << std::endl;
627     result = 1;
628   } catch(...) {
629     log_fatal << "Unexpected exception" << std::endl;
630     result = 1;
631   }
632
633   delete main_loop;
634   main_loop = NULL;
635
636   unload_shared();
637   quit_audio();
638
639   if(config)
640     config->save();
641   delete config;
642   config = NULL;
643   delete main_controller;
644   main_controller = NULL;
645   delete Console::instance;
646   Console::instance = NULL;
647   Scripting::exit_squirrel();
648   delete texture_manager;
649   texture_manager = NULL;
650   SDL_Quit();
651   PHYSFS_deinit();
652
653   return result;
654 }