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