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