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