split worldmap into several files, updates, use SDL_Delay earlier to reduce CPU usage...
[supertux.git] / src / worldmap / 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 "script_manager.hpp"
63 #include "options_menu.hpp"
64 #include "scripting/squirrel_error.hpp"
65 #include "scripting/wrapper_util.hpp"
66 #include "worldmap/level.hpp"
67 #include "worldmap/special_tile.hpp"
68 #include "worldmap/tux.hpp"
69 #include "worldmap/sprite_change.hpp"
70
71 namespace WorldMapNS {
72
73 enum WorldMapMenuIDs {
74   MNID_RETURNWORLDMAP,
75   MNID_QUITWORLDMAP
76 };
77
78 WorldMap* WorldMap::current_ = NULL;
79
80 Direction reverse_dir(Direction direction)
81 {
82   switch(direction)
83     {
84     case D_WEST:
85       return D_EAST;
86     case D_EAST:
87       return D_WEST;
88     case D_NORTH:
89       return D_SOUTH;
90     case D_SOUTH:
91       return D_NORTH;
92     case D_NONE:
93       return D_NONE;
94     }
95   return D_NONE;
96 }
97
98 std::string
99 direction_to_string(Direction direction)
100 {
101   switch(direction)
102     {
103     case D_WEST:
104       return "west";
105     case D_EAST:
106       return "east";
107     case D_NORTH:
108       return "north";
109     case D_SOUTH:
110       return "south";
111     default:
112       return "none";
113     }
114 }
115
116 Direction
117 string_to_direction(const std::string& directory)
118 {
119   if (directory == "west")
120     return D_WEST;
121   else if (directory == "east")
122     return D_EAST;
123   else if (directory == "north")
124     return D_NORTH;
125   else if (directory == "south")
126     return D_SOUTH;
127   else
128     return D_NONE;
129 }
130
131 //---------------------------------------------------------------------------
132
133 WorldMap::WorldMap()
134   : tux(0), solids(0)
135 {
136   tile_manager.reset(new TileManager("images/worldmap.strf"));
137   
138   tux = new Tux(this);
139   add_object(tux);
140     
141   name = "<no title>";
142   music = "music/salcon.ogg";
143   intro_displayed = false;
144
145   total_stats.reset();
146
147   worldmap_menu.reset(new Menu());
148   worldmap_menu->add_label(_("Pause"));
149   worldmap_menu->add_hl();
150   worldmap_menu->add_entry(MNID_RETURNWORLDMAP, _("Continue"));
151   worldmap_menu->add_submenu(_("Options"), get_options_menu());
152   worldmap_menu->add_hl();
153   worldmap_menu->add_entry(MNID_QUITWORLDMAP, _("Quit World"));
154 }
155
156 WorldMap::~WorldMap()
157 {
158   if(current_ == this)
159     current_ = NULL;
160
161   clear_objects();
162   for(SpawnPoints::iterator i = spawn_points.begin();
163       i != spawn_points.end(); ++i) {
164     delete *i;
165   }
166 }
167
168 void
169 WorldMap::add_object(GameObject* object)
170 {
171   TileMap* tilemap = dynamic_cast<TileMap*> (object);
172   if(tilemap != 0 && tilemap->is_solid()) {
173     solids = tilemap;
174   }
175
176   game_objects.push_back(object);
177 }
178
179 void
180 WorldMap::clear_objects()
181 {
182   for(GameObjects::iterator i = game_objects.begin();
183       i != game_objects.end(); ++i)
184     delete *i;
185   game_objects.clear();
186   solids = 0;
187   tux = new Tux(this);
188   add_object(tux);
189 }
190
191 // Don't forget to set map_filename before calling this
192 void
193 WorldMap::load_map()
194 {
195   levels_path = FileSystem::dirname(map_filename);
196
197   try {
198     lisp::Parser parser;
199     std::auto_ptr<lisp::Lisp> root (parser.parse(map_filename));
200
201     const lisp::Lisp* lisp = root->get_lisp("supertux-level");
202     if(!lisp)
203       throw std::runtime_error("file isn't a supertux-level file.");
204
205     lisp->get("name", name);
206     
207     const lisp::Lisp* sector = lisp->get_lisp("sector");
208     if(!sector)
209       throw std::runtime_error("No sector sepcified in worldmap file.");
210     
211     clear_objects();
212     lisp::ListIterator iter(sector);
213     while(iter.next()) {
214       if(iter.item() == "tilemap") {
215         add_object(new TileMap(*(iter.lisp()), tile_manager.get()));
216       } else if(iter.item() == "background") {
217         add_object(new Background(*(iter.lisp())));
218       } else if(iter.item() == "music") {
219         iter.value()->get(music);
220       } else if(iter.item() == "intro-script") {
221         iter.value()->get(intro_script);
222       } else if(iter.item() == "worldmap-spawnpoint") {
223         SpawnPoint* sp = new SpawnPoint(iter.lisp());
224         spawn_points.push_back(sp);
225       } else if(iter.item() == "level") {
226         Level* level = new Level(levels_path, iter.lisp());
227         levels.push_back(level);
228         game_objects.push_back(level);
229       } else if(iter.item() == "special-tile") {
230         SpecialTile* special_tile = new SpecialTile(iter.lisp());
231         special_tiles.push_back(special_tile);
232         game_objects.push_back(special_tile);
233       } else if(iter.item() == "spritechange") {
234         SpriteChange* sprite_change = new SpriteChange(iter.lisp());
235         sprite_changes.push_back(sprite_change);
236         game_objects.push_back(sprite_change);
237       } else if(iter.item() == "name") {
238         // skip
239       } else {
240         log_warning << "Unknown token '" << iter.item() << "' in worldmap" << std::endl;
241       }
242     }
243     if(solids == 0)
244       throw std::runtime_error("No solid tilemap specified");
245
246     // search for main spawnpoint
247     for(SpawnPoints::iterator i = spawn_points.begin();
248         i != spawn_points.end(); ++i) {
249       SpawnPoint* sp = *i;
250       if(sp->name == "main") {
251         Vector p = sp->pos;
252         tux->set_tile_pos(p);
253         break;
254       }
255     }
256
257   } catch(std::exception& e) {
258     std::stringstream msg;
259     msg << "Problem when parsing worldmap '" << map_filename << "': " <<
260       e.what();
261     throw std::runtime_error(msg.str());
262   }
263 }
264
265 void
266 WorldMap::get_level_title(Level& level)
267 {
268   /** get special_tile's title */
269   level.title = "<no title>";
270
271   try {
272     lisp::Parser parser;
273     std::auto_ptr<lisp::Lisp> root (parser.parse(levels_path + level.name));
274
275     const lisp::Lisp* level_lisp = root->get_lisp("supertux-level");
276     if(!level_lisp)
277       return;
278     
279     level_lisp->get("name", level.title);
280   } catch(std::exception& e) {
281     log_warning << "Problem when reading leveltitle: " << e.what() << std::endl;
282     return;
283   }
284 }
285
286 void WorldMap::calculate_total_stats()
287 {
288   total_stats.reset();
289   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i) {
290     Level* level = *i;
291     if (level->solved) {
292       total_stats += level->statistics;
293     }
294   }
295 }
296
297 void
298 WorldMap::on_escape_press()
299 {
300   // Show or hide the menu
301   if(!Menu::current()) {
302     Menu::set_current(worldmap_menu.get());
303     tux->set_direction(D_NONE);  // stop tux movement when menu is called
304   } else {
305     Menu::set_current(NULL);
306   }
307 }
308
309 Vector
310 WorldMap::get_next_tile(Vector pos, Direction direction)
311 {
312   switch(direction) {
313     case D_WEST:
314       pos.x -= 1;
315       break;
316     case D_EAST:
317       pos.x += 1;
318       break;
319     case D_NORTH:
320       pos.y -= 1;
321       break;
322     case D_SOUTH:
323       pos.y += 1;
324       break;
325     case D_NONE:
326       break;
327   }
328   return pos;
329 }
330
331 bool
332 WorldMap::path_ok(Direction direction, Vector old_pos, Vector* new_pos)
333 {
334   *new_pos = get_next_tile(old_pos, direction);
335
336   if (!(new_pos->x >= 0 && new_pos->x < solids->get_width()
337         && new_pos->y >= 0 && new_pos->y < solids->get_height()))
338     { // New position is outsite the tilemap
339       return false;
340     }
341   else
342     { // Check if the tile allows us to go to new_pos
343       switch(direction)
344         {
345         case D_WEST:
346           return (at(old_pos)->getData() & Tile::WORLDMAP_WEST
347               && at(*new_pos)->getData() & Tile::WORLDMAP_EAST);
348
349         case D_EAST:
350           return (at(old_pos)->getData() & Tile::WORLDMAP_EAST
351               && at(*new_pos)->getData() & Tile::WORLDMAP_WEST);
352
353         case D_NORTH:
354           return (at(old_pos)->getData() & Tile::WORLDMAP_NORTH
355               && at(*new_pos)->getData() & Tile::WORLDMAP_SOUTH);
356
357         case D_SOUTH:
358           return (at(old_pos)->getData() & Tile::WORLDMAP_SOUTH
359               && at(*new_pos)->getData() & Tile::WORLDMAP_NORTH);
360
361         case D_NONE:
362           assert(!"path_ok() can't work if direction is NONE");
363         }
364       return false;
365     }
366 }
367
368 void
369 WorldMap::finished_level(const std::string& filename)
370 {
371   // TODO calculate level from filename?
372   (void) filename;
373   Level* level = at_level();
374
375   bool old_level_state = level->solved;
376   level->solved = true;
377   level->sprite->set_action("solved");
378
379   // deal with statistics
380   level->statistics.merge(global_stats);
381   calculate_total_stats();
382
383   save_state();
384   if(World::current() != NULL)
385     World::current()->save_state();
386
387   if (old_level_state != level->solved && level->auto_path) {
388     // Try to detect the next direction to which we should walk
389     // FIXME: Mostly a hack
390     Direction dir = D_NONE;
391   
392     const Tile* tile = at(tux->get_tile_pos());
393
394     // first, test for crossroads
395     if (tile->getData() & Tile::WORLDMAP_CNSE || tile->getData() && Tile::WORLDMAP_CNSW
396      || tile->getData() & Tile::WORLDMAP_CNEW || tile->getData() && Tile::WORLDMAP_CSEW
397      || tile->getData() & Tile::WORLDMAP_CNSEW)
398       dir = D_NONE;
399     else if (tile->getData() & Tile::WORLDMAP_NORTH
400         && tux->back_direction != D_NORTH)
401       dir = D_NORTH;
402     else if (tile->getData() & Tile::WORLDMAP_SOUTH
403         && tux->back_direction != D_SOUTH)
404       dir = D_SOUTH;
405     else if (tile->getData() & Tile::WORLDMAP_EAST
406         && tux->back_direction != D_EAST)
407       dir = D_EAST;
408     else if (tile->getData() & Tile::WORLDMAP_WEST
409         && tux->back_direction != D_WEST)
410       dir = D_WEST;
411
412     if (dir != D_NONE) {
413       tux->set_direction(dir);
414     }
415   }
416
417   if (level->extro_script != "") {
418     try {
419       HSQUIRRELVM vm = ScriptManager::instance->create_thread();
420
421       std::istringstream in(level->extro_script);
422       Scripting::compile_and_run(vm, in, "worldmap,extro_script");
423     } catch(std::exception& e) {
424       log_fatal << "Couldn't run level-extro-script: " << e.what() << std::endl;
425     }
426   }
427 }
428
429 void
430 WorldMap::update(float delta)
431 {
432   Menu* menu = Menu::current();
433   if(menu != NULL) {
434     menu->update();
435
436     if(menu == worldmap_menu.get()) {
437       switch (worldmap_menu->check())
438       {
439         case MNID_RETURNWORLDMAP: // Return to game  
440           Menu::set_current(0);
441           break;
442         case MNID_QUITWORLDMAP: // Quit Worldmap
443           main_loop->exit_screen();
444           break;
445       }
446     }
447
448     return;
449   }
450
451   // update GameObjects
452   for(GameObjects::iterator i = game_objects.begin();
453       i != game_objects.end(); ++i) {
454     GameObject* object = *i;
455     object->update(delta);
456   }
457
458   // remove old GameObjects
459   for(GameObjects::iterator i = game_objects.begin();
460       i != game_objects.end(); ) {
461     GameObject* object = *i;
462     if(!object->is_valid()) {
463       delete object;
464       i = game_objects.erase(i);
465     } else {
466       ++i;
467     }
468   }
469
470   // position "camera"
471   Vector tux_pos = tux->get_pos();
472   camera_offset.x = tux_pos.x - SCREEN_WIDTH/2;
473   camera_offset.y = tux_pos.y - SCREEN_HEIGHT/2;
474
475   if (camera_offset.x < 0)
476     camera_offset.x = 0;
477   if (camera_offset.y < 0)
478     camera_offset.y = 0;
479
480   if (camera_offset.x > solids->get_width()*32 - SCREEN_WIDTH)
481     camera_offset.x = solids->get_width()*32 - SCREEN_WIDTH;
482   if (camera_offset.y > solids->get_height()*32 - SCREEN_HEIGHT)
483     camera_offset.y = solids->get_height()*32 - SCREEN_HEIGHT;
484
485   // handle input
486   bool enter_level = false;
487   if(main_controller->pressed(Controller::ACTION)
488       || main_controller->pressed(Controller::JUMP)
489       || main_controller->pressed(Controller::MENU_SELECT))
490     enter_level = true;
491   if(main_controller->pressed(Controller::PAUSE_MENU))
492     on_escape_press();
493   
494   if (enter_level && !tux->is_moving())
495     {
496       /* Check special tile action */
497       SpecialTile* special_tile = at_special_tile();
498       if(special_tile)
499         {
500         if (special_tile->teleport_dest != Vector(-1,-1))
501           {
502           // TODO: an animation, camera scrolling or a fading would be a nice touch
503           sound_manager->play("sounds/warp.wav");
504           tux->back_direction = D_NONE;
505           tux->set_tile_pos(special_tile->teleport_dest);
506           SDL_Delay(1000);
507           }
508         }
509
510       /* Check level action */
511       Level* level = at_level();
512       if (!level) {
513         log_warning << "No level to enter at: " << tux->get_tile_pos().x << ", " << tux->get_tile_pos().y << std::endl;
514         return;
515       }
516
517       if (level->pos == tux->get_tile_pos()) {
518         // do a shriking fade to the level
519         shrink_fade(Vector((level->pos.x*32 + 16 + offset.x),
520                            (level->pos.y*32 + 16 + offset.y)), 500);
521
522         try {
523           GameSession *session =
524             new GameSession(levels_path + level->name,
525                 ST_GL_LOAD_LEVEL_FILE, &level->statistics);
526           main_loop->push_screen(session);
527         } catch(std::exception& e) {
528           log_fatal << "Couldn't load level: " << e.what() << std::endl;
529         }
530       }
531     }
532   else
533     {
534 //      tux->set_direction(input_direction);
535     }
536 }
537
538 const Tile*
539 WorldMap::at(Vector p)
540 {
541   return solids->get_tile((int) p.x, (int) p.y);
542 }
543
544 Level*
545 WorldMap::at_level()
546 {
547   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i) {
548     Level* level = *i;
549     if (level->pos == tux->get_tile_pos())
550       return level;
551   }
552
553   return NULL;
554 }
555
556 SpecialTile*
557 WorldMap::at_special_tile()
558 {
559   for(SpecialTiles::iterator i = special_tiles.begin();
560       i != special_tiles.end(); ++i) {
561     SpecialTile* special_tile = *i;
562     if (special_tile->pos == tux->get_tile_pos())
563       return special_tile; 
564   }
565
566   return NULL;
567 }
568
569 SpriteChange*
570 WorldMap::at_sprite_change()
571 {
572   return NULL;
573 }
574
575 void
576 WorldMap::draw(DrawingContext& context)
577 {
578   context.push_transform();
579   context.set_translation(camera_offset);
580   
581   for(GameObjects::iterator i = game_objects.begin();
582       i != game_objects.end(); ++i) {
583     GameObject* object = *i;
584     object->draw(context);
585   }
586   
587   draw_status(context);
588   context.pop_transform();
589 }
590
591 void
592 WorldMap::draw_status(DrawingContext& context)
593 {
594   context.push_transform();
595   context.set_translation(Vector(0, 0));
596  
597   player_status->draw(context);
598
599   if (!tux->is_moving()) {
600     for(Levels::iterator i = levels.begin(); i != levels.end(); ++i) {
601       Level* level = *i;
602       
603       if (level->pos == tux->get_tile_pos()) {
604         if(level->title == "")
605           get_level_title(*level);
606
607         context.draw_text(white_text, level->title,
608             Vector(SCREEN_WIDTH/2,
609               SCREEN_HEIGHT - white_text->get_height() - 30),
610             CENTER_ALLIGN, LAYER_FOREGROUND1);
611         
612         level->statistics.draw_worldmap_info(context);
613         break;
614       }
615     }
616
617     for(SpecialTiles::iterator i = special_tiles.begin();
618         i != special_tiles.end(); ++i) {
619       SpecialTile* special_tile = *i;
620       
621       if (special_tile->pos == tux->get_tile_pos()) {
622         /* Display an in-map message in the map, if any as been selected */
623         if(!special_tile->map_message.empty() && !special_tile->passive_message)
624           context.draw_text(gold_text, special_tile->map_message, 
625               Vector(SCREEN_WIDTH/2,
626                 SCREEN_HEIGHT - white_text->get_height() - 60),
627               CENTER_ALLIGN, LAYER_FOREGROUND1);
628         break;
629       }
630     }
631   }
632   
633   /* Display a passive message in the map, if needed */
634   if(passive_message_timer.started())
635     context.draw_text(gold_text, passive_message, 
636             Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT - white_text->get_height() - 60),
637             CENTER_ALLIGN, LAYER_FOREGROUND1);
638
639   context.pop_transform();
640 }
641
642 void
643 WorldMap::setup()
644 {
645   sound_manager->play_music(music);
646   Menu::set_current(NULL);
647
648   current_ = this;
649   load_state();
650 }
651
652 static void store_float(HSQUIRRELVM vm, const char* name, float val)
653 {
654   sq_pushstring(vm, name, -1);
655   sq_pushfloat(vm, val);
656   if(SQ_FAILED(sq_createslot(vm, -3)))
657     throw Scripting::SquirrelError(vm, "Couldn't add float value to table");
658 }
659
660 /*
661 static void store_int(HSQUIRRELVM vm, const char* name, int val)
662 {
663   sq_pushstring(vm, name, -1);
664   sq_pushinteger(vm, val);
665   if(SQ_FAILED(sq_createslot(vm, -3)))
666     throw Scripting::SquirrelError(vm, "Couldn't add float value to table");
667 }
668 */
669
670 static void store_string(HSQUIRRELVM vm, const char* name, const std::string& val)
671 {
672   sq_pushstring(vm, name, -1);
673   sq_pushstring(vm, val.c_str(), val.length());
674   if(SQ_FAILED(sq_createslot(vm, -3)))
675     throw Scripting::SquirrelError(vm, "Couldn't add float value to table");
676 }
677
678 static void store_bool(HSQUIRRELVM vm, const char* name, bool val)
679 {
680   sq_pushstring(vm, name, -1);
681   sq_pushbool(vm, val ? SQTrue : SQFalse);
682   if(SQ_FAILED(sq_createslot(vm, -3)))
683     throw Scripting::SquirrelError(vm, "Couldn't add float value to table");
684 }
685
686 static float read_float(HSQUIRRELVM vm, const char* name)
687 {
688   sq_pushstring(vm, name, -1);
689   if(SQ_FAILED(sq_get(vm, -2))) {
690     std::ostringstream msg;
691     msg << "Couldn't get float value for '" << name << "' from table";
692     throw Scripting::SquirrelError(vm, msg.str());
693   }
694   
695   float result;
696   if(SQ_FAILED(sq_getfloat(vm, -1, &result))) {
697     std::ostringstream msg;
698     msg << "Couldn't get float value for '" << name << "' from table";
699     throw Scripting::SquirrelError(vm, msg.str());
700   }
701   sq_pop(vm, 1);
702
703   return result;
704 }
705
706 static std::string read_string(HSQUIRRELVM vm, const char* name)
707 {
708   sq_pushstring(vm, name, -1);
709   if(SQ_FAILED(sq_get(vm, -2))) {
710     std::ostringstream msg;
711     msg << "Couldn't get string value for '" << name << "' from table";
712     throw Scripting::SquirrelError(vm, msg.str());
713   }
714   
715   const char* result;
716   if(SQ_FAILED(sq_getstring(vm, -1, &result))) {
717     std::ostringstream msg;
718     msg << "Couldn't get string value for '" << name << "' from table";
719     throw Scripting::SquirrelError(vm, msg.str());
720   }
721   sq_pop(vm, 1);
722
723   return std::string(result);
724 }
725
726 static bool read_bool(HSQUIRRELVM vm, const char* name)
727 {
728   sq_pushstring(vm, name, -1);
729   if(SQ_FAILED(sq_get(vm, -2))) {
730     std::ostringstream msg;
731     msg << "Couldn't get bool value for '" << name << "' from table";
732     throw Scripting::SquirrelError(vm, msg.str());
733   } 
734   
735   SQBool result;
736   if(SQ_FAILED(sq_getbool(vm, -1, &result))) {
737     std::ostringstream msg;
738     msg << "Couldn't get bool value for '" << name << "' from table";
739     throw Scripting::SquirrelError(vm, msg.str());
740   }
741   sq_pop(vm, 1);
742
743   return result == SQTrue;
744 }
745
746 void
747 WorldMap::save_state()
748 {
749   HSQUIRRELVM vm = ScriptManager::instance->get_vm();
750   int oldtop = sq_gettop(vm);
751
752   try {
753     // get state table
754     sq_pushroottable(vm);
755     sq_pushstring(vm, "state", -1);
756     if(SQ_FAILED(sq_get(vm, -2)))
757       throw Scripting::SquirrelError(vm, "Couldn't get state table");
758
759     // get or create worlds table
760     sq_pushstring(vm, "worlds", -1);
761     if(SQ_FAILED(sq_get(vm, -2))) {
762       sq_pushstring(vm, "worlds", -1);
763       sq_newtable(vm);
764       if(SQ_FAILED(sq_createslot(vm, -3)))
765         throw Scripting::SquirrelError(vm, "Couldn't create state.worlds");
766
767       sq_pushstring(vm, "worlds", -1);
768       if(SQ_FAILED(sq_get(vm, -2)))
769         throw Scripting::SquirrelError(vm, "Couldn't create.get state.worlds");
770     }
771     
772     sq_pushstring(vm, map_filename.c_str(), map_filename.length());
773     if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
774       sq_pop(vm, 1);
775
776     // construct new table for this worldmap
777     sq_pushstring(vm, map_filename.c_str(), map_filename.length());
778     sq_newtable(vm);
779
780     // store tux
781     sq_pushstring(vm, "tux", -1);
782     sq_newtable(vm);
783     
784     store_float(vm, "x", tux->get_tile_pos().x);
785     store_float(vm, "y", tux->get_tile_pos().y);
786     store_string(vm, "back", direction_to_string(tux->back_direction));
787
788     sq_createslot(vm, -3);
789     
790     // levels...
791     sq_pushstring(vm, "levels", -1);
792     sq_newtable(vm);
793
794     for(Levels::iterator i = levels.begin(); i != levels.end(); ++i) {
795       Level* level = *i;
796       
797       if (level->solved) {
798         sq_pushstring(vm, level->name.c_str(), -1);
799         sq_newtable(vm);
800
801         store_bool(vm, "solved", true);            
802         // TODO write statistics
803         // i->statistics.write(writer);
804
805         sq_createslot(vm, -3);
806       }
807     }
808     
809     sq_createslot(vm, -3);
810
811     // push world into worlds table
812     sq_createslot(vm, -3);
813   } catch(std::exception& e) {
814     sq_settop(vm, oldtop);
815   }
816
817   sq_settop(vm, oldtop);
818 }
819
820 void
821 WorldMap::load_state()
822 {
823   HSQUIRRELVM vm = ScriptManager::instance->get_vm();
824   int oldtop = sq_gettop(vm);
825  
826   try {
827     // get state table
828     sq_pushroottable(vm);
829     sq_pushstring(vm, "state", -1);
830     if(SQ_FAILED(sq_get(vm, -2)))
831       throw Scripting::SquirrelError(vm, "Couldn't get state table");
832
833     // get worlds table
834     sq_pushstring(vm, "worlds", -1);
835     if(SQ_FAILED(sq_get(vm, -2)))
836       throw Scripting::SquirrelError(vm, "Couldn't get state.worlds");
837
838     // get table for our world
839     sq_pushstring(vm, map_filename.c_str(), map_filename.length());
840     if(SQ_FAILED(sq_get(vm, -2)))
841       throw Scripting::SquirrelError(vm, "Couldn't get state.world.mapfilename");
842
843     // load tux
844     sq_pushstring(vm, "tux", -1);
845     if(SQ_FAILED(sq_get(vm, -2)))
846       throw Scripting::SquirrelError(vm, "Couldn't get tux");
847
848     Vector p;
849     p.x = read_float(vm, "x");
850     p.y = read_float(vm, "y");
851     std::string back_str = read_string(vm, "back");
852     tux->back_direction = string_to_direction(back_str);
853     tux->set_tile_pos(p);
854
855     sq_pop(vm, 1);
856
857     // load levels
858     sq_pushstring(vm, "levels", -1);
859     if(SQ_FAILED(sq_get(vm, -2)))
860       throw Scripting::SquirrelError(vm, "Couldn't get levels");
861
862     for(Levels::iterator i = levels.begin(); i != levels.end(); ++i) {
863       Level* level = *i;
864       sq_pushstring(vm, level->name.c_str(), -1);
865       if(SQ_SUCCEEDED(sq_get(vm, -2))) {
866         level->solved = read_bool(vm, "solved");
867         level->sprite->set_action(level->solved ? "solved" : "default");
868         // i->statistics.parse(*level);
869         sq_pop(vm, 1);
870       }
871     }
872     sq_pop(vm, 1);
873
874   } catch(std::exception& e) {
875     log_debug << "Not loading worldmap state: " << e.what() << std::endl;
876   }
877   sq_settop(vm, oldtop);
878 }
879
880 size_t
881 WorldMap::level_count()
882 {
883   return levels.size();
884 }
885
886 size_t
887 WorldMap::solved_level_count()
888 {
889   size_t count = 0;
890   for(Levels::iterator i = levels.begin(); i != levels.end(); ++i) {
891     Level* level = *i;
892     
893     if(level->solved)
894       count++;
895   }
896
897   return count;
898 }
899     
900 void
901 WorldMap::loadmap(const std::string& filename)
902 {
903   savegame_file = "";
904   map_filename = filename;
905   load_map();
906 }
907
908 } // namespace WorldMapNS