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