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