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