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