Messaging subsystem rewrite, step I
[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." << std::endl;
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" << std::endl;
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" << std::endl;
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" << std::endl;
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 << "' does not exist and will not be added to the worldmap" << std::endl;
551     return;
552   }
553
554   level_lisp->get("x", level.pos.x);
555   level_lisp->get("y", level.pos.y);
556
557   level.auto_path = true;
558   level_lisp->get("auto-path", level.auto_path);
559
560   level.vertical_flip = false;
561   level_lisp->get("vertical-flip", level.vertical_flip);
562
563   levels.push_back(level);
564 }
565
566 void
567 WorldMap::get_level_title(Level& level)
568 {
569   /** get special_tile's title */
570   level.title = "<no title>";
571
572   try {
573     lisp::Parser parser;
574     std::auto_ptr<lisp::Lisp> root (parser.parse(levels_path + level.name));
575
576     const lisp::Lisp* level_lisp = root->get_lisp("supertux-level");
577     if(!level_lisp)
578       return;
579     
580     level_lisp->get("name", level.title);
581   } catch(std::exception& e) {
582     msg_warning << "Problem when reading leveltitle: " << e.what() << std::endl;
583     return;
584   }
585 }
586
587 void WorldMap::calculate_total_stats()
588 {
589   total_stats.reset();
590   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i)
591     {
592     if (i->solved)
593       {
594       total_stats += i->statistics;
595       }
596     }
597 }
598
599 void
600 WorldMap::on_escape_press()
601 {
602   // Show or hide the menu
603   if(!Menu::current()) {
604     Menu::set_current(worldmap_menu);
605     tux->set_direction(D_NONE);  // stop tux movement when menu is called
606   } else {
607     Menu::set_current(0);
608   }
609 }
610
611 void
612 WorldMap::get_input()
613 {
614   main_controller->update();
615
616   SDL_Event event;
617   while (SDL_PollEvent(&event)) {
618     if (Menu::current())
619       Menu::current()->event(event);
620     main_controller->process_event(event);
621     if(event.type == SDL_QUIT)
622       throw graceful_shutdown();
623   }
624 }
625
626 Vector
627 WorldMap::get_next_tile(Vector pos, Direction direction)
628 {
629   switch(direction) {
630     case D_WEST:
631       pos.x -= 1;
632       break;
633     case D_EAST:
634       pos.x += 1;
635       break;
636     case D_NORTH:
637       pos.y -= 1;
638       break;
639     case D_SOUTH:
640       pos.y += 1;
641       break;
642     case D_NONE:
643       break;
644   }
645   return pos;
646 }
647
648 bool
649 WorldMap::path_ok(Direction direction, Vector old_pos, Vector* new_pos)
650 {
651   *new_pos = get_next_tile(old_pos, direction);
652
653   if (!(new_pos->x >= 0 && new_pos->x < solids->get_width()
654         && new_pos->y >= 0 && new_pos->y < solids->get_height()))
655     { // New position is outsite the tilemap
656       return false;
657     }
658   else
659     { // Check if the tile allows us to go to new_pos
660       switch(direction)
661         {
662         case D_WEST:
663           return (at(old_pos)->getData() & Tile::WORLDMAP_WEST
664               && at(*new_pos)->getData() & Tile::WORLDMAP_EAST);
665
666         case D_EAST:
667           return (at(old_pos)->getData() & Tile::WORLDMAP_EAST
668               && at(*new_pos)->getData() & Tile::WORLDMAP_WEST);
669
670         case D_NORTH:
671           return (at(old_pos)->getData() & Tile::WORLDMAP_NORTH
672               && at(*new_pos)->getData() & Tile::WORLDMAP_SOUTH);
673
674         case D_SOUTH:
675           return (at(old_pos)->getData() & Tile::WORLDMAP_SOUTH
676               && at(*new_pos)->getData() & Tile::WORLDMAP_NORTH);
677
678         case D_NONE:
679           assert(!"path_ok() can't work if direction is NONE");
680         }
681       return false;
682     }
683 }
684
685 void
686 WorldMap::finished_level(const std::string& filename)
687 {
688   // TODO calculate level from filename?
689   (void) filename;
690   Level* level = at_level();
691
692   bool old_level_state = level->solved;
693   level->solved = true;
694   level->sprite->set_action("solved");
695
696   // deal with statistics
697   level->statistics.merge(global_stats);
698   calculate_total_stats();
699
700   if(savegame_file != "")
701     savegame(savegame_file);
702
703   if (old_level_state != level->solved && level->auto_path) {
704     // Try to detect the next direction to which we should walk
705     // FIXME: Mostly a hack
706     Direction dir = D_NONE;
707   
708     const Tile* tile = at(tux->get_tile_pos());
709
710     if (tile->getData() & Tile::WORLDMAP_NORTH
711         && tux->back_direction != D_NORTH)
712       dir = D_NORTH;
713     else if (tile->getData() & Tile::WORLDMAP_SOUTH
714         && tux->back_direction != D_SOUTH)
715       dir = D_SOUTH;
716     else if (tile->getData() & Tile::WORLDMAP_EAST
717         && tux->back_direction != D_EAST)
718       dir = D_EAST;
719     else if (tile->getData() & Tile::WORLDMAP_WEST
720         && tux->back_direction != D_WEST)
721       dir = D_WEST;
722
723     if (dir != D_NONE) {
724       tux->set_direction(dir);
725     }
726   }
727
728   if (level->extro_script != "") {
729     try {
730       std::auto_ptr<ScriptInterpreter> interpreter
731         (new ScriptInterpreter(levels_path));
732       std::istringstream in(level->extro_script);
733       interpreter->run_script(in, "level-extro-script");
734       add_object(interpreter.release());
735     } catch(std::exception& e) {
736       msg_fatal << "Couldn't run level-extro-script:" << e.what() << std::endl;
737     }
738   }
739   
740   if (!level->next_worldmap.empty()) {
741     // Load given worldmap
742     loadmap(level->next_worldmap);
743   }
744   
745   if (level->quit_worldmap)
746     main_loop->exit_screen();
747 }
748
749 void
750 WorldMap::update(float delta)
751 {
752   Menu* menu = Menu::current();
753   if(menu) {
754     menu->update();
755
756     if(menu == worldmap_menu) {
757       switch (worldmap_menu->check())
758       {
759         case MNID_RETURNWORLDMAP: // Return to game  
760           Menu::set_current(0);
761           break;
762         case MNID_QUITWORLDMAP: // Quit Worldmap
763           main_loop->exit_screen();
764           break;
765       }
766     } else if(menu == options_menu) {
767       process_options_menu();
768     }
769
770     return;
771   }
772
773   // update GameObjects
774   for(GameObjects::iterator i = game_objects.begin();
775       i != game_objects.end(); ++i) {
776     GameObject* object = *i;
777     object->update(delta);
778   }
779
780   // remove old GameObjects
781   for(GameObjects::iterator i = game_objects.begin();
782       i != game_objects.end(); ) {
783     GameObject* object = *i;
784     if(!object->is_valid()) {
785       delete object;
786       i = game_objects.erase(i);
787     } else {
788       ++i;
789     }
790   }
791
792   // position "camera"
793   Vector tux_pos = tux->get_pos();
794   camera_offset.x = tux_pos.x - SCREEN_WIDTH/2;
795   camera_offset.y = tux_pos.y - SCREEN_HEIGHT/2;
796
797   if (camera_offset.x < 0)
798     camera_offset.x = 0;
799   if (camera_offset.y < 0)
800     camera_offset.y = 0;
801
802   if (camera_offset.x > solids->get_width()*32 - SCREEN_WIDTH)
803     camera_offset.x = solids->get_width()*32 - SCREEN_WIDTH;
804   if (camera_offset.y > solids->get_height()*32 - SCREEN_HEIGHT)
805     camera_offset.y = solids->get_height()*32 - SCREEN_HEIGHT;
806
807   // handle input
808   bool enter_level = false;
809   if(main_controller->pressed(Controller::ACTION)
810       || main_controller->pressed(Controller::JUMP)
811       || main_controller->pressed(Controller::MENU_SELECT))
812     enter_level = true;
813   if(main_controller->pressed(Controller::PAUSE_MENU))
814     on_escape_press();
815   
816   if (enter_level && !tux->is_moving())
817     {
818       /* Check special tile action */
819       SpecialTile* special_tile = at_special_tile();
820       if(special_tile)
821         {
822         if (special_tile->teleport_dest != Vector(-1,-1))
823           {
824           // TODO: an animation, camera scrolling or a fading would be a nice touch
825           sound_manager->play("sounds/warp.wav");
826           tux->back_direction = D_NONE;
827           tux->set_tile_pos(special_tile->teleport_dest);
828           SDL_Delay(1000);
829           }
830         }
831
832       /* Check level action */
833       Level* level = at_level();
834       if (!level) {
835         msg_warning << "No level to enter at: " << tux->get_tile_pos().x << ", " << tux->get_tile_pos().y << std::endl;
836         return;
837       }
838
839       if (level->pos == tux->get_tile_pos()) {
840         // do a shriking fade to the level
841         shrink_fade(Vector((level->pos.x*32 + 16 + offset.x),
842                            (level->pos.y*32 + 16 + offset.y)), 500);
843
844         try {
845           GameSession *session =
846             new GameSession(levels_path + level->name,
847                 ST_GL_LOAD_LEVEL_FILE, &level->statistics);
848           main_loop->push_screen(session);
849         } catch(std::exception& e) {
850           msg_fatal << "Couldn't load level: " << e.what() << std::endl;
851         }
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 << std::endl;
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() << "' in levels block in worldmap" << std::endl;
1121           }
1122         }
1123       }
1124     } catch(std::exception& e) {
1125       msg_warning << "Problem loading game '" << filename << "': " << e.what() << std::endl;
1126       load_map();
1127       player_status->reset();
1128     }
1129   }
1130   else
1131   {
1132     load_map();
1133     player_status->reset();
1134   }
1135
1136   calculate_total_stats();
1137 }
1138
1139 void
1140 WorldMap::loadmap(const std::string& filename)
1141 {
1142   savegame_file = "";
1143   map_filename = filename;
1144   load_map();
1145 }
1146
1147 } // namespace WorldMapNS