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