Tweaked pause level name text.
[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   context.draw_text_center(white_small_text,
163       std::string(_("by ")) + level->get_author(), 
164       Vector(0, 400), LAYER_FOREGROUND1);
165
166   context.do_drawing();
167
168   SDL_Event event;
169   wait_for_event(event,1000,3000,true);
170 }
171
172 /* Reset Timers */
173 void
174 GameSession::start_timers()
175 {
176   time_left.start(level->time_left*1000);
177   st_pause_ticks_init();
178   update_time = st_get_ticks();
179 }
180
181 void
182 GameSession::on_escape_press()
183 {
184   if(currentsector->player->dying || end_sequence != NO_ENDSEQUENCE)
185     return;   // don't let the player open the menu, when he is dying
186   
187   if(game_pause)
188     return;
189
190   if(st_gl_mode == ST_GL_TEST)
191     {
192       exit_status = ES_LEVEL_ABORT;
193     }
194   else if (!Menu::current())
195     {
196       /* Tell Tux that the keys are all down, otherwise
197         it could have nasty bugs, like going allways to the right
198         or whatever that key does */
199       Player& tux = *(currentsector->player);
200       tux.key_event((SDLKey)keymap.jump, UP);
201       tux.key_event((SDLKey)keymap.duck, UP);
202       tux.key_event((SDLKey)keymap.left, UP);
203       tux.key_event((SDLKey)keymap.right, UP);
204       tux.key_event((SDLKey)keymap.fire, UP);
205
206       Menu::set_current(game_menu);
207       st_pause_ticks_start();
208     }
209 }
210
211 void
212 GameSession::process_events()
213 {
214   if (end_sequence != NO_ENDSEQUENCE)
215     {
216       Player& tux = *currentsector->player;
217          
218       tux.input.fire  = UP;
219       tux.input.left  = UP;
220       tux.input.right = DOWN;
221       tux.input.down  = UP; 
222
223       if (int(last_x_pos) == int(tux.base.x))
224         tux.input.up    = DOWN; 
225       else
226         tux.input.up    = UP; 
227
228       last_x_pos = tux.base.x;
229
230       SDL_Event event;
231       while (SDL_PollEvent(&event))
232         {
233           /* Check for menu-events, if the menu is shown */
234           if (Menu::current())
235             {
236               Menu::current()->event(event);
237               if(!Menu::current())
238               st_pause_ticks_stop();
239             }
240
241           switch(event.type)
242             {
243             case SDL_QUIT:        /* Quit event - quit: */
244               st_abort("Received window close", "");
245               break;
246               
247             case SDL_KEYDOWN:     /* A keypress! */
248               {
249                 SDLKey key = event.key.keysym.sym;
250            
251                 switch(key)
252                   {
253                   case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
254                     on_escape_press();
255                     break;
256                   default:
257                     break;
258                   }
259               }
260           
261             case SDL_JOYBUTTONDOWN:
262               if (event.jbutton.button == joystick_keymap.start_button)
263                 on_escape_press();
264               break;
265             }
266         }
267     }
268   else // normal mode
269     {
270       if(!Menu::current() && !game_pause)
271         st_pause_ticks_stop();
272
273       SDL_Event event;
274       while (SDL_PollEvent(&event))
275         {
276           /* Check for menu-events, if the menu is shown */
277           if (Menu::current())
278             {
279               Menu::current()->event(event);
280               if(!Menu::current())
281                 st_pause_ticks_stop();
282             }
283           else
284             {
285               Player& tux = *currentsector->player;
286   
287               switch(event.type)
288                 {
289                 case SDL_QUIT:        /* Quit event - quit: */
290                   st_abort("Received window close", "");
291                   break;
292
293                 case SDL_KEYDOWN:     /* A keypress! */
294                   {
295                     SDLKey key = event.key.keysym.sym;
296             
297                     if(tux.key_event(key,DOWN))
298                       break;
299
300                     switch(key)
301                       {
302                       case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
303                         on_escape_press();
304                         break;
305                       default:
306                         break;
307                       }
308                   }
309                   break;
310                 case SDL_KEYUP:      /* A keyrelease! */
311                   {
312                     SDLKey key = event.key.keysym.sym;
313
314                     if(tux.key_event(key, UP))
315                       break;
316
317                     switch(key)
318                       {
319                       case SDLK_a:
320                         if(debug_mode)
321                         {
322                           char buf[160];
323                           snprintf(buf, sizeof(buf), "P: %4.1f,%4.1f",
324                               tux.base.x, tux.base.y);
325                           context->draw_text(white_text, buf,
326                               Vector(0, screen->h - white_text->get_height()),
327                               LAYER_FOREGROUND1);
328                           context->do_drawing();
329                           SDL_Delay(1000);
330                         }
331                         break;
332                       case SDLK_p:
333                         if(!Menu::current())
334                           {
335                             if(game_pause)
336                               {
337                                 game_pause = false;
338                                 st_pause_ticks_stop();
339                               }
340                             else
341                               {
342                                 game_pause = true;
343                                 st_pause_ticks_start();
344                               }
345                           }
346                         break;
347                       case SDLK_TAB:
348                         if(debug_mode)
349                           {
350                             tux.grow(false);
351                           }
352                         break;
353                       case SDLK_END:
354                         if(debug_mode)
355                           player_status.distros += 50;
356                         break;
357                       case SDLK_DELETE:
358                         if(debug_mode)
359                           tux.got_power = tux.FIRE_POWER;
360                         break;
361                       case SDLK_HOME:
362                         if(debug_mode)
363                           tux.got_power = tux.ICE_POWER;
364                         break;
365                       case SDLK_INSERT:
366                         if(debug_mode)
367                           tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
368                         break;
369                       case SDLK_l:
370                         if(debug_mode)
371                           --player_status.lives;
372                         break;
373                       case SDLK_s:
374                         if(debug_mode)
375                           player_status.score += 1000;
376                       case SDLK_f:
377                         if(debug_fps)
378                           debug_fps = false;
379                         else
380                           debug_fps = true;
381                         break;
382                       default:
383                         break;
384                       }
385                   }
386                   break;
387
388                 case SDL_JOYAXISMOTION:
389                   if (event.jaxis.axis == joystick_keymap.x_axis)
390                     {
391                       if (event.jaxis.value < -joystick_keymap.dead_zone)
392                         {
393                           tux.input.left  = DOWN;
394                           tux.input.right = UP;
395                         }
396                       else if (event.jaxis.value > joystick_keymap.dead_zone)
397                         {
398                           tux.input.left  = UP;
399                           tux.input.right = DOWN;
400                         }
401                       else
402                         {
403                           tux.input.left  = DOWN;
404                           tux.input.right = DOWN;
405                         }
406                     }
407                   else if (event.jaxis.axis == joystick_keymap.y_axis)
408                     {
409                       if (event.jaxis.value > joystick_keymap.dead_zone)
410                         tux.input.down = DOWN;
411                       else if (event.jaxis.value < -joystick_keymap.dead_zone)
412                         tux.input.down = UP;
413                       else
414                         tux.input.down = UP;
415                     }
416                   break;
417             
418                 case SDL_JOYBUTTONDOWN:
419                   if (event.jbutton.button == joystick_keymap.a_button)
420                     tux.input.up = DOWN;
421                   else if (event.jbutton.button == joystick_keymap.b_button)
422                     tux.input.fire = DOWN;
423                   else if (event.jbutton.button == joystick_keymap.start_button)
424                     on_escape_press();
425                   break;
426                 case SDL_JOYBUTTONUP:
427                   if (event.jbutton.button == joystick_keymap.a_button)
428                     tux.input.up = UP;
429                   else if (event.jbutton.button == joystick_keymap.b_button)
430                     tux.input.fire = UP;
431                   break;
432
433                 default:
434                   break;
435                 }  /* switch */
436             }
437         } /* while */
438     }
439 }
440
441 void
442 GameSession::check_end_conditions()
443 {
444   Player* tux = currentsector->player;
445
446   /* End of level? */
447   Tile* endtile = collision_goal(tux->base);
448
449   if(end_sequence && !endsequence_timer.check())
450     {
451       exit_status = ES_LEVEL_FINISHED;
452       return;
453     }
454   else if(end_sequence == ENDSEQUENCE_RUNNING && endtile && endtile->data >= 1)
455     {
456       end_sequence = ENDSEQUENCE_WAITING;
457     }
458   else if(!end_sequence && endtile && endtile->data == 0)
459     {
460       end_sequence = ENDSEQUENCE_RUNNING;
461       last_x_pos = -1;
462       sound_manager->play_music(level_end_song, 0);
463       endsequence_timer.start(7000); // 5 seconds until we finish the map
464       tux->invincible_timer.start(7000); //FIXME: Implement a winning timer for the end sequence (with special winning animation etc.)
465     }
466   else if (!end_sequence && tux->is_dead())
467     {
468       player_status.bonus = PlayerStatus::NO_BONUS;
469
470       if (player_status.lives < 0)
471         { // No more lives!?
472           exit_status = ES_GAME_OVER;
473         }
474       else
475         { // Still has lives, so reset Tux to the levelstart
476           restart_level();
477         }
478
479       return;
480     }
481 }
482
483 void
484 GameSession::action(double frame_ratio)
485 {
486   if (exit_status == ES_NONE && !currentsector->player->growing_timer.check())
487     {
488       // Update Tux and the World
489       currentsector->action(frame_ratio);
490     }
491
492   // respawning in new sector?
493   if(newsector != "" && newspawnpoint != "") {
494     Sector* sector = level->get_sector(newsector);
495     currentsector = sector;
496     currentsector->activate(newspawnpoint);
497     currentsector->play_music(LEVEL_MUSIC);
498     newsector = newspawnpoint = "";
499   }
500 }
501
502 void 
503 GameSession::draw()
504 {
505   currentsector->draw(*context);
506   drawstatus(*context);
507
508   if(game_pause)
509     {
510       int x = screen->h / 20;
511       for(int i = 0; i < x; ++i)
512         {
513           context->draw_filled_rect(
514               Vector(i % 2 ? (pause_menu_frame * i)%screen->w :
515                 -((pause_menu_frame * i)%screen->w)
516                 ,(i*20+pause_menu_frame)%screen->h),
517               Vector(screen->w,10),
518               Color(20,20,20, rand() % 20 + 1), LAYER_FOREGROUND1+1);
519         }
520       context->draw_filled_rect(
521           Vector(0,0), Vector(screen->w, screen->h),
522           Color(rand() % 50, rand() % 50, rand() % 50, 128), LAYER_FOREGROUND1);
523       context->draw_text_center(blue_text, _("PAUSE - Press 'P' To Play"),
524           Vector(0, 230), LAYER_FOREGROUND1+2);
525
526       char str1[60];
527       char str2[124];
528       sprintf(str1, _("Playing: "));
529       sprintf(str2, level->name.c_str());
530
531       context->draw_text(blue_text, str1,
532           Vector((screen->w - (blue_text->get_text_width(str1) + white_text->get_text_width(str2)))/2, 340),
533           LAYER_FOREGROUND1+2);
534       context->draw_text(white_text, str2,
535           Vector(((screen->w - (blue_text->get_text_width(str1) + white_text->get_text_width(str2)))/2)+blue_text->get_text_width(str1), 340),
536           LAYER_FOREGROUND1+2);
537     }
538
539   if(Menu::current())
540     {
541       Menu::current()->draw(*context);
542       mouse_cursor->draw(*context);
543     }
544
545   context->do_drawing();
546 }
547
548 void
549 GameSession::process_menu()
550 {
551   Menu* menu = Menu::current();
552   if(menu)
553     {
554       menu->action();
555
556       if(menu == game_menu)
557         {
558           switch (game_menu->check())
559             {
560             case MNID_CONTINUE:
561               st_pause_ticks_stop();
562               break;
563             case MNID_ABORTLEVEL:
564               st_pause_ticks_stop();
565               exit_status = ES_LEVEL_ABORT;
566               break;
567             }
568         }
569       else if(menu == options_menu)
570         {
571           process_options_menu();
572         }
573       else if(menu == load_game_menu )
574         {
575           process_load_game_menu();
576         }
577     }
578 }
579
580 GameSession::ExitStatus
581 GameSession::run()
582 {
583   Menu::set_current(0);
584   current_ = this;
585   
586   int fps_cnt = 0;
587
588   update_time = last_update_time = st_get_ticks();
589
590   // Eat unneeded events
591   SDL_Event event;
592   while (SDL_PollEvent(&event)) {}
593
594   draw();
595
596   while (exit_status == ES_NONE)
597     {
598       /* Calculate the movement-factor */
599       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
600
601       if(!frame_timer.check())
602         {
603           frame_timer.start(25);
604           ++global_frame_counter;
605         }
606
607       /* Handle events: */
608       currentsector->player->input.old_fire 
609         = currentsector->player->input.fire;
610
611       process_events();
612       process_menu();
613
614       // Update the world state and all objects in the world
615       // Do that with a constante time-delta so that the game will run
616       // determistic and not different on different machines
617       if(!game_pause && !Menu::current())
618         {
619           // Update the world
620           check_end_conditions();
621           if (end_sequence == ENDSEQUENCE_RUNNING)
622              action(frame_ratio/2);
623           else if(end_sequence == NO_ENDSEQUENCE)
624              action(frame_ratio);
625         }
626       else
627         {
628           ++pause_menu_frame;
629           SDL_Delay(50);
630         }
631
632       draw();
633
634       /* Time stops in pause mode */
635       if(game_pause || Menu::current())
636         {
637           continue;
638         }
639
640       /* Set the time of the last update and the time of the current update */
641       last_update_time = update_time;
642       update_time      = st_get_ticks();
643
644       /* Pause till next frame, if the machine running the game is too fast: */
645       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
646          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
647       if(last_update_time >= update_time - 12) 
648         {
649           SDL_Delay(10);
650           update_time = st_get_ticks();
651         }
652
653       /* Handle time: */
654       if (!time_left.check() && currentsector->player->dying == DYING_NOT
655               && !end_sequence)
656         currentsector->player->kill(Player::KILL);
657
658       /* Handle music: */
659       if(currentsector->player->invincible_timer.check() && !end_sequence)
660         {
661           currentsector->play_music(HERRING_MUSIC);
662         }
663       /* are we low on time ? */
664       else if (time_left.get_left() < TIME_WARNING && !end_sequence)
665         {
666           currentsector->play_music(HURRYUP_MUSIC);
667         }
668       /* or just normal music? */
669       else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence)
670         {
671           currentsector->play_music(LEVEL_MUSIC);
672         }
673
674       /* Calculate frames per second */
675       if(show_fps)
676         {
677           ++fps_cnt;
678           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
679
680           if(!fps_timer.check())
681             {
682               fps_timer.start(1000);
683               fps_cnt = 0;
684             }
685         }
686     }
687   
688   return exit_status;
689 }
690
691 void
692 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
693 {
694   newsector = sector;
695   newspawnpoint = spawnpoint;
696 }
697
698 /* Bounce a brick: */
699 void bumpbrick(float x, float y)
700 {
701   Sector::current()->add_bouncy_brick(Vector(((int)(x + 1) / 32) * 32,
702                          (int)(y / 32) * 32));
703
704   sound_manager->play_sound(sounds[SND_BRICK], Vector(x, y));
705 }
706
707 /* (Status): */
708 void
709 GameSession::drawstatus(DrawingContext& context)
710 {
711   char str[60];
712   
713   snprintf(str, 60, " %d", player_status.score);
714   context.draw_text(white_text, _("SCORE"), Vector(0, 0), LAYER_FOREGROUND1);
715   context.draw_text(gold_text, str, Vector(96, 0), LAYER_FOREGROUND1);
716
717   if(st_gl_mode == ST_GL_TEST)
718     {
719       context.draw_text(white_text, _("Press ESC To Return"), Vector(0,20),
720           LAYER_FOREGROUND1);
721     }
722
723   if(!time_left.check()) {
724     context.draw_text_center(white_text, _("TIME's UP"), Vector(0, 0),
725         LAYER_FOREGROUND1);
726   } else if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5) {
727     sprintf(str, " %d", time_left.get_left() / 1000 );
728     context.draw_text_center(white_text, _("TIME"),
729         Vector(0, 0), LAYER_FOREGROUND1);
730     context.draw_text_center(gold_text, str,
731         Vector(4*16, 0), LAYER_FOREGROUND1);
732   }
733
734   sprintf(str, " %d", player_status.distros);
735   context.draw_text(white_text, _("COINS"),
736       Vector(screen->w - white_text->get_text_width(_("COINS"))-white_text->get_text_width("   99"), 0),
737         LAYER_FOREGROUND1);
738   context.draw_text(gold_text, str,
739       Vector(screen->w - gold_text->get_text_width(" 99"), 0),LAYER_FOREGROUND1);
740
741   if (player_status.lives >= 5)
742     {
743       sprintf(str, "%dx", player_status.lives);
744       float x = screen->w - gold_text->get_text_width(str) - tux_life->w;
745       context.draw_text(gold_text, str, Vector(x, 20), LAYER_FOREGROUND1);
746       context.draw_surface(tux_life, Vector(screen->w - 16, 20),
747           LAYER_FOREGROUND1);
748     }
749   else
750     {
751       for(int i= 0; i < player_status.lives; ++i)
752         context.draw_surface(tux_life, 
753             Vector(screen->w - tux_life->w*4 +(tux_life->w*i), 20),
754             LAYER_FOREGROUND1);
755     }
756
757   context.draw_text(white_text, _("LIVES"),
758       Vector(screen->w - white_text->get_text_width(_("LIVES")) - white_text->get_text_width("   99"), 20),
759       LAYER_FOREGROUND1);
760
761   if(show_fps)
762     {
763       sprintf(str, "%2.1f", fps_fps);
764       context.draw_text(white_text, "FPS", 
765           Vector(screen->w - white_text->get_text_width("FPS      "), 40),
766           LAYER_FOREGROUND1);
767       context.draw_text(gold_text, str,
768           Vector(screen->w-4*16, 40), LAYER_FOREGROUND1);
769     }
770 }
771
772 void
773 GameSession::drawresultscreen(void)
774 {
775   char str[80];
776
777   DrawingContext context;
778   currentsector->background->draw(context);  
779
780   context.draw_text_center(blue_text, _("Result:"), Vector(0, 200),
781       LAYER_FOREGROUND1);
782
783   sprintf(str, _("SCORE: %d"), player_status.score);
784   context.draw_text_center(gold_text, str, Vector(0, 224), LAYER_FOREGROUND1);
785
786   sprintf(str, _("COINS: %d"), player_status.distros);
787   context.draw_text_center(gold_text, str, Vector(0, 256), LAYER_FOREGROUND1);
788
789   context.do_drawing();
790   
791   SDL_Event event;
792   wait_for_event(event,2000,5000,true);
793 }
794
795 std::string slotinfo(int slot)
796 {
797   char tmp[1024];
798   char slotfile[1024];
799   std::string title;
800   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
801
802   lisp_object_t* savegame = lisp_read_from_file(slotfile);
803   if (savegame)
804     {
805       LispReader reader(lisp_cdr(savegame));
806       reader.read_string("title", title);
807       lisp_free(savegame);
808     }
809
810   if (access(slotfile, F_OK) == 0)
811     {
812       if (!title.empty())
813         snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
814       else
815         snprintf(tmp, 1024,_("Slot %d - Savegame"),slot);
816     }
817   else
818     sprintf(tmp,_("Slot %d - Free"),slot);
819
820   return tmp;
821 }
822
823