moved some sprites to separate files
[supertux.git] / src / worldmap.cpp
1 //  $Id$
2 //
3 //  SuperTux -  A Jump'n Run
4 //  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.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  02111-1307, USA.
19 #include <config.h>
20
21 #include <iostream>
22 #include <fstream>
23 #include <vector>
24 #include <cassert>
25 #include <stdexcept>
26 #include <sstream>
27 #include <unistd.h>
28
29 #include "gettext.hpp"
30 #include "video/surface.hpp"
31 #include "video/screen.hpp"
32 #include "video/drawing_context.hpp"
33 #include "sprite/sprite_manager.hpp"
34 #include "audio/sound_manager.hpp"
35 #include "lisp/parser.hpp"
36 #include "lisp/lisp.hpp"
37 #include "lisp/list_iterator.hpp"
38 #include "lisp/writer.hpp"
39 #include "game_session.hpp"
40 #include "sector.hpp"
41 #include "worldmap.hpp"
42 #include "resources.hpp"
43 #include "misc.hpp"
44 #include "player_status.hpp"
45 #include "textscroller.hpp"
46 #include "main.hpp"
47 #include "spawn_point.hpp"
48 #include "file_system.hpp"
49 #include "gui/menu.hpp"
50 #include "gui/mousecursor.hpp"
51 #include "control/joystickkeyboardcontroller.hpp"
52 #include "object/background.hpp"
53 #include "object/tilemap.hpp"
54 #include "scripting/script_interpreter.hpp"
55
56 Menu* worldmap_menu  = 0;
57
58 static const float TUXSPEED = 200;
59 static const float map_message_TIME = 2.8;
60
61 namespace WorldMapNS {
62
63 Direction reverse_dir(Direction direction)
64 {
65   switch(direction)
66     {
67     case D_WEST:
68       return D_EAST;
69     case D_EAST:
70       return D_WEST;
71     case D_NORTH:
72       return D_SOUTH;
73     case D_SOUTH:
74       return D_NORTH;
75     case D_NONE:
76       return D_NONE;
77     }
78   return D_NONE;
79 }
80
81 std::string
82 direction_to_string(Direction direction)
83 {
84   switch(direction)
85     {
86     case D_WEST:
87       return "west";
88     case D_EAST:
89       return "east";
90     case D_NORTH:
91       return "north";
92     case D_SOUTH:
93       return "south";
94     default:
95       return "none";
96     }
97 }
98
99 Direction
100 string_to_direction(const std::string& directory)
101 {
102   if (directory == "west")
103     return D_WEST;
104   else if (directory == "east")
105     return D_EAST;
106   else if (directory == "north")
107     return D_NORTH;
108   else if (directory == "south")
109     return D_SOUTH;
110   else
111     return D_NONE;
112 }
113
114 //---------------------------------------------------------------------------
115
116 Tux::Tux(WorldMap* worldmap_)
117   : worldmap(worldmap_)
118 {
119   tux_sprite = sprite_manager->create("worldmaptux");
120   
121   offset = 0;
122   moving = false;
123   direction = D_NONE;
124   input_direction = D_NONE;
125 }
126
127 Tux::~Tux()
128 {
129   delete tux_sprite;
130 }
131
132 void
133 Tux::draw(DrawingContext& context)
134 {
135   switch (player_status->bonus) {
136     case GROWUP_BONUS:
137       tux_sprite->set_action("large");
138       break;
139     case FIRE_BONUS:
140       tux_sprite->set_action("fire");
141       break;
142     case NO_BONUS:
143       tux_sprite->set_action("small");
144       break;
145     default:
146 #ifdef DBEUG
147       std::cerr << "Bonus type not handled in worldmap.\n";
148 #endif
149       tux_sprite->set_action("large");
150       break;
151   }
152
153   tux_sprite->draw(context, get_pos(), LAYER_OBJECTS);
154 }
155
156
157 Vector
158 Tux::get_pos()
159 {
160   float x = tile_pos.x * 32;
161   float y = tile_pos.y * 32;
162
163   switch(direction)
164     {
165     case D_WEST:
166       x -= offset - 32;
167       break;
168     case D_EAST:
169       x += offset - 32;
170       break;
171     case D_NORTH:
172       y -= offset - 32;
173       break;
174     case D_SOUTH:
175       y += offset - 32;
176       break;
177     case D_NONE:
178       break;
179     }
180   
181   return Vector(x, y);
182 }
183
184 void
185 Tux::stop()
186 {
187   offset = 0;
188   direction = D_NONE;
189   input_direction = D_NONE;
190   moving = false;
191 }
192
193 void
194 Tux::set_direction(Direction dir)
195 {
196   input_direction = dir;
197 }
198
199 void
200 Tux::update(float delta)
201 {
202   // check controller
203   if(main_controller->pressed(Controller::UP))
204     input_direction = D_NORTH;
205   else if(main_controller->pressed(Controller::DOWN))
206     input_direction = D_SOUTH;
207   else if(main_controller->pressed(Controller::LEFT))
208     input_direction = D_WEST;
209   else if(main_controller->pressed(Controller::RIGHT))
210     input_direction = D_EAST;
211
212   if(!moving)
213     {
214       if (input_direction != D_NONE)
215         { 
216           WorldMap::Level* level = worldmap->at_level();
217
218           // We got a new direction, so lets start walking when possible
219           Vector next_tile;
220           if ((!level || level->solved)
221               && worldmap->path_ok(input_direction, tile_pos, &next_tile))
222             {
223               tile_pos = next_tile;
224               moving = true;
225               direction = input_direction;
226               back_direction = reverse_dir(direction);
227             }
228           else if (input_direction == back_direction)
229             {
230               moving = true;
231               direction = input_direction;
232               tile_pos = worldmap->get_next_tile(tile_pos, direction);
233               back_direction = reverse_dir(direction);
234             }
235         }
236     }
237   else
238     {
239       // Let tux walk
240       offset += TUXSPEED * delta;
241
242       if (offset > 32)
243         { // We reached the next tile, so we check what to do now
244           offset -= 32;
245
246           WorldMap::SpecialTile* special_tile = worldmap->at_special_tile();
247           if(special_tile && special_tile->passive_message)
248             {  // direction and the apply_action_ are opposites, since they "see"
249                // directions in a different way
250             if((direction == D_NORTH && special_tile->apply_action_south) ||
251                (direction == D_SOUTH && special_tile->apply_action_north) ||
252                (direction == D_WEST && special_tile->apply_action_east) ||
253                (direction == D_EAST && special_tile->apply_action_west))
254               {
255               worldmap->passive_message = special_tile->map_message;
256               worldmap->passive_message_timer.start(map_message_TIME);
257               }
258             }
259
260           if (worldmap->at(tile_pos)->getData() & Tile::WORLDMAP_STOP ||
261              (special_tile && !special_tile->passive_message) ||
262               worldmap->at_level())
263             {
264               if(special_tile && !special_tile->map_message.empty() &&
265                 !special_tile->passive_message)
266                 worldmap->passive_message_timer.start(0);
267               stop();
268             }
269           else
270             {
271               const Tile* tile = worldmap->at(tile_pos);
272               if (direction != input_direction)
273                 { 
274                   // Turn to a new direction
275                   const Tile* tile = worldmap->at(tile_pos);
276
277                   if((tile->getData() & Tile::WORLDMAP_NORTH 
278                       && input_direction == D_NORTH) ||
279                      (tile->getData() & Tile::WORLDMAP_SOUTH
280                       && input_direction == D_SOUTH) ||
281                      (tile->getData() & Tile::WORLDMAP_EAST
282                       && input_direction == D_EAST) ||
283                      (tile->getData() & Tile::WORLDMAP_WEST
284                       && input_direction == D_WEST))
285                     {  // player has changed direction during auto-movement
286                       direction = input_direction;
287                       back_direction = reverse_dir(direction);
288                     }
289                   else
290                     {  // player has changed to impossible tile
291                       back_direction = reverse_dir(direction);
292                       stop();
293                     }
294                 }
295               else
296                 {
297                 Direction dir = D_NONE;
298               
299                 if (tile->getData() & Tile::WORLDMAP_NORTH
300                     && back_direction != D_NORTH)
301                   dir = D_NORTH;
302                 else if (tile->getData() & Tile::WORLDMAP_SOUTH
303                     && back_direction != D_SOUTH)
304                   dir = D_SOUTH;
305                 else if (tile->getData() & Tile::WORLDMAP_EAST
306                     && back_direction != D_EAST)
307                   dir = D_EAST;
308                 else if (tile->getData() & Tile::WORLDMAP_WEST
309                     && back_direction != D_WEST)
310                   dir = D_WEST;
311
312                 if (dir != D_NONE)
313                   {
314                   direction = dir;
315                   input_direction = direction;
316                   back_direction = reverse_dir(direction);
317                   }
318                 else
319                   {
320                   // Should never be reached if tiledata is good
321                   stop();
322                   return;
323                   }
324                 }
325
326               // Walk automatically to the next tile
327               if(direction != D_NONE)
328                 {
329                 Vector next_tile;
330                 if (worldmap->path_ok(direction, tile_pos, &next_tile))
331                   {
332                   tile_pos = next_tile;
333                   }
334                 else
335                   {
336                   puts("Tilemap data is buggy");
337                   stop();
338                   }
339                 }
340             }
341         }
342     }
343 }
344
345 //---------------------------------------------------------------------------
346
347 WorldMap::WorldMap()
348   : tux(0), solids(0)
349 {
350   tile_manager = new TileManager("images/worldmap.strf");
351   
352   tux = new Tux(this);
353   add_object(tux);
354     
355   messagedot = new Surface("images/worldmap/common/messagedot.png", true);
356   teleporterdot = new Surface("images/worldmap/common/teleporterdot.png", true);
357
358   name = "<no title>";
359   music = "salcon.ogg";
360   intro_displayed = false;
361
362   total_stats.reset();
363 }
364
365 WorldMap::~WorldMap()
366 {
367   clear_objects();
368   for(SpawnPoints::iterator i = spawn_points.begin();
369       i != spawn_points.end(); ++i) {
370     delete *i;
371   }
372   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i) {
373     Level& level = *i;
374     delete level.sprite;
375   }
376   
377   delete tile_manager;
378
379   delete messagedot;
380   delete teleporterdot;
381 }
382
383 void
384 WorldMap::add_object(GameObject* object)
385 {
386   TileMap* tilemap = dynamic_cast<TileMap*> (object);
387   if(tilemap != 0 && tilemap->is_solid()) {
388     solids = tilemap;
389   }
390
391   game_objects.push_back(object);
392 }
393
394 void
395 WorldMap::clear_objects()
396 {
397   for(GameObjects::iterator i = game_objects.begin();
398       i != game_objects.end(); ++i)
399     delete *i;
400   game_objects.clear();
401   solids = 0;
402   tux = new Tux(this);
403   add_object(tux);
404 }
405
406 // Don't forget to set map_filename before calling this
407 void
408 WorldMap::load_map()
409 {
410   levels_path = FileSystem::dirname(map_filename);
411
412   try {
413     lisp::Parser parser;
414     std::auto_ptr<lisp::Lisp> root (parser.parse(map_filename));
415
416     const lisp::Lisp* lisp = root->get_lisp("supertux-worldmap");
417     if(!lisp)
418       throw std::runtime_error("file isn't a supertux-worldmap file.");
419
420     clear_objects();
421     lisp::ListIterator iter(lisp);
422     while(iter.next()) {
423       if(iter.item() == "tilemap") {
424         add_object(new TileMap(*(iter.lisp()), tile_manager));
425       } else if(iter.item() == "background") {
426         add_object(new Background(*(iter.lisp())));
427       } else if(iter.item() == "properties") {
428         const lisp::Lisp* props = iter.lisp();
429         props->get("name", name);
430         props->get("music", music);
431       } else if(iter.item() == "intro-script") {
432         iter.value()->get(intro_script);
433       } else if(iter.item() == "spawnpoint") {
434         SpawnPoint* sp = new SpawnPoint(iter.lisp());
435         spawn_points.push_back(sp);
436       } else if(iter.item() == "level") {
437         parse_level_tile(iter.lisp());
438       } else if(iter.item() == "special-tile") {
439         parse_special_tile(iter.lisp());
440       } else {
441         std::cerr << "Unknown token '" << iter.item() << "' in worldmap.\n";
442       }
443     }
444     if(solids == 0)
445       throw std::runtime_error("No solid tilemap specified");
446
447     // search for main spawnpoint
448     for(SpawnPoints::iterator i = spawn_points.begin();
449         i != spawn_points.end(); ++i) {
450       SpawnPoint* sp = *i;
451       if(sp->name == "main") {
452         Vector p = sp->pos;
453         tux->set_tile_pos(p);
454         break;
455       }
456     }
457
458   } catch(std::exception& e) {
459     std::stringstream msg;
460     msg << "Problem when parsing worldmap '" << map_filename << "': " <<
461       e.what();
462     throw std::runtime_error(msg.str());
463   }
464 }
465
466 void
467 WorldMap::parse_special_tile(const lisp::Lisp* lisp)
468 {
469   SpecialTile special_tile;
470   
471   lisp->get("x", special_tile.pos.x);
472   lisp->get("y", special_tile.pos.y);
473   lisp->get("map-message", special_tile.map_message);
474   special_tile.passive_message = false;
475   lisp->get("passive-message", special_tile.passive_message);
476   special_tile.teleport_dest = Vector(-1,-1);
477   lisp->get("teleport-to-x", special_tile.teleport_dest.x);
478   lisp->get("teleport-to-y", special_tile.teleport_dest.y);
479   special_tile.invisible = false;
480   lisp->get("invisible-tile", special_tile.invisible);
481
482   special_tile.apply_action_north = true;
483   special_tile.apply_action_south = true;
484   special_tile.apply_action_east = true;
485   special_tile.apply_action_west = true;
486
487   std::string apply_direction;
488   lisp->get("apply-to-direction", apply_direction);
489   if(!apply_direction.empty()) {
490     special_tile.apply_action_north = false;
491     special_tile.apply_action_south = false;
492     special_tile.apply_action_east = false;
493     special_tile.apply_action_west = false;
494     if(apply_direction.find("north") != std::string::npos)
495       special_tile.apply_action_north = true;
496     if(apply_direction.find("south") != std::string::npos)
497       special_tile.apply_action_south = true;
498     if(apply_direction.find("east") != std::string::npos)
499       special_tile.apply_action_east = true;
500     if(apply_direction.find("west") != std::string::npos)
501       special_tile.apply_action_west = true;
502   }
503   
504   special_tiles.push_back(special_tile);
505 }
506
507 void
508 WorldMap::parse_level_tile(const lisp::Lisp* level_lisp)
509 {
510   Level level;
511
512   level.solved = false;
513                   
514   level.north = true;
515   level.east  = true;
516   level.south = true;
517   level.west  = true;
518
519   std::string sprite = "images/worldmap/common/leveldot.sprite";
520   level_lisp->get("sprite", sprite);
521   level.sprite = sprite_manager->create(sprite);
522
523   level_lisp->get("extro-script", level.extro_script);
524   level_lisp->get("next-worldmap", level.next_worldmap);
525
526   level.quit_worldmap = false;
527   level_lisp->get("quit-worldmap", level.quit_worldmap);
528
529   level_lisp->get("name", level.name);
530   level_lisp->get("x", level.pos.x);
531   level_lisp->get("y", level.pos.y);
532
533   level.auto_path = true;
534   level_lisp->get("auto-path", level.auto_path);
535
536   level.vertical_flip = false;
537   level_lisp->get("vertical-flip", level.vertical_flip);
538
539   levels.push_back(level);
540 }
541
542 void
543 WorldMap::get_level_title(Level& level)
544 {
545   /** get special_tile's title */
546   level.title = "<no title>";
547
548   try {
549     lisp::Parser parser;
550     std::auto_ptr<lisp::Lisp> root (parser.parse(levels_path + level.name));
551
552     const lisp::Lisp* level_lisp = root->get_lisp("supertux-level");
553     if(!level_lisp)
554       return;
555     
556     level_lisp->get("name", level.title);
557   } catch(std::exception& e) {
558     std::cerr << "Problem when reading leveltitle: " << e.what() << "\n";
559     return;
560   }
561 }
562
563 void WorldMap::calculate_total_stats()
564 {
565   total_stats.reset();
566   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
567     {
568     if (i->solved)
569       {
570       total_stats += i->statistics;
571       }
572     }
573 }
574
575 void
576 WorldMap::on_escape_press()
577 {
578   // Show or hide the menu
579   if(!Menu::current()) {
580     Menu::set_current(worldmap_menu);
581     tux->set_direction(D_NONE);  // stop tux movement when menu is called
582   } else {
583     Menu::set_current(0);
584   }
585 }
586
587 void
588 WorldMap::get_input()
589 {
590   main_controller->update();
591
592   SDL_Event event;
593   while (SDL_PollEvent(&event)) {
594     if (Menu::current())
595       Menu::current()->event(event);
596     main_controller->process_event(event);
597     if(event.type == SDL_QUIT)
598       throw std::runtime_error("Received window close");
599   }
600 }
601
602 Vector
603 WorldMap::get_next_tile(Vector pos, Direction direction)
604 {
605   switch(direction) {
606     case D_WEST:
607       pos.x -= 1;
608       break;
609     case D_EAST:
610       pos.x += 1;
611       break;
612     case D_NORTH:
613       pos.y -= 1;
614       break;
615     case D_SOUTH:
616       pos.y += 1;
617       break;
618     case D_NONE:
619       break;
620   }
621   return pos;
622 }
623
624 bool
625 WorldMap::path_ok(Direction direction, Vector old_pos, Vector* new_pos)
626 {
627   *new_pos = get_next_tile(old_pos, direction);
628
629   if (!(new_pos->x >= 0 && new_pos->x < solids->get_width()
630         && new_pos->y >= 0 && new_pos->y < solids->get_height()))
631     { // New position is outsite the tilemap
632       return false;
633     }
634   else
635     { // Check if the tile allows us to go to new_pos
636       switch(direction)
637         {
638         case D_WEST:
639           return (at(old_pos)->getData() & Tile::WORLDMAP_WEST
640               && at(*new_pos)->getData() & Tile::WORLDMAP_EAST);
641
642         case D_EAST:
643           return (at(old_pos)->getData() & Tile::WORLDMAP_EAST
644               && at(*new_pos)->getData() & Tile::WORLDMAP_WEST);
645
646         case D_NORTH:
647           return (at(old_pos)->getData() & Tile::WORLDMAP_NORTH
648               && at(*new_pos)->getData() & Tile::WORLDMAP_SOUTH);
649
650         case D_SOUTH:
651           return (at(old_pos)->getData() & Tile::WORLDMAP_SOUTH
652               && at(*new_pos)->getData() & Tile::WORLDMAP_NORTH);
653
654         case D_NONE:
655           assert(!"path_ok() can't work if direction is NONE");
656         }
657       return false;
658     }
659 }
660
661 void
662 WorldMap::update(float delta)
663 {
664   Menu* menu = Menu::current();
665   if(menu) {
666     menu->update();
667
668     if(menu == worldmap_menu) {
669       switch (worldmap_menu->check())
670       {
671         case MNID_RETURNWORLDMAP: // Return to game  
672           Menu::set_current(0);
673           break;
674         case MNID_QUITWORLDMAP: // Quit Worldmap
675           quit = true;                               
676           break;
677       }
678     } else if(menu == options_menu) {
679       process_options_menu();
680     }
681
682     return;
683   }
684
685   // update GameObjects
686   for(GameObjects::iterator i = game_objects.begin();
687       i != game_objects.end(); ++i) {
688     GameObject* object = *i;
689     object->update(delta);
690   }
691   // remove old GameObjects
692   for(GameObjects::iterator i = game_objects.begin();
693       i != game_objects.end(); ) {
694     GameObject* object = *i;
695     if(!object->is_valid()) {
696       delete object;
697       i = game_objects.erase(i);
698     } else {
699       ++i;
700     }
701   }
702   
703   bool enter_level = false;
704   if(main_controller->pressed(Controller::ACTION)
705       || main_controller->pressed(Controller::JUMP)
706       || main_controller->pressed(Controller::MENU_SELECT))
707     enter_level = true;
708   if(main_controller->pressed(Controller::PAUSE_MENU))
709     on_escape_press();
710   
711   if (enter_level && !tux->is_moving())
712     {
713       /* Check special tile action */
714       SpecialTile* special_tile = at_special_tile();
715       if(special_tile)
716         {
717         if (special_tile->teleport_dest != Vector(-1,-1))
718           {
719           // TODO: an animation, camera scrolling or a fading would be a nice touch
720           sound_manager->play("sounds/warp.wav");
721           tux->back_direction = D_NONE;
722           tux->set_tile_pos(special_tile->teleport_dest);
723           SDL_Delay(1000);
724           }
725         }
726
727       /* Check level action */
728       bool level_finished = true;
729       Level* level = at_level();
730       if (!level)
731         {
732         std::cout << "No level to enter at: "
733           << tux->get_tile_pos().x << ", " << tux->get_tile_pos().y
734           << std::endl;
735         return;
736         }
737
738
739       if (level->pos == tux->get_tile_pos())
740         {
741           sound_manager->stop_music();
742           PlayerStatus old_player_status;
743           old_player_status = *player_status;
744
745           // do a shriking fade to the level
746           shrink_fade(Vector((level->pos.x*32 + 16 + offset.x),
747                              (level->pos.y*32 + 16 + offset.y)), 500);
748           GameSession session(levels_path + level->name,
749                               ST_GL_LOAD_LEVEL_FILE, &level->statistics);
750
751           switch (session.run())
752             {
753             case GameSession::ES_LEVEL_FINISHED:
754               {
755                 level_finished = true;
756                 bool old_level_state = level->solved;
757                 level->solved = true;
758                 level->sprite->set_action("solved");
759
760                 // deal with statistics
761                 level->statistics.merge(global_stats);
762                 calculate_total_stats();
763
764                 if (old_level_state != level->solved && level->auto_path)
765                   { // Try to detect the next direction to which we should walk
766                     // FIXME: Mostly a hack
767                     Direction dir = D_NONE;
768                 
769                     const Tile* tile = at(tux->get_tile_pos());
770
771                     if (tile->getData() & Tile::WORLDMAP_NORTH
772                         && tux->back_direction != D_NORTH)
773                       dir = D_NORTH;
774                     else if (tile->getData() & Tile::WORLDMAP_SOUTH
775                         && tux->back_direction != D_SOUTH)
776                       dir = D_SOUTH;
777                     else if (tile->getData() & Tile::WORLDMAP_EAST
778                         && tux->back_direction != D_EAST)
779                       dir = D_EAST;
780                     else if (tile->getData() & Tile::WORLDMAP_WEST
781                         && tux->back_direction != D_WEST)
782                       dir = D_WEST;
783
784                     if (dir != D_NONE)
785                       {
786                         tux->set_direction(dir);
787                       }
788                   }
789               }
790
791               break;
792             case GameSession::ES_LEVEL_ABORT:
793               level_finished = false;
794               /* In case the player's abort the level, keep it using the old
795                   status. But the minimum lives and no bonus. */
796               player_status->coins = old_player_status.coins;
797               player_status->lives = std::min(old_player_status.lives, player_status->lives);
798               player_status->bonus = NO_BONUS;
799
800               break;
801             case GameSession::ES_GAME_OVER:
802               {
803               level_finished = false;
804               /* draw an end screen */
805               /* TODO: in the future, this should make a dialog a la SuperMario, asking
806               if the player wants to restart the world map with no score and from
807               level 1 */
808               char str[80];
809
810               DrawingContext context;
811               context.draw_gradient(Color (200,240,220), Color(200,200,220),
812                   LAYER_BACKGROUND0);
813
814               context.draw_text(blue_text, _("GAMEOVER"), 
815                   Vector(SCREEN_WIDTH/2, 200), CENTER_ALLIGN, LAYER_FOREGROUND1);
816
817               sprintf(str, _("COINS: %d"), player_status->coins);
818               context.draw_text(gold_text, str,
819                   Vector(SCREEN_WIDTH/2, SCREEN_WIDTH - 32), CENTER_ALLIGN,
820                   LAYER_FOREGROUND1);
821
822               total_stats.draw_message_info(context, _("Total Statistics"));
823
824               context.do_drawing();
825
826               wait_for_event(2.0, 6.0);
827
828               quit = true;
829               player_status->reset();
830               break;
831               }
832             case GameSession::ES_NONE:
833               assert(false);
834               // Should never be reached 
835               break;
836             }
837
838           sound_manager->play_music(std::string("music/") + music);
839           Menu::set_current(0);
840           if (!savegame_file.empty())
841             savegame(savegame_file);
842         }
843       /* The porpose of the next checking is that if the player lost
844          the level (in case there is one), don't show anything */
845       if(level_finished) {
846         if (level->extro_script != "") {
847           try {
848             std::auto_ptr<ScriptInterpreter> interpreter 
849               (new ScriptInterpreter(levels_path));
850             std::istringstream in(level->extro_script);
851             interpreter->run_script(in, "level-extro-script");
852             add_object(interpreter.release());
853           } catch(std::exception& e) {
854             std::cerr << "Couldn't run level-extro-script:" << e.what() << "\n";
855           }
856         }
857
858         if (!level->next_worldmap.empty())
859           {
860           // Load given worldmap
861           loadmap(level->next_worldmap);
862           }
863         if (level->quit_worldmap)
864           quit = true;
865         }
866     }
867   else
868     {
869 //      tux->set_direction(input_direction);
870     }
871 }
872
873 const Tile*
874 WorldMap::at(Vector p)
875 {
876   return solids->get_tile((int) p.x, (int) p.y);
877 }
878
879 WorldMap::Level*
880 WorldMap::at_level()
881 {
882   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
883     {
884       if (i->pos == tux->get_tile_pos())
885         return &*i; 
886     }
887
888   return 0;
889 }
890
891 WorldMap::SpecialTile*
892 WorldMap::at_special_tile()
893 {
894   for(SpecialTiles::iterator i = special_tiles.begin(); i != special_tiles.end(); ++i)
895     {
896       if (i->pos == tux->get_tile_pos())
897         return &*i; 
898     }
899
900   return 0;
901 }
902
903 void
904 WorldMap::draw(DrawingContext& context)
905 {
906   for(GameObjects::iterator i = game_objects.begin();
907       i != game_objects.end(); ++i) {
908     GameObject* object = *i;
909     object->draw(context);
910   }
911   
912   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
913     {
914       const Level& level = *i;
915       level.sprite->draw(context, level.pos*32 + Vector(16, 16), LAYER_TILES+1);
916     }
917
918   for(SpecialTiles::iterator i = special_tiles.begin(); i != special_tiles.end(); ++i)
919     {
920       if(i->invisible)
921         continue;
922
923       if (i->teleport_dest != Vector(-1, -1))
924         context.draw_surface(teleporterdot,
925                 Vector(i->pos.x*32, i->pos.y*32), LAYER_TILES+1);
926
927       else if (!i->map_message.empty() && !i->passive_message)
928         context.draw_surface(messagedot,
929                 Vector(i->pos.x*32, i->pos.y*32), LAYER_TILES+1);
930     }
931
932   draw_status(context);
933 }
934
935 void
936 WorldMap::draw_status(DrawingContext& context)
937 {
938   context.push_transform();
939   context.set_translation(Vector(0, 0));
940  
941   player_status->draw(context);
942
943   if (!tux->is_moving())
944     {
945       for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
946         {
947           if (i->pos == tux->get_tile_pos())
948             {
949               if(i->title == "")
950                 get_level_title(*i);
951
952               context.draw_text(white_text, i->title, 
953                   Vector(SCREEN_WIDTH/2,
954                          SCREEN_HEIGHT - white_text->get_height() - 30),
955                   CENTER_ALLIGN, LAYER_FOREGROUND1);
956
957               i->statistics.draw_worldmap_info(context);
958               break;
959             }
960         }
961       for(SpecialTiles::iterator i = special_tiles.begin(); i != special_tiles.end(); ++i)
962         {
963           if (i->pos == tux->get_tile_pos())
964             {
965                /* Display an in-map message in the map, if any as been selected */
966               if(!i->map_message.empty() && !i->passive_message)
967                 context.draw_text(gold_text, i->map_message, 
968                     Vector(SCREEN_WIDTH/2,
969                            SCREEN_HEIGHT - white_text->get_height() - 60),
970                     CENTER_ALLIGN, LAYER_FOREGROUND1);
971               break;
972             }
973         }
974     }
975   /* Display a passive message in the map, if needed */
976   if(passive_message_timer.started())
977     context.draw_text(gold_text, passive_message, 
978             Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT - white_text->get_height() - 60),
979             CENTER_ALLIGN, LAYER_FOREGROUND1);
980
981   context.pop_transform();
982 }
983
984 void
985 WorldMap::display()
986 {
987   Menu::set_current(0);
988
989   quit = false;
990
991   sound_manager->play_music(std::string("music/") + music);
992
993   if(!intro_displayed && intro_script != "") {
994     try {
995       std::auto_ptr<ScriptInterpreter> interpreter 
996         (new ScriptInterpreter(levels_path));
997       std::istringstream in(intro_script);
998       interpreter->run_script(in, "worldmap-intro-script");
999       add_object(interpreter.release());
1000     } catch(std::exception& e) {
1001       std::cerr << "Couldn't execute worldmap-intro-script: "
1002         << e.what() << "\n";
1003     }
1004                                            
1005     intro_displayed = true;
1006   }
1007
1008   Uint32 lastticks = SDL_GetTicks();
1009   DrawingContext context;
1010   while(!quit) {
1011     Uint32 ticks = SDL_GetTicks();
1012     float elapsed_time = float(ticks - lastticks) / 1000;
1013     game_time += elapsed_time;
1014     lastticks = ticks;
1015     
1016     // 40 fps minimum // TODO use same code as in GameSession here
1017     if(elapsed_time > .025)
1018       elapsed_time = .025;
1019     
1020     Vector tux_pos = tux->get_pos();
1021     offset.x = tux_pos.x - SCREEN_WIDTH/2;
1022     offset.y = tux_pos.y - SCREEN_HEIGHT/2;
1023
1024     if (offset.x < 0)
1025       offset.x = 0;
1026     if (offset.y < 0)
1027       offset.y = 0;
1028
1029     if (offset.x > solids->get_width()*32 - SCREEN_WIDTH)
1030       offset.x = solids->get_width()*32 - SCREEN_WIDTH;
1031     if (offset.y > solids->get_height()*32 - SCREEN_HEIGHT)
1032       offset.y = solids->get_height()*32 - SCREEN_HEIGHT;
1033
1034     context.push_transform();
1035     context.set_translation(offset);
1036     draw(context);
1037     context.pop_transform();
1038     get_input();
1039     update(elapsed_time);
1040     sound_manager->update();
1041       
1042     if(Menu::current()) {
1043       Menu::current()->draw(context);
1044     }
1045
1046     context.do_drawing();
1047   }
1048 }
1049
1050 void
1051 WorldMap::savegame(const std::string& filename)
1052 {
1053   if(filename == "")
1054     return;
1055
1056   lisp::Writer writer(filename);
1057
1058   int nb_solved_levels = 0, total_levels = 0;
1059   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i) {
1060     ++total_levels;
1061     if (i->solved)
1062       ++nb_solved_levels;
1063   }
1064   char nb_solved_levels_str[80], total_levels_str[80];
1065   sprintf(nb_solved_levels_str, "%d", nb_solved_levels);
1066   sprintf(total_levels_str, "%d", total_levels);
1067
1068   writer.write_comment("Worldmap save file");
1069
1070   writer.start_list("supertux-savegame");
1071
1072   writer.write_int("version", 1);
1073   writer.write_string("title",
1074       std::string(name + " - " + nb_solved_levels_str+"/"+total_levels_str));
1075   writer.write_string("map", map_filename);
1076   writer.write_bool("intro-displayed", intro_displayed);
1077
1078   writer.start_list("tux");
1079
1080   writer.write_float("x", tux->get_tile_pos().x);
1081   writer.write_float("y", tux->get_tile_pos().y);
1082   writer.write_string("back", direction_to_string(tux->back_direction));
1083   player_status->write(writer);
1084   writer.write_string("back", direction_to_string(tux->back_direction));
1085
1086   writer.end_list("tux");
1087
1088   writer.start_list("levels");
1089
1090   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
1091     {
1092       if (i->solved)
1093         {
1094         writer.start_list("level");
1095
1096         writer.write_string("name", i->name);
1097         writer.write_bool("solved", true);
1098         i->statistics.write(writer);
1099
1100         writer.end_list("level");
1101         }
1102     }  
1103
1104   writer.end_list("levels");
1105
1106   writer.end_list("supertux-savegame");
1107 }
1108
1109 void
1110 WorldMap::loadgame(const std::string& filename)
1111 {
1112   std::cout << "loadgame: " << filename << std::endl;
1113   savegame_file = filename;
1114
1115   try {
1116     lisp::Parser parser;
1117     std::auto_ptr<lisp::Lisp> root (parser.parse(filename));
1118   
1119     const lisp::Lisp* savegame = root->get_lisp("supertux-savegame");
1120     if(!savegame)
1121       throw std::runtime_error("File is not a supertux-savegame file.");
1122
1123     /* Get the Map filename and then load it before setting level settings */
1124     std::string cur_map_filename = map_filename;
1125     savegame->get("map", map_filename);
1126     load_map(); 
1127
1128     savegame->get("intro-displayed", intro_displayed);
1129     savegame->get("lives", player_status->lives);
1130     savegame->get("coins", player_status->coins);
1131     savegame->get("max-score-multiplier", player_status->max_score_multiplier);
1132     if (player_status->lives < 0)
1133       player_status->reset();
1134
1135     const lisp::Lisp* tux_lisp = savegame->get_lisp("tux");
1136     if(tux)
1137     {
1138       Vector p;
1139       std::string back_str = "none";
1140
1141       tux_lisp->get("x", p.x);
1142       tux_lisp->get("y", p.y);
1143       tux_lisp->get("back", back_str);
1144       player_status->read(*tux_lisp);
1145       
1146       tux->back_direction = string_to_direction(back_str);      
1147       tux->set_tile_pos(p);
1148     }
1149
1150     const lisp::Lisp* levels_lisp = savegame->get_lisp("levels");
1151     if(levels_lisp) {
1152       lisp::ListIterator iter(levels_lisp);
1153       while(iter.next()) {
1154         if(iter.item() == "level") {
1155           std::string name;
1156           bool solved = false;
1157
1158           const lisp::Lisp* level = iter.lisp();
1159           level->get("name", name);
1160           level->get("solved", solved);
1161
1162           for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
1163           {
1164             if (name == i->name)
1165             {
1166               i->solved = solved;
1167               i->sprite->set_action(solved ? "solved" : "default");
1168               i->statistics.parse(*level);
1169               break;
1170             }
1171           }
1172         } else {
1173           std::cerr << "Unknown token '" << iter.item() 
1174                     << "' in levels block in worldmap.\n";
1175         }
1176       }
1177     }
1178   } catch(std::exception& e) {
1179     std::cerr << "Problem loading game '" << filename << "': " << e.what() 
1180               << "\n";
1181     load_map();
1182     player_status->reset();
1183   }
1184
1185   calculate_total_stats();
1186 }
1187
1188 void
1189 WorldMap::loadmap(const std::string& filename)
1190 {
1191   savegame_file = "";
1192   map_filename = filename;
1193   load_map();
1194 }
1195
1196 } // namespace WorldMapNS