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