2400ed0a22624fd93194a59c9e7c1f12d270a7c4
[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             "  --disable-sfx                Disable sound effects\n"
228             "  --disable-music              Disable music\n"
229             "  -h, --help                   Show this help message and quit\n"
230             "  -v, --version                Show SuperTux version and quit\n"
231             "  --console                    Enable ingame scripting console\n"
232             "  --noconsole                  Disable ingame scripting console\n"
233             "  --show-fps                   Display framerate in levels\n"
234             "  --no-show-fps                Do not display framerate in levels\n"
235             "  --record-demo FILE LEVEL     Record a demo to FILE\n"
236             "  --play-demo FILE LEVEL       Play a recorded demo\n"
237             "  -s, --debug-scripts          Enable script debugger.\n"
238             "%s\n"), "");
239 }
240
241 /**
242  * Options that should be evaluated prior to any initializations at all go here
243  */
244 static bool pre_parse_commandline(int argc, char** argv)
245 {
246   for(int i = 1; i < argc; ++i) {
247     std::string arg = argv[i];
248
249     if(arg == "--version" || arg == "-v") {
250       std::cout << PACKAGE_NAME << " " << PACKAGE_VERSION << std::endl;
251       return true;
252     }
253     if(arg == "--help" || arg == "-h") {
254       print_usage(argv[0]);
255       return true;
256     }
257   }
258
259   return false;
260 }
261
262 /**
263  * Options that should be evaluated after config is read go here
264  */
265 static bool parse_commandline(int argc, char** argv)
266 {
267   for(int i = 1; i < argc; ++i) {
268     std::string arg = argv[i];
269
270     if(arg == "--fullscreen" || arg == "-f") {
271       config->use_fullscreen = true;
272     } else if(arg == "--default" || arg == "-d") {
273       config->use_fullscreen = false;
274       
275       config->window_width  = 800;
276       config->window_height = 600;
277
278       config->fullscreen_width  = 800;
279       config->fullscreen_height = 600;
280
281       config->aspect_width  = 0;  // auto detect
282       config->aspect_height = 0;
283       
284     } else if(arg == "--window" || arg == "-w") {
285       config->use_fullscreen = false;
286     } else if(arg == "--geometry" || arg == "-g") {
287       i += 1;
288       if(i >= argc) 
289         {
290           print_usage(argv[0]);
291           throw std::runtime_error("Need to specify a size (WIDTHxHEIGHT) for geometry argument");
292         } 
293       else 
294         {
295           int width, height;
296           if (sscanf(argv[i], "%dx%d", &width, &height) != 2)
297             {
298               print_usage(argv[0]);
299               throw std::runtime_error("Invalid geometry spec, should be WIDTHxHEIGHT");
300             }
301           else
302             {
303               config->window_width  = width;
304               config->window_height = height;
305
306               config->fullscreen_width  = width;
307               config->fullscreen_height = height;
308             }
309         }
310     } else if(arg == "--aspect" || arg == "-a") {
311       i += 1;
312       if(i >= argc) 
313         {
314           print_usage(argv[0]);
315           throw std::runtime_error("Need to specify a ratio (WIDTH:HEIGHT) for aspect ratio");
316         } 
317       else 
318         {
319           int aspect_width  = 0;
320           int aspect_height = 0;
321           if (strcmp(argv[i], "auto") == 0)
322             {
323               aspect_width  = 0;
324               aspect_height = 0;
325             }
326           else if (sscanf(argv[i], "%d:%d", &aspect_width, &aspect_height) != 2) 
327             {
328               print_usage(argv[0]);
329               throw std::runtime_error("Invalid aspect spec, should be WIDTH:HEIGHT or auto");
330             }
331           else 
332             {
333               float aspect_ratio = static_cast<double>(config->aspect_width) /
334                 static_cast<double>(config->aspect_height);
335
336               // use aspect ratio to calculate logical resolution
337               if (aspect_ratio > 1) {
338                 config->aspect_width  = static_cast<int> (600 * aspect_ratio + 0.5);
339                 config->aspect_height = 600;
340               } else {
341                 config->aspect_width  = 600;
342                 config->aspect_height = static_cast<int> (600 * 1/aspect_ratio + 0.5);
343               }
344             }
345         }
346     } else if(arg == "--show-fps") {
347       config->show_fps = true;
348     } else if(arg == "--no-show-fps") {
349       config->show_fps = false;
350     } else if(arg == "--console") {
351       config->console_enabled = true;
352     } else if(arg == "--noconsole") {
353       config->console_enabled = false;
354     } else if(arg == "--disable-sfx") {
355       config->sound_enabled = false;
356     } else if(arg == "--disable-music") {
357       config->music_enabled = false;
358     } else if(arg == "--play-demo") {
359       if(i+1 >= argc) {
360         print_usage(argv[0]);
361         throw std::runtime_error("Need to specify a demo filename");
362       }
363       config->start_demo = argv[++i];
364     } else if(arg == "--record-demo") {
365       if(i+1 >= argc) {
366         print_usage(argv[0]);
367         throw std::runtime_error("Need to specify a demo filename");
368       }
369       config->record_demo = argv[++i];
370     } else if(arg == "--debug-scripts" || arg == "-s") {
371       config->enable_script_debugger = true;
372     } else if(arg[0] != '-') {
373       config->start_level = arg;
374     } else {
375       log_warning << "Unknown option '" << arg << "'. Use --help to see a list of options" << std::endl;
376       return true;
377     }
378   }
379
380   return false;
381 }
382
383 static void init_sdl()
384 {
385   if(SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
386     std::stringstream msg;
387     msg << "Couldn't initialize SDL: " << SDL_GetError();
388     throw std::runtime_error(msg.str());
389   }
390   // just to be sure
391   atexit(SDL_Quit);
392
393   SDL_EnableUNICODE(1);
394
395   // wait 100ms and clear SDL event queue because sometimes we have random
396   // joystick events in the queue on startup...
397   SDL_Delay(100);
398   SDL_Event dummy;
399   while(SDL_PollEvent(&dummy))
400       ;
401 }
402
403 static void init_rand()
404 {
405   config->random_seed = systemRandom.srand(config->random_seed);
406
407   //const char *how = config->random_seed? ", user fixed.": ", from time().";
408   //log_info << "Using random seed " << config->random_seed << how << std::endl;
409 }
410
411 void init_video()
412 {
413   // FIXME: Add something here
414   SCREEN_WIDTH  = 800;
415   SCREEN_HEIGHT = 600;
416
417   context_pointer->init_renderer();
418   screen = SDL_GetVideoSurface();
419
420   SDL_WM_SetCaption(PACKAGE_NAME " " PACKAGE_VERSION, 0);
421
422   // set icon
423 #ifdef MACOSX
424   const char* icon_fname = "images/engine/icons/supertux-256x256.png";
425 #else
426   const char* icon_fname = "images/engine/icons/supertux.xpm";
427 #endif
428   SDL_Surface* icon;
429   try {
430     icon = IMG_Load_RW(get_physfs_SDLRWops(icon_fname), true);
431   } catch (const std::runtime_error& err) {
432     icon = 0;
433     log_warning << "Couldn't load icon '" << icon_fname << "': " << err.what() << std::endl;
434   }
435   if(icon != 0) {
436     SDL_WM_SetIcon(icon, 0);
437     SDL_FreeSurface(icon);
438   }
439 #ifdef DEBUG
440   else {
441     log_warning << "Couldn't load icon '" << icon_fname << "'" << std::endl;
442   }
443 #endif
444
445   SDL_ShowCursor(0);
446
447   log_info << (config->use_fullscreen?"fullscreen ":"window ")
448            << " Window: "     << config->window_width     << "x" << config->window_height
449            << " Fullscreen: " << config->fullscreen_width << "x" << config->fullscreen_height
450            << " Area: "       << config->aspect_width     << "x" << config->aspect_height << std::endl;
451 }
452
453 static void init_audio()
454 {
455   sound_manager = new SoundManager();
456
457   sound_manager->enable_sound(config->sound_enabled);
458   sound_manager->enable_music(config->music_enabled);
459 }
460
461 static void quit_audio()
462 {
463   if(sound_manager != NULL) {
464     delete sound_manager;
465     sound_manager = NULL;
466   }
467 }
468
469 void wait_for_event(float min_delay, float max_delay)
470 {
471   assert(min_delay <= max_delay);
472
473   Uint32 min = (Uint32) (min_delay * 1000);
474   Uint32 max = (Uint32) (max_delay * 1000);
475
476   Uint32 ticks = SDL_GetTicks();
477   while(SDL_GetTicks() - ticks < min) {
478     SDL_Delay(10);
479     sound_manager->update();
480   }
481
482   // clear event queue
483   SDL_Event event;
484   while (SDL_PollEvent(&event))
485   {}
486
487   /* Handle events: */
488   bool running = false;
489   ticks = SDL_GetTicks();
490   while(running) {
491     while(SDL_PollEvent(&event)) {
492       switch(event.type) {
493         case SDL_QUIT:
494           main_loop->quit();
495           break;
496         case SDL_KEYDOWN:
497         case SDL_JOYBUTTONDOWN:
498         case SDL_MOUSEBUTTONDOWN:
499           running = false;
500       }
501     }
502     if(SDL_GetTicks() - ticks >= (max - min))
503       running = false;
504     sound_manager->update();
505     SDL_Delay(10);
506   }
507 }
508
509 #ifdef DEBUG
510 static Uint32 last_timelog_ticks = 0;
511 static const char* last_timelog_component = 0;
512
513 static inline void timelog(const char* component)
514 {
515   Uint32 current_ticks = SDL_GetTicks();
516
517   if(last_timelog_component != 0) {
518     log_info << "Component '" << last_timelog_component <<  "' finished after " << (current_ticks - last_timelog_ticks) / 1000.0 << " seconds" << std::endl;
519   }
520
521   last_timelog_ticks = current_ticks;
522   last_timelog_component = component;
523 }
524 #else
525 static inline void timelog(const char* )
526 {
527 }
528 #endif
529
530 int main(int argc, char** argv)
531 {
532   int result = 0;
533
534   try {
535
536     if(pre_parse_commandline(argc, argv))
537       return 0;
538
539     Console::instance = new Console();
540     init_physfs(argv[0]);
541     init_sdl();
542
543     timelog("controller");
544     main_controller = new JoystickKeyboardController();
545
546     timelog("config");
547     init_config();
548
549     timelog("addons");
550     AddonManager::get_instance().load_addons();
551
552     timelog("tinygettext");
553     init_tinygettext();
554
555     timelog("commandline");
556     if(parse_commandline(argc, argv))
557       return 0;
558
559     timelog("audio");
560     init_audio();
561
562     timelog("video");
563     DrawingContext context;
564     context_pointer = &context;
565     init_video();
566
567     Console::instance->init_graphics();
568
569     timelog("scripting");
570     Scripting::init_squirrel(config->enable_script_debugger);
571
572     timelog("resources");
573     load_shared();
574
575     timelog(0);
576
577     main_loop = new MainLoop();
578     if(config->start_level != "") {
579       // we have a normal path specified at commandline, not a physfs path.
580       // So we simply mount that path here...
581       std::string dir = FileSystem::dirname(config->start_level);
582       log_debug << "Adding dir: " << dir << std::endl;
583       PHYSFS_addToSearchPath(dir.c_str(), true);
584
585       if(config->start_level.size() > 4 &&
586               config->start_level.compare(config->start_level.size() - 5, 5, ".stwm") == 0) {
587           init_rand();
588           main_loop->push_screen(new WorldMapNS::WorldMap(
589                       FileSystem::basename(config->start_level)));
590       } else {
591         init_rand();//If level uses random eg. for
592         // rain particles before we do this:
593         std::auto_ptr<GameSession> session (
594                 new GameSession(FileSystem::basename(config->start_level)));
595
596         config->random_seed =session->get_demo_random_seed(config->start_demo);
597         init_rand();//initialise generator with seed from session
598
599         if(config->start_demo != "")
600           session->play_demo(config->start_demo);
601
602         if(config->record_demo != "")
603           session->record_demo(config->record_demo);
604         main_loop->push_screen(session.release());
605       }
606     } else {
607       init_rand();
608       main_loop->push_screen(new TitleScreen());
609     }
610
611     //init_rand(); PAK: this call might subsume the above 3, but I'm chicken!
612     main_loop->run(context);
613   } catch(std::exception& e) {
614     log_fatal << "Unexpected exception: " << e.what() << std::endl;
615     result = 1;
616   } catch(...) {
617     log_fatal << "Unexpected exception" << std::endl;
618     result = 1;
619   }
620
621   delete main_loop;
622   main_loop = NULL;
623
624   unload_shared();
625   quit_audio();
626
627   if(config)
628     config->save();
629   delete config;
630   config = NULL;
631   delete main_controller;
632   main_controller = NULL;
633   delete Console::instance;
634   Console::instance = NULL;
635   Scripting::exit_squirrel();
636   delete texture_manager;
637   texture_manager = NULL;
638   SDL_Quit();
639   PHYSFS_deinit();
640
641   return result;
642 }