Console commands can pass arguments
[supertux.git] / src / main.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2005 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 "msg.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 <SDL_opengl.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 "control/joystickkeyboardcontroller.hpp"
47 #include "misc.hpp"
48 #include "mainloop.hpp"
49 #include "title.hpp"
50 #include "game_session.hpp"
51 #include "file_system.hpp"
52 #include "physfs/physfs_sdl.hpp"
53 #include "exceptions.hpp"
54
55 SDL_Surface* screen = 0;
56 JoystickKeyboardController* main_controller = 0;
57 TinyGetText::DictionaryManager dictionary_manager;
58
59 static void init_config()
60 {
61   config = new Config();
62   try {
63     config->load();
64   } catch(std::exception& e) {
65     msg_info << "Couldn't load config file: " << e.what() << ", using default settings" << std::endl;
66   }
67 }
68
69 static void init_tinygettext()
70 {
71   dictionary_manager.add_directory("locale");
72   dictionary_manager.set_charset("UTF-8");
73 }
74
75 static void init_physfs(const char* argv0)
76 {
77   if(!PHYSFS_init(argv0)) {
78     std::stringstream msg;
79     msg << "Couldn't initialize physfs: " << PHYSFS_getLastError();
80     throw std::runtime_error(msg.str());
81   }
82
83   // Initialize physfs (this is a slightly modified version of
84   // PHYSFS_setSaneConfig
85   const char* application = PACKAGE_NAME;
86   const char* userdir = PHYSFS_getUserDir();
87   const char* dirsep = PHYSFS_getDirSeparator();
88   char* writedir = new char[strlen(userdir) + strlen(application) + 2];
89
90   // Set configuration directory
91   sprintf(writedir, "%s.%s", userdir, application);
92   if(!PHYSFS_setWriteDir(writedir)) {
93     // try to create the directory
94     char* mkdir = new char[strlen(application) + 2];
95     sprintf(mkdir, ".%s", application);
96     if(!PHYSFS_setWriteDir(userdir) || !PHYSFS_mkdir(mkdir)) {
97       std::ostringstream msg;
98       msg << "Failed creating configuration directory '" 
99           << writedir << "': " << PHYSFS_getLastError();
100       delete[] writedir;
101       delete[] mkdir;
102       throw std::runtime_error(msg.str());
103     }
104     delete[] mkdir;
105     
106     if(!PHYSFS_setWriteDir(writedir)) {
107       std::ostringstream msg;
108       msg << "Failed to use configuration directory '" 
109           <<  writedir << "': " << PHYSFS_getLastError();
110       delete[] writedir;
111       throw std::runtime_error(msg.str());
112     }
113   }
114   PHYSFS_addToSearchPath(writedir, 0);
115   delete[] writedir;
116
117   // Search for archives and add them to the search path
118   const char* archiveExt = "zip";
119   char** rc = PHYSFS_enumerateFiles("/");
120   size_t extlen = strlen(archiveExt);
121
122   for(char** i = rc; *i != 0; ++i) {
123     size_t l = strlen(*i);
124     if((l > extlen) && ((*i)[l - extlen - 1] == '.')) {
125       const char* ext = (*i) + (l - extlen);
126       if(strcasecmp(ext, archiveExt) == 0) {
127         const char* d = PHYSFS_getRealDir(*i);
128         char* str = new char[strlen(d) + strlen(dirsep) + l + 1];
129         sprintf(str, "%s%s%s", d, dirsep, *i);
130         PHYSFS_addToSearchPath(str, 1);
131         delete[] str;
132       }
133     }
134   }
135   
136   PHYSFS_freeList(rc);
137
138   // when started from source dir...
139   std::string dir = PHYSFS_getBaseDir();
140   dir += "/data";
141   std::string testfname = dir;
142   testfname += "/credits.txt";
143   bool sourcedir = false;
144   FILE* f = fopen(testfname.c_str(), "r");
145   if(f) {
146     fclose(f);
147     if(!PHYSFS_addToSearchPath(dir.c_str(), 1)) {
148       msg_warning << "Couldn't add '" << dir << "' to physfs searchpath: " << PHYSFS_getLastError() << std::endl;
149     } else {
150       sourcedir = true;
151     }
152   }
153
154   if(!sourcedir) {
155 #if defined(APPDATADIR) || defined(ENABLE_BINRELOC)
156     std::string datadir;
157 #ifdef ENABLE_BINRELOC
158     char* brdatadir = br_strcat(DATADIR, "/" PACKAGE_NAME);
159     datadir = brdatadir;
160     free(brdatadir);
161 #else
162     datadir = APPDATADIR;
163 #endif
164     if(!PHYSFS_addToSearchPath(datadir.c_str(), 1)) {
165       msg_warning << "Couldn't add '" << datadir << "' to physfs searchpath: " << PHYSFS_getLastError() << std::endl;
166     }
167 #endif
168   }
169
170   // allow symbolic links
171   PHYSFS_permitSymbolicLinks(1);
172
173   //show search Path
174   for(char** i = PHYSFS_getSearchPath(); *i != NULL; i++)
175     msg_info << "[" << *i << "] is in the search path" << std::endl;
176 }
177
178 static void print_usage(const char* argv0)
179 {
180   fprintf(stderr, _("Usage: %s [OPTIONS] [LEVELFILE]\n\n"), argv0);
181   fprintf(stderr,
182           _("Options:\n"
183             "  -f, --fullscreen             Run in fullscreen mode\n"
184             "  -w, --window                 Run in window mode\n"
185             "  -g, --geometry WIDTHxHEIGHT  Run SuperTux in given resolution\n"
186             "  --disable-sfx                Disable sound effects\n"
187             "  --disable-music              Disable music\n"
188             "  --help                       Show this help message\n"
189             "  --version                    Display SuperTux version and quit\n"
190             "  --show-fps                   Display framerate in levels\n"
191             "  --record-demo FILE LEVEL     Record a demo to FILE\n"
192             "  --play-demo FILE LEVEL       Play a recorded demo\n"
193             "\n"));
194 }
195
196 static void parse_commandline(int argc, char** argv)
197 {
198   for(int i = 1; i < argc; ++i) {
199     std::string arg = argv[i];
200
201     if(arg == "--fullscreen" || arg == "-f") {
202       config->use_fullscreen = true;
203     } else if(arg == "--window" || arg == "-w") {
204       config->use_fullscreen = false;
205     } else if(arg == "--geometry" || arg == "-g") {
206       if(i+1 >= argc) {
207         print_usage(argv[0]);
208         throw std::runtime_error("Need to specify a parameter for geometry switch");
209       }
210       if(sscanf(argv[++i], "%dx%d", &config->screenwidth, &config->screenheight)
211          != 2) {
212         print_usage(argv[0]);
213         throw std::runtime_error("Invalid geometry spec, should be WIDTHxHEIGHT");
214       }
215     } else if(arg == "--show-fps") {
216       config->show_fps = true;
217     } else if(arg == "--disable-sfx") {
218       config->sound_enabled = false;
219     } else if(arg == "--disable-music") {
220       config->music_enabled = false;
221     } else if(arg == "--play-demo") {
222       if(i+1 >= argc) {
223         print_usage(argv[0]);
224         throw std::runtime_error("Need to specify a demo filename");
225       }
226       config->start_demo = argv[++i];
227     } else if(arg == "--record-demo") {
228       if(i+1 >= argc) {
229         print_usage(argv[0]);
230         throw std::runtime_error("Need to specify a demo filename");
231       }
232       config->record_demo = argv[++i];
233     } else if(arg == "--help") {
234       print_usage(argv[0]);
235       throw graceful_shutdown();
236     } else if(arg == "--version") {
237       msg_info << PACKAGE_NAME << " " << PACKAGE_VERSION << std::endl;
238       throw graceful_shutdown();
239     } else if(arg[0] != '-') {
240       config->start_level = arg;
241     } else {
242       msg_warning << "Unknown option '" << arg << "'. Use --help to see a list of options" << std::endl;
243     }
244   }
245
246   // TODO joystick switchyes...
247 }
248
249 static void init_sdl()
250 {
251   if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
252     std::stringstream msg;
253     msg << "Couldn't initialize SDL: " << SDL_GetError();
254     throw std::runtime_error(msg.str());
255   }
256
257   SDL_EnableUNICODE(1);
258
259   // wait 100ms and clear SDL event queue because sometimes we have random
260   // joystick events in the queue on startup...
261   SDL_Delay(100);
262   SDL_Event dummy;
263   while(SDL_PollEvent(&dummy))
264       ;
265 }
266
267 static void check_gl_error()
268 {
269   GLenum glerror = glGetError();
270   std::string errormsg;
271   
272   if(glerror != GL_NO_ERROR) {
273     switch(glerror) {
274       case GL_INVALID_ENUM:
275         errormsg = "Invalid enumeration value";
276         break;
277       case GL_INVALID_VALUE:
278         errormsg = "Numeric argzment out of range";
279         break;
280       case GL_INVALID_OPERATION:
281         errormsg = "Invalid operation";
282         break;
283       case GL_STACK_OVERFLOW:
284         errormsg = "stack overflow";
285         break;
286       case GL_STACK_UNDERFLOW:
287         errormsg = "stack underflow";
288         break;
289       case GL_OUT_OF_MEMORY:
290         errormsg = "out of memory";
291         break;
292       case GL_TABLE_TOO_LARGE:
293         errormsg = "table too large";
294         break;
295       default:
296         errormsg = "unknown error number";
297         break;
298     }
299     std::stringstream msg;
300     msg << "OpenGL Error: " << errormsg;
301     throw std::runtime_error(msg.str());
302   }
303 }
304
305 void init_video()
306 {
307   if(texture_manager != NULL)
308     texture_manager->save_textures();
309   
310   SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 
311   SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
312   SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
313   SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
314   
315   int flags = SDL_OPENGL;
316   if(config->use_fullscreen)
317     flags |= SDL_FULLSCREEN;
318   int width = config->screenwidth;
319   int height = config->screenheight;
320   int bpp = 0;
321
322   screen = SDL_SetVideoMode(width, height, bpp, flags);
323   if(screen == 0) {
324     std::stringstream msg;
325     msg << "Couldn't set video mode (" << width << "x" << height
326         << "-" << bpp << "bpp): " << SDL_GetError();
327     throw std::runtime_error(msg.str());
328   }
329
330   SDL_WM_SetCaption(PACKAGE_NAME " " PACKAGE_VERSION, 0);
331
332   // set icon
333   SDL_Surface* icon = IMG_Load_RW(
334       get_physfs_SDLRWops("images/engine/icons/supertux.xpm"), true);
335   if(icon != 0) {
336     SDL_WM_SetIcon(icon, 0);
337     SDL_FreeSurface(icon);
338   }
339 #ifdef DEBUG
340   else {
341     msg_warning << "Couldn't find icon 'images/engine/icons/supertux.xpm'" << std::endl;
342   }
343 #endif
344
345   // setup opengl state and transform
346   glDisable(GL_DEPTH_TEST);
347   glDisable(GL_CULL_FACE);
348   glEnable(GL_TEXTURE_2D);
349   glEnable(GL_BLEND);
350   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
351
352   glViewport(0, 0, screen->w, screen->h);
353   glMatrixMode(GL_PROJECTION);
354   glLoadIdentity();
355   // logical resolution here not real monitor resolution
356   glOrtho(0, 800, 600, 0, -1.0, 1.0);
357   glMatrixMode(GL_MODELVIEW);
358   glLoadIdentity();
359   glTranslatef(0, 0, 0);
360
361   check_gl_error();
362
363   if(texture_manager != NULL)
364     texture_manager->reload_textures();
365   else
366     texture_manager = new TextureManager();
367 }
368
369 static void init_audio()
370 {
371   sound_manager = new SoundManager();
372   
373   sound_manager->enable_sound(config->sound_enabled);
374   sound_manager->enable_music(config->music_enabled);
375 }
376
377 static void quit_audio()
378 {
379   if(sound_manager) {
380     delete sound_manager;
381     sound_manager = 0;
382   }
383 }
384
385 void wait_for_event(float min_delay, float max_delay)
386 {
387   assert(min_delay <= max_delay);
388   
389   Uint32 min = (Uint32) (min_delay * 1000);
390   Uint32 max = (Uint32) (max_delay * 1000);
391
392   Uint32 ticks = SDL_GetTicks();
393   while(SDL_GetTicks() - ticks < min) {
394     SDL_Delay(10);
395     sound_manager->update();
396   }
397
398   // clear even queue
399   SDL_Event event;
400   while (SDL_PollEvent(&event))
401   {}
402
403   /* Handle events: */
404   bool running = false;
405   ticks = SDL_GetTicks();
406   while(running) {
407     while(SDL_PollEvent(&event)) {
408       switch(event.type) {
409         case SDL_QUIT:
410           throw graceful_shutdown();
411         case SDL_KEYDOWN:
412         case SDL_JOYBUTTONDOWN:
413         case SDL_MOUSEBUTTONDOWN:
414           running = false;
415       }
416     }
417     if(SDL_GetTicks() - ticks >= (max - min))
418       running = false;
419     sound_manager->update();
420     SDL_Delay(10);
421   }
422 }
423
424 #ifdef DEBUG
425 static Uint32 last_timelog_ticks = 0;
426 static const char* last_timelog_component = 0;
427
428 static inline void timelog(const char* component)
429 {
430   Uint32 current_ticks = SDL_GetTicks();
431   
432   if(last_timelog_component != 0) {
433     msg_info << "Component '" << last_timelog_component <<  "' finished after " << (current_ticks - last_timelog_ticks) / 1000.0 << " seconds" << std::endl;
434   }
435
436   last_timelog_ticks = current_ticks;
437   last_timelog_component = component;
438 }
439 #else
440 static inline void timelog(const char* )
441 {
442 }
443 #endif
444
445 int main(int argc, char** argv) 
446 {
447   try {
448     srand(time(0));
449     init_physfs(argv[0]);
450     init_sdl();
451     timelog("controller");
452     main_controller = new JoystickKeyboardController();    
453     timelog("config");
454     init_config();
455     timelog("tinygettext");
456     init_tinygettext();
457     timelog("commandline");
458     parse_commandline(argc, argv);
459     timelog("audio");
460     init_audio();
461     timelog("video");
462     init_video();
463
464     timelog("menu");
465     setup_menu();
466     timelog("resources");
467     load_shared();
468     timelog(0);
469
470     main_loop = new MainLoop(); 
471     if(config->start_level != "") {
472       // we have a normal path specified at commandline not physfs paths.
473       // So we simply mount that path here...
474       std::string dir = FileSystem::dirname(config->start_level);
475       PHYSFS_addToSearchPath(dir.c_str(), true);
476       GameSession* session
477         = new GameSession(
478           FileSystem::basename(config->start_level), ST_GL_LOAD_LEVEL_FILE);
479       if(config->start_demo != "")
480         session->play_demo(config->start_demo);
481       if(config->record_demo != "")
482         session->record_demo(config->record_demo);
483       main_loop->push_screen(session);
484     } else {
485       main_loop->push_screen(new TitleScreen());
486     }
487
488     main_loop->run();
489
490     delete main_loop;
491     main_loop = NULL;
492   } catch(graceful_shutdown& e) {
493   } catch(std::exception& e) {
494     msg_fatal << "Unexpected exception: " << e.what() << std::endl;
495     return 1;
496   } catch(...) {
497     msg_fatal << "Unexpected exception" << std::endl;
498     return 1;
499   }
500
501   free_menu();
502   unload_shared();
503   quit_audio();
504
505   if(config)
506     config->save();
507   delete config;
508   delete main_controller;
509   delete texture_manager;
510   SDL_Quit();
511   PHYSFS_deinit();
512   
513   return 0;
514 }