- More work on scripting interface
[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
22 #include "main.h"
23
24 #include <stdexcept>
25 #include <iostream>
26 #include <sstream>
27 #include <time.h>
28 #include <stdlib.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <dirent.h>
32 #include <unistd.h>
33 #ifndef WIN32
34 #include <libgen.h>
35 #endif
36 #include <SDL.h>
37 #include <SDL_mixer.h>
38 #include <SDL_image.h>
39 #include <SDL_opengl.h>
40
41 #include "gameconfig.h"
42 #include "resources.h"
43 #include "gettext.h"
44 #include "audio/sound_manager.h"
45 #include "video/surface.h"
46 #include "control/joystickkeyboardcontroller.h"
47 #include "misc.h"
48 #include "title.h"
49 #include "game_session.h"
50 #include "file_system.h"
51
52 SDL_Surface* screen = 0;
53 JoystickKeyboardController* main_controller = 0;
54 TinyGetText::DictionaryManager dictionary_manager;
55
56 static void init_config()
57 {
58   config = new Config();
59   try {
60     config->load();
61   } catch(std::exception& e) {
62 #ifdef DEBUG
63     std::cerr << "Couldn't load config file: " << e.what() << "\n";
64 #endif
65   }
66 }
67
68 static void find_directories()
69 {
70   const char* home = getenv("HOME");
71   if(home == 0) {
72 #ifdef DEBUG
73     std::cerr << "Couldn't find home directory.\n";
74 #endif
75     home = ".";
76   }
77
78   user_dir = home;
79   user_dir += "/.supertux";
80
81   // create directories
82   std::string savedir = user_dir + "/save";
83   mkdir(user_dir.c_str(), 0755);
84   mkdir(savedir.c_str(), 0755);
85
86   // try current directory as datadir
87   if(datadir.empty()) {
88     if(FileSystem::faccessible("./data/credits.txt")) {
89       datadir = "./data/";
90     }
91   }
92
93   // Detect datadir with some linux magic
94 #ifndef WIN32
95   if(datadir.empty()) {
96     char exe_file[PATH_MAX];
97     if(readlink("/proc/self/exe", exe_file, PATH_MAX) < 0) {
98 #ifdef DEBUG
99       std::cerr << "Couldn't read /proc/self/exe \n";
100 #endif
101     } else {
102       std::string exedir = std::string(dirname(exe_file)) + "/";
103       std::string testdir = exedir + "./data/";
104       if(access(testdir.c_str(), F_OK) == 0) {
105         datadir = testdir;
106       }
107       
108       testdir = exedir + "../share/supertux/";
109       if(datadir.empty() && access(testdir.c_str(), F_OK) == 0) {
110         datadir = testdir;
111       }
112     }  
113   }
114 #endif
115   
116 #ifdef DATA_PREFIX
117   // use default location
118   if(datadir.empty()) {
119     datadir = DATA_PREFIX;
120   }
121 #endif
122
123   if(datadir.empty())
124     throw std::runtime_error("Couldn't find datadir");
125 }
126
127 static void init_tinygettext()
128 {
129   dictionary_manager.add_directory(datadir + "/locale");
130   dictionary_manager.set_charset("iso8859-1");
131 }
132
133 static void print_usage(const char* argv0)
134 {
135   fprintf(stderr, _("Usage: %s [OPTIONS] LEVELFILE\n\n"), argv0);
136   fprintf(stderr,
137           _("Options:\n"
138             "  -f, --fullscreen             Run in fullscreen mode.\n"
139             "  -w, --window                 Run in window mode.\n"
140             "  -g, --geometry WIDTHxHEIGHT  Run SuperTux in give resolution\n"
141             "  --help                       Show this help message\n"
142             "  --version                    Display SuperTux version and quit\n"
143             "\n"));
144 }
145
146 static void parse_commandline(int argc, char** argv)
147 {
148   for(int i = 1; i < argc; ++i) {
149     std::string arg = argv[i];
150
151     if(arg == "--fullscreen" || arg == "-f") {
152       config->use_fullscreen = true;
153     } else if(arg == "--window" || arg == "-w") {
154       config->use_fullscreen = false;
155     } else if(arg == "--geometry" || arg == "-g") {
156       if(i+1 >= argc) {
157         print_usage(argv[0]);
158         throw std::runtime_error("Need to specify a parameter for geometry switch");
159       }
160       if(sscanf(argv[++i], "%dx%d", &config->screenwidth, &config->screenheight)
161          != 2) {
162         print_usage(argv[0]);
163         throw std::runtime_error("Invalid geometry spec, should be WIDTHxHEIGHT");
164       }
165     } else if(arg == "--show-fps") {
166       config->show_fps = true;
167     } else if(arg == "--play-demo") {
168       if(i+1 >= argc) {
169         print_usage(argv[0]);
170         throw std::runtime_error("Need to specify a demo filename");
171       }
172       config->start_demo = argv[++i];
173     } else if(arg == "--record-demo") {
174       if(i+1 >= argc) {
175         print_usage(argv[0]);
176         throw std::runtime_error("Need to specify a demo filename");
177       }
178       config->record_demo = argv[++i];
179     } else if(arg == "--help") {
180       print_usage(argv[0]);
181       throw std::runtime_error("");
182     } else if(arg == "--version") {
183       std::cerr << PACKAGE_NAME << " " << PACKAGE_VERSION << "\n";
184       throw std::runtime_error("");
185     } else if(arg[0] != '-') {
186       config->start_level = arg;
187     } else {
188       std::cerr << "Unknown option '" << arg << "'.\n";
189       std::cerr << "Use --help to see a list of options.\n";
190     }
191   }
192
193   // TODO joystick switchyes...
194 }
195
196 static void init_sdl()
197 {
198   if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
199     std::stringstream msg;
200     msg << "Couldn't initialize SDL: " << SDL_GetError();
201     throw std::runtime_error(msg.str());
202   }
203
204   SDL_EnableUNICODE(1);
205
206   // wait 100ms and clear SDL event queue because sometimes we have random
207   // joystick events in the queue on startup...
208   SDL_Delay(100);
209   SDL_Event dummy;
210   while(SDL_PollEvent(&dummy))
211       ;
212 }
213
214 static void check_gl_error()
215 {
216   GLenum glerror = glGetError();
217   std::string errormsg;
218   
219   if(glerror != GL_NO_ERROR) {
220     switch(glerror) {
221       case GL_INVALID_ENUM:
222         errormsg = "Invalid enumeration value";
223         break;
224       case GL_INVALID_VALUE:
225         errormsg = "Numeric argzment out of range";
226         break;
227       case GL_INVALID_OPERATION:
228         errormsg = "Invalid operation";
229         break;
230       case GL_STACK_OVERFLOW:
231         errormsg = "stack overflow";
232         break;
233       case GL_STACK_UNDERFLOW:
234         errormsg = "stack underflow";
235         break;
236       case GL_OUT_OF_MEMORY:
237         errormsg = "out of memory";
238         break;
239       case GL_TABLE_TOO_LARGE:
240         errormsg = "table too large";
241         break;
242       default:
243         errormsg = "unknown error number";
244         break;
245     }
246     std::stringstream msg;
247     msg << "OpenGL Error: " << errormsg;
248     throw std::runtime_error(msg.str());
249   }
250 }
251
252 void init_video()
253 {
254   SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 
255   SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
256   SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
257   SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
258   
259   int flags = SDL_OPENGL;
260   if(config->use_fullscreen)
261     flags |= SDL_FULLSCREEN;
262   int width = config->screenwidth;
263   int height = config->screenheight;
264   int bpp = 0;
265
266   screen = SDL_SetVideoMode(width, height, bpp, flags);
267   if(screen == 0) {
268     std::stringstream msg;
269     msg << "Couldn't set video mode (" << width << "x" << height
270         << "-" << bpp << "bpp): " << SDL_GetError();
271     throw std::runtime_error(msg.str());
272   }
273
274   SDL_WM_SetCaption(PACKAGE_NAME " " PACKAGE_VERSION, 0);
275
276   // set icon
277   SDL_Surface* icon = IMG_Load(
278     get_resource_filename("images/supertux.xpm").c_str());
279   if(icon != 0) {
280     SDL_WM_SetIcon(icon, 0);
281     SDL_FreeSurface(icon);
282   }
283 #ifdef DEBUG
284   else {
285     std::cerr << "Warning: Couldn't find icon 'images/supertux.xpm'.\n";
286   }
287 #endif
288
289   // setup opengl state and transform
290   glDisable(GL_DEPTH_TEST);
291   glDisable(GL_CULL_FACE);
292
293   glViewport(0, 0, screen->w, screen->h);
294   glMatrixMode(GL_PROJECTION);
295   glLoadIdentity();
296   // logical resolution here not real monitor resolution
297   glOrtho(0, 800, 600, 0, -1.0, 1.0);
298   glMatrixMode(GL_MODELVIEW);
299   glLoadIdentity();
300   glTranslatef(0, 0, 0);
301
302   check_gl_error();
303
304   Surface::reload_all();
305 }
306
307 static void init_audio()
308 {
309   sound_manager = new SoundManager();
310   
311   int format = MIX_DEFAULT_FORMAT;
312   if(Mix_OpenAudio(config->audio_frequency, format, config->audio_channels,
313                    config->audio_chunksize) < 0) {
314     std::cerr << "Couldn't initialize audio ("
315               << config->audio_frequency << "HZ, " << config->audio_channels
316               << " Channels, Format " << format << ", ChunkSize "
317               << config->audio_chunksize << "): " << SDL_GetError() << "\n";
318     return;
319   }
320   sound_manager->set_audio_device_available(true);
321   sound_manager->enable_sound(config->sound_enabled);
322   sound_manager->enable_music(config->music_enabled);
323   
324   if(Mix_AllocateChannels(config->audio_voices) < 0) {
325     std::cerr << "Couldn't allocate '" << config->audio_voices << "' audio voices: "
326               << SDL_GetError() << "\n";
327     return;
328   }
329 }
330
331 static void quit_audio()
332 {
333   if(sound_manager) {
334     if(sound_manager->audio_device_available())
335       Mix_CloseAudio();
336
337     delete sound_manager;
338     sound_manager = 0;
339   }
340 }
341
342 void wait_for_event(float min_delay, float max_delay)
343 {
344   assert(min_delay <= max_delay);
345   
346   Uint32 min = (Uint32) (min_delay * 1000);
347   Uint32 max = (Uint32) (max_delay * 1000);
348
349   SDL_Delay(min);
350
351   // clear even queue
352   SDL_Event event;
353   while (SDL_PollEvent(&event))
354   {}
355
356   /* Handle events: */
357   bool running = false;
358   Uint32 ticks = SDL_GetTicks();
359   while(running) {
360     while(SDL_PollEvent(&event)) {
361       switch(event.type) {
362         case SDL_QUIT:
363           throw std::runtime_error("received window close");
364         case SDL_KEYDOWN:
365         case SDL_JOYBUTTONDOWN:
366         case SDL_MOUSEBUTTONDOWN:
367           running = false;
368       }
369     }
370     if(SDL_GetTicks() - ticks >= (max - min))
371       running = false;
372     SDL_Delay(10);
373   }
374 }
375
376 int main(int argc, char** argv) 
377 {
378 #ifndef DEBUG // we want backtraces in debug mode so don't catch exceptions
379   try {
380 #endif
381     srand(time(0));
382     init_sdl();
383     main_controller = new JoystickKeyboardController();    
384     find_directories();
385     init_config();
386     init_tinygettext();
387     parse_commandline(argc, argv);
388     init_audio();
389     init_video();
390
391     setup_menu();
392     load_shared();
393     if(config->start_level != "") {
394       GameSession session(config->start_level, ST_GL_LOAD_LEVEL_FILE);
395       if(config->start_demo != "")
396         session.play_demo(config->start_demo);
397       if(config->record_demo != "")
398         session.record_demo(config->record_demo);
399       session.run();
400     } else {
401       // normal game
402       title();
403     }    
404     
405 #ifndef DEBUG
406   } catch(std::exception& e) {
407     std::cerr << "Unexpected exception: " << e.what() << std::endl;
408     return 1;
409   } catch(...) {
410     std::cerr << "Unexpected exception." << std::endl;
411     return 1;
412   }
413 #endif
414
415   free_menu();
416   unload_shared();
417 #ifdef DEBUG
418   Surface::debug_check();
419 #endif
420   quit_audio();
421
422   if(config)
423     config->save();
424   delete config;
425   delete main_controller;
426   SDL_Quit();
427   
428   return 0;
429 }