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