Don't show "by", in case the author name is blank.
[supertux.git] / src / gameloop.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
5 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
6 //  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU General Public License
10 //  as published by the Free Software Foundation; either version 2
11 //  of the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 // 
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21
22 #include <iostream>
23 #include <cassert>
24 #include <cstdio>
25 #include <cstdlib>
26 #include <cmath>
27 #include <cstring>
28 #include <cerrno>
29 #include <unistd.h>
30 #include <ctime>
31
32 #include "SDL.h"
33
34 #ifndef WIN32
35 #include <sys/types.h>
36 #include <ctype.h>
37 #endif
38
39 #include "defines.h"
40 #include "globals.h"
41 #include "gameloop.h"
42 #include "screen/screen.h"
43 #include "setup.h"
44 #include "high_scores.h"
45 #include "menu.h"
46 #include "badguy.h"
47 #include "sector.h"
48 #include "special.h"
49 #include "player.h"
50 #include "level.h"
51 #include "scene.h"
52 #include "collision.h"
53 #include "tile.h"
54 #include "particlesystem.h"
55 #include "resources.h"
56 #include "background.h"
57 #include "tilemap.h"
58 #include "gettext.h"
59
60 GameSession* GameSession::current_ = 0;
61
62 GameSession::GameSession(const std::string& levelname_, int mode)
63   : level(0), currentsector(0), st_gl_mode(mode),
64     end_sequence(NO_ENDSEQUENCE), levelname(levelname_)
65 {
66   current_ = this;
67   
68   global_frame_counter = 0;
69   game_pause = false;
70   fps_fps = 0;
71
72   fps_timer.init(true);            
73   frame_timer.init(true);
74
75   context = new DrawingContext();
76
77   restart_level();
78 }
79
80 void
81 GameSession::restart_level()
82 {
83   game_pause   = false;
84   exit_status  = ES_NONE;
85   end_sequence = NO_ENDSEQUENCE;
86
87   fps_timer.init(true);
88   frame_timer.init(true);
89
90 #if 0
91   float old_x_pos = -1;
92   if (world)
93     { // Tux has lost a life, so we try to respawn him at the nearest reset point
94       old_x_pos = world->get_tux()->base.x;
95     }
96 #endif
97   
98   delete level;
99   currentsector = 0;
100
101   level = new Level;
102   level->load(levelname);
103   currentsector = level->get_sector("main");
104   if(!currentsector)
105     st_abort("Level has no main sector.", "");
106   currentsector->activate("main");
107
108 #if 0 // TODO
109   // Set Tux to the nearest reset point
110   if (old_x_pos != -1)
111     {
112       ResetPoint best_reset_point = { -1, -1 };
113       for(std::vector<ResetPoint>::iterator i = get_level()->reset_points.begin();
114           i != get_level()->reset_points.end(); ++i)
115         {
116           if (i->x < old_x_pos && best_reset_point.x < i->x)
117             best_reset_point = *i;
118         }
119       
120       if (best_reset_point.x != -1)
121         {
122           world->get_tux()->base.x = best_reset_point.x;
123           world->get_tux()->base.y = best_reset_point.y;
124         }
125     }
126 #endif
127     
128   if (st_gl_mode != ST_GL_DEMO_GAME)
129     {
130       if(st_gl_mode == ST_GL_PLAY || st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
131         levelintro();
132     }
133
134   time_left.init(true);
135   start_timers();
136   currentsector->play_music(LEVEL_MUSIC);
137 }
138
139 GameSession::~GameSession()
140 {
141   delete level;
142   delete context;
143 }
144
145 void
146 GameSession::levelintro(void)
147 {
148   sound_manager->halt_music();
149   
150   char str[60];
151
152   DrawingContext context;
153   currentsector->background->draw(context);
154
155   context.draw_text_center(gold_text, level->get_name(), Vector(0, 220),
156       LAYER_FOREGROUND1);
157
158   sprintf(str, "TUX x %d", player_status.lives);
159   context.draw_text_center(white_text, str, Vector(0, 240),
160       LAYER_FOREGROUND1);
161
162   if(level->get_author().size())
163     context.draw_text_center(white_small_text,
164       std::string(_("by ")) + level->get_author(), 
165       Vector(0, 400), LAYER_FOREGROUND1);
166
167   context.do_drawing();
168
169   SDL_Event event;
170   wait_for_event(event,1000,3000,true);
171 }
172
173 /* Reset Timers */
174 void
175 GameSession::start_timers()
176 {
177   time_left.start(level->time_left*1000);
178   st_pause_ticks_init();
179   update_time = st_get_ticks();
180 }
181
182 void
183 GameSession::on_escape_press()
184 {
185   if(currentsector->player->dying || end_sequence != NO_ENDSEQUENCE)
186     return;   // don't let the player open the menu, when he is dying
187   
188   if(game_pause)
189     return;
190
191   if(st_gl_mode == ST_GL_TEST)
192     {
193       exit_status = ES_LEVEL_ABORT;
194     }
195   else if (!Menu::current())
196     {
197       /* Tell Tux that the keys are all down, otherwise
198         it could have nasty bugs, like going allways to the right
199         or whatever that key does */
200       Player& tux = *(currentsector->player);
201       tux.key_event((SDLKey)keymap.jump, UP);
202       tux.key_event((SDLKey)keymap.duck, UP);
203       tux.key_event((SDLKey)keymap.left, UP);
204       tux.key_event((SDLKey)keymap.right, UP);
205       tux.key_event((SDLKey)keymap.fire, UP);
206
207       Menu::set_current(game_menu);
208       st_pause_ticks_start();
209     }
210 }
211
212 void
213 GameSession::process_events()
214 {
215   if (end_sequence != NO_ENDSEQUENCE)
216     {
217       Player& tux = *currentsector->player;
218          
219       tux.input.fire  = UP;
220       tux.input.left  = UP;
221       tux.input.right = DOWN;
222       tux.input.down  = UP; 
223
224       if (int(last_x_pos) == int(tux.base.x))
225         tux.input.up    = DOWN; 
226       else
227         tux.input.up    = UP; 
228
229       last_x_pos = tux.base.x;
230
231       SDL_Event event;
232       while (SDL_PollEvent(&event))
233         {
234           /* Check for menu-events, if the menu is shown */
235           if (Menu::current())
236             {
237               Menu::current()->event(event);
238               if(!Menu::current())
239               st_pause_ticks_stop();
240             }
241
242           switch(event.type)
243             {
244             case SDL_QUIT:        /* Quit event - quit: */
245               st_abort("Received window close", "");
246               break;
247               
248             case SDL_KEYDOWN:     /* A keypress! */
249               {
250                 SDLKey key = event.key.keysym.sym;
251            
252                 switch(key)
253                   {
254                   case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
255                     on_escape_press();
256                     break;
257                   default:
258                     break;
259                   }
260               }
261           
262             case SDL_JOYBUTTONDOWN:
263               if (event.jbutton.button == joystick_keymap.start_button)
264                 on_escape_press();
265               break;
266             }
267         }
268     }
269   else // normal mode
270     {
271       if(!Menu::current() && !game_pause)
272         st_pause_ticks_stop();
273
274       SDL_Event event;
275       while (SDL_PollEvent(&event))
276         {
277           /* Check for menu-events, if the menu is shown */
278           if (Menu::current())
279             {
280               Menu::current()->event(event);
281               if(!Menu::current())
282                 st_pause_ticks_stop();
283             }
284           else
285             {
286               Player& tux = *currentsector->player;
287   
288               switch(event.type)
289                 {
290                 case SDL_QUIT:        /* Quit event - quit: */
291                   st_abort("Received window close", "");
292                   break;
293
294                 case SDL_KEYDOWN:     /* A keypress! */
295                   {
296                     SDLKey key = event.key.keysym.sym;
297             
298                     if(tux.key_event(key,DOWN))
299                       break;
300
301                     switch(key)
302                       {
303                       case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
304                         on_escape_press();
305                         break;
306                       default:
307                         break;
308                       }
309                   }
310                   break;
311                 case SDL_KEYUP:      /* A keyrelease! */
312                   {
313                     SDLKey key = event.key.keysym.sym;
314
315                     if(tux.key_event(key, UP))
316                       break;
317
318                     switch(key)
319                       {
320                       case SDLK_a:
321                         if(debug_mode)
322                         {
323                           char buf[160];
324                           snprintf(buf, sizeof(buf), "P: %4.1f,%4.1f",
325                               tux.base.x, tux.base.y);
326                           context->draw_text(white_text, buf,
327                               Vector(0, screen->h - white_text->get_height()),
328                               LAYER_FOREGROUND1);
329                           context->do_drawing();
330                           SDL_Delay(1000);
331                         }
332                         break;
333                       case SDLK_p:
334                         if(!Menu::current())
335                           {
336                             if(game_pause)
337                               {
338                                 game_pause = false;
339                                 st_pause_ticks_stop();
340                               }
341                             else
342                               {
343                                 game_pause = true;
344                                 st_pause_ticks_start();
345                               }
346                           }
347                         break;
348                       case SDLK_TAB:
349                         if(debug_mode)
350                           {
351                             tux.grow(false);
352                           }
353                         break;
354                       case SDLK_END:
355                         if(debug_mode)
356                           player_status.distros += 50;
357                         break;
358                       case SDLK_DELETE:
359                         if(debug_mode)
360                           tux.got_power = tux.FIRE_POWER;
361                         break;
362                       case SDLK_HOME:
363                         if(debug_mode)
364                           tux.got_power = tux.ICE_POWER;
365                         break;
366                       case SDLK_INSERT:
367                         if(debug_mode)
368                           tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
369                         break;
370                       case SDLK_l:
371                         if(debug_mode)
372                           --player_status.lives;
373                         break;
374                       case SDLK_s:
375                         if(debug_mode)
376                           player_status.score += 1000;
377                       case SDLK_f:
378                         if(debug_fps)
379                           debug_fps = false;
380                         else
381                           debug_fps = true;
382                         break;
383                       default:
384                         break;
385                       }
386                   }
387                   break;
388
389                 case SDL_JOYAXISMOTION:
390                   if (event.jaxis.axis == joystick_keymap.x_axis)
391                     {
392                       if (event.jaxis.value < -joystick_keymap.dead_zone)
393                         {
394                           tux.input.left  = DOWN;
395                           tux.input.right = UP;
396                         }
397                       else if (event.jaxis.value > joystick_keymap.dead_zone)
398                         {
399                           tux.input.left  = UP;
400                           tux.input.right = DOWN;
401                         }
402                       else
403                         {
404                           tux.input.left  = DOWN;
405                           tux.input.right = DOWN;
406                         }
407                     }
408                   else if (event.jaxis.axis == joystick_keymap.y_axis)
409                     {
410                       if (event.jaxis.value > joystick_keymap.dead_zone)
411                         tux.input.down = DOWN;
412                       else if (event.jaxis.value < -joystick_keymap.dead_zone)
413                         tux.input.down = UP;
414                       else
415                         tux.input.down = UP;
416                     }
417                   break;
418             
419                 case SDL_JOYBUTTONDOWN:
420                   if (event.jbutton.button == joystick_keymap.a_button)
421                     tux.input.up = DOWN;
422                   else if (event.jbutton.button == joystick_keymap.b_button)
423                     tux.input.fire = DOWN;
424                   else if (event.jbutton.button == joystick_keymap.start_button)
425                     on_escape_press();
426                   break;
427                 case SDL_JOYBUTTONUP:
428                   if (event.jbutton.button == joystick_keymap.a_button)
429                     tux.input.up = UP;
430                   else if (event.jbutton.button == joystick_keymap.b_button)
431                     tux.input.fire = UP;
432                   break;
433
434                 default:
435                   break;
436                 }  /* switch */
437             }
438         } /* while */
439     }
440 }
441
442 void
443 GameSession::check_end_conditions()
444 {
445   Player* tux = currentsector->player;
446
447   /* End of level? */
448   Tile* endtile = collision_goal(tux->base);
449
450   if(end_sequence && !endsequence_timer.check())
451     {
452       exit_status = ES_LEVEL_FINISHED;
453       return;
454     }
455   else if(end_sequence == ENDSEQUENCE_RUNNING && endtile && endtile->data >= 1)
456     {
457       end_sequence = ENDSEQUENCE_WAITING;
458     }
459   else if(!end_sequence && endtile && endtile->data == 0)
460     {
461       end_sequence = ENDSEQUENCE_RUNNING;
462       last_x_pos = -1;
463       sound_manager->play_music(level_end_song, 0);
464       endsequence_timer.start(7000); // 5 seconds until we finish the map
465       tux->invincible_timer.start(7000); //FIXME: Implement a winning timer for the end sequence (with special winning animation etc.)
466     }
467   else if (!end_sequence && tux->is_dead())
468     {
469       player_status.bonus = PlayerStatus::NO_BONUS;
470
471       if (player_status.lives < 0)
472         { // No more lives!?
473           exit_status = ES_GAME_OVER;
474         }
475       else
476         { // Still has lives, so reset Tux to the levelstart
477           restart_level();
478         }
479
480       return;
481     }
482 }
483
484 void
485 GameSession::action(double frame_ratio)
486 {
487   if (exit_status == ES_NONE && !currentsector->player->growing_timer.check())
488     {
489       // Update Tux and the World
490       currentsector->action(frame_ratio);
491     }
492
493   // respawning in new sector?
494   if(newsector != "" && newspawnpoint != "") {
495     Sector* sector = level->get_sector(newsector);
496     currentsector = sector;
497     currentsector->activate(newspawnpoint);
498     currentsector->play_music(LEVEL_MUSIC);
499     newsector = newspawnpoint = "";
500   }
501 }
502
503 void 
504 GameSession::draw()
505 {
506   currentsector->draw(*context);
507   drawstatus(*context);
508
509   if(game_pause)
510     {
511       int x = screen->h / 20;
512       for(int i = 0; i < x; ++i)
513         {
514           context->draw_filled_rect(
515               Vector(i % 2 ? (pause_menu_frame * i)%screen->w :
516                 -((pause_menu_frame * i)%screen->w)
517                 ,(i*20+pause_menu_frame)%screen->h),
518               Vector(screen->w,10),
519               Color(20,20,20, rand() % 20 + 1), LAYER_FOREGROUND1+1);
520         }
521       context->draw_filled_rect(
522           Vector(0,0), Vector(screen->w, screen->h),
523           Color(rand() % 50, rand() % 50, rand() % 50, 128), LAYER_FOREGROUND1);
524       context->draw_text_center(blue_text, _("PAUSE - Press 'P' To Play"),
525           Vector(0, 230), LAYER_FOREGROUND1+2);
526
527       char str1[60];
528       char str2[124];
529       sprintf(str1, _("Playing: "));
530       sprintf(str2, level->name.c_str());
531
532       context->draw_text(blue_text, str1,
533           Vector((screen->w - (blue_text->get_text_width(str1) + white_text->get_text_width(str2)))/2, 340),
534           LAYER_FOREGROUND1+2);
535       context->draw_text(white_text, str2,
536           Vector(((screen->w - (blue_text->get_text_width(str1) + white_text->get_text_width(str2)))/2)+blue_text->get_text_width(str1), 340),
537           LAYER_FOREGROUND1+2);
538     }
539
540   if(Menu::current())
541     {
542       Menu::current()->draw(*context);
543       mouse_cursor->draw(*context);
544     }
545
546   context->do_drawing();
547 }
548
549 void
550 GameSession::process_menu()
551 {
552   Menu* menu = Menu::current();
553   if(menu)
554     {
555       menu->action();
556
557       if(menu == game_menu)
558         {
559           switch (game_menu->check())
560             {
561             case MNID_CONTINUE:
562               st_pause_ticks_stop();
563               break;
564             case MNID_ABORTLEVEL:
565               st_pause_ticks_stop();
566               exit_status = ES_LEVEL_ABORT;
567               break;
568             }
569         }
570       else if(menu == options_menu)
571         {
572           process_options_menu();
573         }
574       else if(menu == load_game_menu )
575         {
576           process_load_game_menu();
577         }
578     }
579 }
580
581 GameSession::ExitStatus
582 GameSession::run()
583 {
584   Menu::set_current(0);
585   current_ = this;
586   
587   int fps_cnt = 0;
588
589   update_time = last_update_time = st_get_ticks();
590
591   // Eat unneeded events
592   SDL_Event event;
593   while (SDL_PollEvent(&event)) {}
594
595   draw();
596
597   while (exit_status == ES_NONE)
598     {
599       /* Calculate the movement-factor */
600       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
601
602       if(!frame_timer.check())
603         {
604           frame_timer.start(25);
605           ++global_frame_counter;
606         }
607
608       /* Handle events: */
609       currentsector->player->input.old_fire 
610         = currentsector->player->input.fire;
611
612       process_events();
613       process_menu();
614
615       // Update the world state and all objects in the world
616       // Do that with a constante time-delta so that the game will run
617       // determistic and not different on different machines
618       if(!game_pause && !Menu::current())
619         {
620           // Update the world
621           check_end_conditions();
622           if (end_sequence == ENDSEQUENCE_RUNNING)
623              action(frame_ratio/2);
624           else if(end_sequence == NO_ENDSEQUENCE)
625              action(frame_ratio);
626         }
627       else
628         {
629           ++pause_menu_frame;
630           SDL_Delay(50);
631         }
632
633       draw();
634
635       /* Time stops in pause mode */
636       if(game_pause || Menu::current())
637         {
638           continue;
639         }
640
641       /* Set the time of the last update and the time of the current update */
642       last_update_time = update_time;
643       update_time      = st_get_ticks();
644
645       /* Pause till next frame, if the machine running the game is too fast: */
646       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
647          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
648       if(last_update_time >= update_time - 12) 
649         {
650           SDL_Delay(10);
651           update_time = st_get_ticks();
652         }
653
654       /* Handle time: */
655       if (!time_left.check() && currentsector->player->dying == DYING_NOT
656               && !end_sequence)
657         currentsector->player->kill(Player::KILL);
658
659       /* Handle music: */
660       if(currentsector->player->invincible_timer.check() && !end_sequence)
661         {
662           currentsector->play_music(HERRING_MUSIC);
663         }
664       /* are we low on time ? */
665       else if (time_left.get_left() < TIME_WARNING && !end_sequence)
666         {
667           currentsector->play_music(HURRYUP_MUSIC);
668         }
669       /* or just normal music? */
670       else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence)
671         {
672           currentsector->play_music(LEVEL_MUSIC);
673         }
674
675       /* Calculate frames per second */
676       if(show_fps)
677         {
678           ++fps_cnt;
679           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
680
681           if(!fps_timer.check())
682             {
683               fps_timer.start(1000);
684               fps_cnt = 0;
685             }
686         }
687     }
688   
689   return exit_status;
690 }
691
692 void
693 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
694 {
695   newsector = sector;
696   newspawnpoint = spawnpoint;
697 }
698
699 /* Bounce a brick: */
700 void bumpbrick(float x, float y)
701 {
702   Sector::current()->add_bouncy_brick(Vector(((int)(x + 1) / 32) * 32,
703                          (int)(y / 32) * 32));
704
705   sound_manager->play_sound(sounds[SND_BRICK], Vector(x, y));
706 }
707
708 /* (Status): */
709 void
710 GameSession::drawstatus(DrawingContext& context)
711 {
712   char str[60];
713   
714   snprintf(str, 60, " %d", player_status.score);
715   context.draw_text(white_text, _("SCORE"), Vector(0, 0), LAYER_FOREGROUND1);
716   context.draw_text(gold_text, str, Vector(96, 0), LAYER_FOREGROUND1);
717
718   if(st_gl_mode == ST_GL_TEST)
719     {
720       context.draw_text(white_text, _("Press ESC To Return"), Vector(0,20),
721           LAYER_FOREGROUND1);
722     }
723
724   if(!time_left.check()) {
725     context.draw_text_center(white_text, _("TIME's UP"), Vector(0, 0),
726         LAYER_FOREGROUND1);
727   } else if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5) {
728     sprintf(str, " %d", time_left.get_left() / 1000 );
729     context.draw_text_center(white_text, _("TIME"),
730         Vector(0, 0), LAYER_FOREGROUND1);
731     context.draw_text_center(gold_text, str,
732         Vector(4*16, 0), LAYER_FOREGROUND1);
733   }
734
735   sprintf(str, " %d", player_status.distros);
736   context.draw_text(white_text, _("COINS"),
737       Vector(screen->w - white_text->get_text_width(_("COINS"))-white_text->get_text_width("   99"), 0),
738         LAYER_FOREGROUND1);
739   context.draw_text(gold_text, str,
740       Vector(screen->w - gold_text->get_text_width(" 99"), 0),LAYER_FOREGROUND1);
741
742   if (player_status.lives >= 5)
743     {
744       sprintf(str, "%dx", player_status.lives);
745       float x = screen->w - gold_text->get_text_width(str) - tux_life->w;
746       context.draw_text(gold_text, str, Vector(x, 20), LAYER_FOREGROUND1);
747       context.draw_surface(tux_life, Vector(screen->w - 16, 20),
748           LAYER_FOREGROUND1);
749     }
750   else
751     {
752       for(int i= 0; i < player_status.lives; ++i)
753         context.draw_surface(tux_life, 
754             Vector(screen->w - tux_life->w*4 +(tux_life->w*i), 20),
755             LAYER_FOREGROUND1);
756     }
757
758   context.draw_text(white_text, _("LIVES"),
759       Vector(screen->w - white_text->get_text_width(_("LIVES")) - white_text->get_text_width("   99"), 20),
760       LAYER_FOREGROUND1);
761
762   if(show_fps)
763     {
764       sprintf(str, "%2.1f", fps_fps);
765       context.draw_text(white_text, "FPS", 
766           Vector(screen->w - white_text->get_text_width("FPS      "), 40),
767           LAYER_FOREGROUND1);
768       context.draw_text(gold_text, str,
769           Vector(screen->w-4*16, 40), LAYER_FOREGROUND1);
770     }
771 }
772
773 void
774 GameSession::drawresultscreen(void)
775 {
776   char str[80];
777
778   DrawingContext context;
779   currentsector->background->draw(context);  
780
781   context.draw_text_center(blue_text, _("Result:"), Vector(0, 200),
782       LAYER_FOREGROUND1);
783
784   sprintf(str, _("SCORE: %d"), player_status.score);
785   context.draw_text_center(gold_text, str, Vector(0, 224), LAYER_FOREGROUND1);
786
787   sprintf(str, _("COINS: %d"), player_status.distros);
788   context.draw_text_center(gold_text, str, Vector(0, 256), LAYER_FOREGROUND1);
789
790   context.do_drawing();
791   
792   SDL_Event event;
793   wait_for_event(event,2000,5000,true);
794 }
795
796 std::string slotinfo(int slot)
797 {
798   char tmp[1024];
799   char slotfile[1024];
800   std::string title;
801   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
802
803   lisp_object_t* savegame = lisp_read_from_file(slotfile);
804   if (savegame)
805     {
806       LispReader reader(lisp_cdr(savegame));
807       reader.read_string("title", title);
808       lisp_free(savegame);
809     }
810
811   if (access(slotfile, F_OK) == 0)
812     {
813       if (!title.empty())
814         snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
815       else
816         snprintf(tmp, 1024,_("Slot %d - Savegame"),slot);
817     }
818   else
819     sprintf(tmp,_("Slot %d - Free"),slot);
820
821   return tmp;
822 }
823
824