Added MenuManager to keep track of created Menus
[supertux.git] / src / worldmap / worldmap.cpp
1 //  SuperTux -  A Jump'n Run
2 //  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
3 //  Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
4 //
5 //  This program is free software: you can redistribute it and/or modify
6 //  it under the terms of the GNU General Public License as published by
7 //  the Free Software Foundation, either version 3 of the License, or
8 //  (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 #include "worldmap/worldmap.hpp"
19
20 #include <config.h>
21
22 #include <assert.h>
23 #include <fstream>
24 #include <iostream>
25 #include <physfs.h>
26 #include <sstream>
27 #include <stdexcept>
28 #include <unistd.h>
29 #include <vector>
30
31 #include "audio/sound_manager.hpp"
32 #include "control/joystickkeyboardcontroller.hpp"
33 #include "gui/menu.hpp"
34 #include "gui/mousecursor.hpp"
35 #include "lisp/lisp.hpp"
36 #include "lisp/list_iterator.hpp"
37 #include "lisp/parser.hpp"
38 #include "object/background.hpp"
39 #include "object/tilemap.hpp"
40 #include "physfs/ifile_stream.hpp"
41 #include "scripting/squirrel_error.hpp"
42 #include "scripting/squirrel_util.hpp"
43 #include "sprite/sprite.hpp"
44 #include "sprite/sprite_manager.hpp"
45 #include "supertux/game_session.hpp"
46 #include "supertux/globals.hpp"
47 #include "supertux/mainloop.hpp"
48 #include "supertux/menu/menu_manager.hpp"
49 #include "supertux/menu/options_menu.hpp"
50 #include "supertux/player_status.hpp"
51 #include "supertux/resources.hpp"
52 #include "supertux/sector.hpp"
53 #include "supertux/shrinkfade.hpp"
54 #include "supertux/spawn_point.hpp"
55 #include "supertux/textscroller.hpp"
56 #include "supertux/tile_manager.hpp"
57 #include "supertux/tile_set.hpp"
58 #include "supertux/world.hpp"
59 #include "util/file_system.hpp"
60 #include "util/gettext.hpp"
61 #include "util/log.hpp"
62 #include "util/reader.hpp"
63 #include "video/drawing_context.hpp"
64 #include "video/surface.hpp"
65 #include "worldmap/level.hpp"
66 #include "worldmap/special_tile.hpp"
67 #include "worldmap/sprite_change.hpp"
68 #include "worldmap/tux.hpp"
69 #include "worldmap/worldmap.hpp"
70
71 static const float CAMERA_PAN_SPEED = 5.0;
72
73 namespace WorldMapNS {
74
75 enum WorldMapMenuIDs {
76   MNID_RETURNWORLDMAP,
77   MNID_QUITWORLDMAP
78 };
79
80 WorldMap* WorldMap::current_ = NULL;
81
82 Direction reverse_dir(Direction direction)
83 {
84   switch(direction)
85   {
86     case D_WEST:
87       return D_EAST;
88     case D_EAST:
89       return D_WEST;
90     case D_NORTH:
91       return D_SOUTH;
92     case D_SOUTH:
93       return D_NORTH;
94     case D_NONE:
95       return D_NONE;
96   }
97   return D_NONE;
98 }
99
100 std::string
101 direction_to_string(Direction direction)
102 {
103   switch(direction)
104   {
105     case D_WEST:
106       return "west";
107     case D_EAST:
108       return "east";
109     case D_NORTH:
110       return "north";
111     case D_SOUTH:
112       return "south";
113     default:
114       return "none";
115   }
116 }
117
118 Direction
119 string_to_direction(const std::string& directory)
120 {
121   if (directory == "west")
122     return D_WEST;
123   else if (directory == "east")
124     return D_EAST;
125   else if (directory == "north")
126     return D_NORTH;
127   else if (directory == "south")
128     return D_SOUTH;
129   else if (directory == "none")
130     return D_NONE;
131   else {
132     log_warning << "unknown direction: \"" << directory << "\"" << std::endl;
133     return D_NONE;
134   }
135 }
136
137 //---------------------------------------------------------------------------
138
139 WorldMap::WorldMap(const std::string& filename, const std::string& force_spawnpoint) :
140   tux(0),
141   tileset(NULL), 
142   free_tileset(false),
143   worldmap_menu(),
144   camera_offset(),
145   name(),
146   music(),
147   init_script(),
148   game_objects(),
149   solid_tilemaps(),
150   passive_message_timer(),
151   passive_message(),
152   map_filename(),
153   levels_path(),
154   special_tiles(),
155   levels(),
156   sprite_changes(),
157   spawn_points(),
158   teleporters(),
159   total_stats(),
160   worldmap_table(),
161   scripts(),
162   ambient_light( 1.0f, 1.0f, 1.0f, 1.0f ), 
163   force_spawnpoint(force_spawnpoint),
164   in_level(false), 
165   pan_pos(),
166   panning(false)
167 {
168   tux = new Tux(this);
169   add_object(tux);
170
171   name = "<no title>";
172   music = "music/salcon.ogg";
173
174   total_stats.reset();
175
176   worldmap_menu.reset(new Menu());
177   worldmap_menu->add_label(_("Pause"));
178   worldmap_menu->add_hl();
179   worldmap_menu->add_entry(MNID_RETURNWORLDMAP, _("Continue"));
180   worldmap_menu->add_submenu(_("Options"), MenuManager::get_options_menu());
181   worldmap_menu->add_hl();
182   worldmap_menu->add_entry(MNID_QUITWORLDMAP, _("Quit World"));
183
184   // create a new squirrel table for the worldmap
185   using namespace Scripting;
186
187   sq_collectgarbage(global_vm);
188   sq_newtable(global_vm);
189   sq_pushroottable(global_vm);
190   if(SQ_FAILED(sq_setdelegate(global_vm, -2)))
191     throw Scripting::SquirrelError(global_vm, "Couldn't set worldmap_table delegate");
192
193   sq_resetobject(&worldmap_table);
194   if(SQ_FAILED(sq_getstackobj(global_vm, -1, &worldmap_table)))
195     throw Scripting::SquirrelError(global_vm, "Couldn't get table from stack");
196
197   sq_addref(global_vm, &worldmap_table);
198   sq_pop(global_vm, 1);
199
200   sound_manager->preload("sounds/warp.wav");
201   
202   // load worldmap objects
203   load(filename);
204 }
205
206 WorldMap::~WorldMap()
207 {
208   using namespace Scripting;
209
210   if(free_tileset)
211     delete tileset;
212
213   for(GameObjects::iterator i = game_objects.begin();
214       i != game_objects.end(); ++i) {
215     GameObject* object = *i;
216     try_unexpose(object);
217     object->unref();
218   }
219
220   for(SpawnPoints::iterator i = spawn_points.begin();
221       i != spawn_points.end(); ++i) {
222     delete *i;
223   }
224
225   for(ScriptList::iterator i = scripts.begin();
226       i != scripts.end(); ++i) {
227     HSQOBJECT& object = *i;
228     sq_release(global_vm, &object);
229   }
230   sq_release(global_vm, &worldmap_table);
231
232   sq_collectgarbage(global_vm);
233
234   if(current_ == this)
235     current_ = NULL;
236 }
237
238 void
239 WorldMap::add_object(GameObject* object)
240 {
241   TileMap* tilemap = dynamic_cast<TileMap*> (object);
242   if(tilemap != 0 && tilemap->is_solid()) {
243     solid_tilemaps.push_back(tilemap);
244   }
245
246   object->ref();
247   try_expose(object);
248   game_objects.push_back(object);
249 }
250
251 void
252 WorldMap::try_expose(GameObject* object)
253 {
254   ScriptInterface* interface = dynamic_cast<ScriptInterface*> (object);
255   if(interface != NULL) {
256     HSQUIRRELVM vm = Scripting::global_vm;
257     sq_pushobject(vm, worldmap_table);
258     interface->expose(vm, -1);
259     sq_pop(vm, 1);
260   }
261 }
262
263 void
264 WorldMap::try_unexpose(GameObject* object)
265 {
266   ScriptInterface* interface = dynamic_cast<ScriptInterface*> (object);
267   if(interface != NULL) {
268     HSQUIRRELVM vm = Scripting::global_vm;
269     SQInteger oldtop = sq_gettop(vm);
270     sq_pushobject(vm, worldmap_table);
271     try {
272       interface->unexpose(vm, -1);
273     } catch(std::exception& e) {
274       log_warning << "Couldn't unregister object: " << e.what() << std::endl;
275     }
276     sq_settop(vm, oldtop);
277   }
278 }
279
280 void
281 WorldMap::move_to_spawnpoint(const std::string& spawnpoint, bool pan)
282 {
283   for(SpawnPoints::iterator i = spawn_points.begin(); i != spawn_points.end(); ++i) {
284     SpawnPoint* sp = *i;
285     if(sp->name == spawnpoint) {
286       Vector p = sp->pos;
287       tux->set_tile_pos(p);
288       tux->set_direction(sp->auto_dir);
289       if(pan) {
290         panning = true;
291         pan_pos = get_camera_pos_for_tux();
292         clamp_camera_position(pan_pos);
293       }
294       return;
295     }
296   }
297   log_warning << "Spawnpoint '" << spawnpoint << "' not found." << std::endl;
298   if (spawnpoint != "main") {
299     move_to_spawnpoint("main");
300   }
301 }
302
303 void
304 WorldMap::change(const std::string& filename, const std::string& force_spawnpoint)
305 {
306   g_main_loop->exit_screen();
307   g_main_loop->push_screen(new WorldMap(filename, force_spawnpoint));
308 }
309
310 void
311 WorldMap::load(const std::string& filename)
312 {
313   map_filename = filename;
314   levels_path = FileSystem::dirname(map_filename);
315
316   try {
317     lisp::Parser parser;
318     const lisp::Lisp* root = parser.parse(map_filename);
319
320     const lisp::Lisp* level = root->get_lisp("supertux-level");
321     if(level == NULL)
322       throw std::runtime_error("file isn't a supertux-level file.");
323
324     level->get("name", name);
325
326     const lisp::Lisp* sector = level->get_lisp("sector");
327     if(!sector)
328       throw std::runtime_error("No sector specified in worldmap file.");
329
330     const lisp::Lisp* tilesets_lisp = level->get_lisp("tilesets");
331     if(tilesets_lisp != NULL) {
332       tileset      = tile_manager->parse_tileset_definition(*tilesets_lisp);
333       free_tileset = true;
334     }
335     std::string tileset_name;
336     if(level->get("tileset", tileset_name)) {
337       if(tileset != NULL) {
338         log_warning << "multiple tilesets specified in level" << std::endl;
339       } else {
340         tileset = tile_manager->get_tileset(tileset_name);
341       }
342     }
343     /* load default tileset */
344     if(tileset == NULL) {
345       tileset = tile_manager->get_tileset("images/worldmap.strf");
346     }
347     current_tileset = tileset;
348
349     lisp::ListIterator iter(sector);
350     while(iter.next()) {
351       if(iter.item() == "tilemap") {
352         add_object(new TileMap(*(iter.lisp())));
353       } else if(iter.item() == "background") {
354         add_object(new Background(*(iter.lisp())));
355       } else if(iter.item() == "music") {
356         iter.value()->get(music);
357       } else if(iter.item() == "init-script") {
358         iter.value()->get(init_script);
359       } else if(iter.item() == "worldmap-spawnpoint") {
360         SpawnPoint* sp = new SpawnPoint(*iter.lisp());
361         spawn_points.push_back(sp);
362       } else if(iter.item() == "level") {
363         LevelTile* level = new LevelTile(levels_path, *iter.lisp());
364         levels.push_back(level);
365         add_object(level);
366       } else if(iter.item() == "special-tile") {
367         SpecialTile* special_tile = new SpecialTile(*iter.lisp());
368         special_tiles.push_back(special_tile);
369         add_object(special_tile);
370       } else if(iter.item() == "sprite-change") {
371         SpriteChange* sprite_change = new SpriteChange(*iter.lisp());
372         sprite_changes.push_back(sprite_change);
373         add_object(sprite_change);
374       } else if(iter.item() == "teleporter") {
375         Teleporter* teleporter = new Teleporter(*iter.lisp());
376         teleporters.push_back(teleporter);
377         add_object(teleporter);
378       } else if(iter.item() == "ambient-light") {
379         std::vector<float> vColor;
380         sector->get( "ambient-light", vColor );
381         if(vColor.size() < 3) {
382           log_warning << "(ambient-light) requires a color as argument" << std::endl;
383         } else {
384           ambient_light = Color( vColor );
385         }
386       } else if(iter.item() == "name") {
387         // skip
388       } else {
389         log_warning << "Unknown token '" << iter.item() << "' in worldmap" << std::endl;
390       }
391     }
392     current_tileset = NULL;
393
394     if(solid_tilemaps.size() == 0)
395       throw std::runtime_error("No solid tilemap specified");
396
397     move_to_spawnpoint("main");
398
399   } catch(std::exception& e) {
400     std::stringstream msg;
401     msg << "Problem when parsing worldmap '" << map_filename << "': " <<
402       e.what();
403     throw std::runtime_error(msg.str());
404   }
405 }
406
407 void
408 WorldMap::get_level_title(LevelTile& level)
409 {
410   /** get special_tile's title */
411   level.title = "<no title>";
412
413   try {
414     lisp::Parser parser;
415     const lisp::Lisp* root = parser.parse(levels_path + level.get_name());
416
417     const lisp::Lisp* level_lisp = root->get_lisp("supertux-level");
418     if(!level_lisp)
419       return;
420
421     level_lisp->get("name", level.title);
422   } catch(std::exception& e) {
423     log_warning << "Problem when reading leveltitle: " << e.what() << std::endl;
424     return;
425   }
426 }
427
428 void WorldMap::calculate_total_stats()
429 {
430   total_stats.zero();
431   for(LevelTiles::iterator i = levels.begin(); i != levels.end(); ++i) {
432     LevelTile* level = *i;
433     if (level->solved) {
434       total_stats += level->statistics;
435     }
436   }
437 }
438
439 void
440 WorldMap::on_escape_press()
441 {
442   // Show or hide the menu
443   if(!Menu::current()) {
444     Menu::set_current(worldmap_menu.get());
445     tux->set_direction(D_NONE);  // stop tux movement when menu is called
446   } else {
447     Menu::set_current(NULL);
448   }
449 }
450
451 Vector
452 WorldMap::get_next_tile(Vector pos, Direction direction)
453 {
454   switch(direction) {
455     case D_WEST:
456       pos.x -= 1;
457       break;
458     case D_EAST:
459       pos.x += 1;
460       break;
461     case D_NORTH:
462       pos.y -= 1;
463       break;
464     case D_SOUTH:
465       pos.y += 1;
466       break;
467     case D_NONE:
468       break;
469   }
470   return pos;
471 }
472
473 bool
474 WorldMap::path_ok(Direction direction, const Vector& old_pos, Vector* new_pos)
475 {
476   *new_pos = get_next_tile(old_pos, direction);
477
478   if (!(new_pos->x >= 0 && new_pos->x < get_width()
479         && new_pos->y >= 0 && new_pos->y < get_height()))
480   { // New position is outsite the tilemap
481     return false;
482   }
483   else
484   { // Check if the tile allows us to go to new_pos
485     int old_tile_data = tile_data_at(old_pos);
486     int new_tile_data = tile_data_at(*new_pos);
487     switch(direction)
488     {
489       case D_WEST:
490         return (old_tile_data & Tile::WORLDMAP_WEST
491                 && new_tile_data & Tile::WORLDMAP_EAST);
492
493       case D_EAST:
494         return (old_tile_data & Tile::WORLDMAP_EAST
495                 && new_tile_data & Tile::WORLDMAP_WEST);
496
497       case D_NORTH:
498         return (old_tile_data & Tile::WORLDMAP_NORTH
499                 && new_tile_data & Tile::WORLDMAP_SOUTH);
500
501       case D_SOUTH:
502         return (old_tile_data & Tile::WORLDMAP_SOUTH
503                 && new_tile_data & Tile::WORLDMAP_NORTH);
504
505       case D_NONE:
506         assert(!"path_ok() can't walk if direction is NONE");
507     }
508     return false;
509   }
510 }
511
512 void
513 WorldMap::finished_level(Level* gamelevel)
514 {
515   // TODO use Level* parameter here?
516   LevelTile* level = at_level();
517
518   bool old_level_state = level->solved;
519   level->solved = true;
520   level->sprite->set_action("solved");
521
522   // deal with statistics
523   level->statistics.merge(gamelevel->stats);
524   calculate_total_stats();
525
526   save_state();
527
528   if (old_level_state != level->solved) {
529     // Try to detect the next direction to which we should walk
530     // FIXME: Mostly a hack
531     Direction dir = D_NONE;
532
533     int dirdata = available_directions_at(tux->get_tile_pos());
534     // first, test for crossroads
535     if (dirdata == Tile::WORLDMAP_CNSE ||
536         dirdata == Tile::WORLDMAP_CNSW ||
537         dirdata == Tile::WORLDMAP_CNEW ||
538         dirdata == Tile::WORLDMAP_CSEW ||
539         dirdata == Tile::WORLDMAP_CNSEW)
540       dir = D_NONE;
541     else if (dirdata & Tile::WORLDMAP_NORTH
542              && tux->back_direction != D_NORTH)
543       dir = D_NORTH;
544     else if (dirdata & Tile::WORLDMAP_SOUTH
545              && tux->back_direction != D_SOUTH)
546       dir = D_SOUTH;
547     else if (dirdata & Tile::WORLDMAP_EAST
548              && tux->back_direction != D_EAST)
549       dir = D_EAST;
550     else if (dirdata & Tile::WORLDMAP_WEST
551              && tux->back_direction != D_WEST)
552       dir = D_WEST;
553
554     if (dir != D_NONE) {
555       tux->set_direction(dir);
556     }
557   }
558
559   if (level->extro_script != "") {
560     try {
561       std::istringstream in(level->extro_script);
562       run_script(in, "worldmap:extro_script");
563     } catch(std::exception& e) {
564       log_warning << "Couldn't run level-extro-script: " << e.what() << std::endl;
565     }
566   }
567 }
568
569 Vector
570 WorldMap::get_camera_pos_for_tux() {
571   Vector camera_offset;
572   Vector tux_pos = tux->get_pos();
573   camera_offset.x = tux_pos.x - SCREEN_WIDTH/2;
574   camera_offset.y = tux_pos.y - SCREEN_HEIGHT/2;
575   return camera_offset;
576 }
577
578 void
579 WorldMap::clamp_camera_position(Vector& c) {
580   if (c.x < 0)
581     c.x = 0;
582   if (c.y < 0)
583     c.y = 0;
584
585   if (c.x > (int)get_width()*32 - SCREEN_WIDTH)
586     c.x = (int)get_width()*32 - SCREEN_WIDTH;
587   if (c.y > (int)get_height()*32 - SCREEN_HEIGHT)
588     c.y = (int)get_height()*32 - SCREEN_HEIGHT;
589
590   if (int(get_width()*32) < SCREEN_WIDTH)
591     c.x = get_width()*16.0 - SCREEN_WIDTH/2.0;
592   if (int(get_height()*32) < SCREEN_HEIGHT)
593     c.y = get_height()*16.0 - SCREEN_HEIGHT/2.0;
594 }
595
596 void
597 WorldMap::update(float delta)
598 {
599   if(!in_level) {
600     Menu* menu = Menu::current();
601     if(menu != NULL) {
602       if(menu == worldmap_menu.get()) {
603         switch (worldmap_menu->check())
604         {
605           case MNID_RETURNWORLDMAP: // Return to game
606             Menu::set_current(0);
607             break;
608           case MNID_QUITWORLDMAP: // Quit Worldmap
609             g_main_loop->exit_screen();
610             break;
611         }
612       }
613
614       return;
615     }
616
617     // update GameObjects
618     for(size_t i = 0; i < game_objects.size(); ++i) {
619       GameObject* object = game_objects[i];
620       if(!panning || object != tux) {
621         object->update(delta);
622       }
623     }
624
625     // remove old GameObjects
626     for(GameObjects::iterator i = game_objects.begin();
627         i != game_objects.end(); ) {
628       GameObject* object = *i;
629       if(!object->is_valid()) {
630         try_unexpose(object);
631         object->unref();
632         i = game_objects.erase(i);
633       } else {
634         ++i;
635       }
636     }
637
638     /* update solid_tilemaps list */
639     //FIXME: this could be more efficient
640     solid_tilemaps.clear();
641     for(std::vector<GameObject*>::iterator i = game_objects.begin();
642         i != game_objects.end(); ++i)
643     {
644       TileMap* tm = dynamic_cast<TileMap*>(*i);
645       if (!tm) continue;
646       if (tm->is_solid()) solid_tilemaps.push_back(tm);
647     }
648
649     Vector requested_pos;
650
651     // position "camera"
652     if(!panning) {
653       camera_offset = get_camera_pos_for_tux();
654     } else {
655       Vector delta = pan_pos - camera_offset;
656       float mag = delta.norm();
657       if(mag > CAMERA_PAN_SPEED) {
658         delta *= CAMERA_PAN_SPEED/mag;
659       }
660       camera_offset += delta;
661       if(camera_offset == pan_pos) {
662         panning = false;
663       }
664     }
665
666     requested_pos = camera_offset;
667     clamp_camera_position(camera_offset);
668
669     if(panning) {
670       if(requested_pos.x != camera_offset.x) {
671         pan_pos.x = camera_offset.x;
672       }
673       if(requested_pos.y != camera_offset.y) {
674         pan_pos.y = camera_offset.y;
675       }
676     }
677
678     // handle input
679     bool enter_level = false;
680     if(g_main_controller->pressed(Controller::ACTION)
681        || g_main_controller->pressed(Controller::JUMP)
682        || g_main_controller->pressed(Controller::MENU_SELECT)) {
683       /* some people define UP and JUMP on the same key... */
684       if(!g_main_controller->pressed(Controller::UP))
685         enter_level = true;
686     }
687     if(g_main_controller->pressed(Controller::PAUSE_MENU))
688       on_escape_press();
689
690     // check for teleporters
691     Teleporter* teleporter = at_teleporter(tux->get_tile_pos());
692     if (teleporter && (teleporter->automatic || (enter_level && (!tux->is_moving())))) {
693       enter_level = false;
694       if (teleporter->worldmap != "") {
695         change(teleporter->worldmap, teleporter->spawnpoint);
696       } else {
697         // TODO: an animation, camera scrolling or a fading would be a nice touch
698         sound_manager->play("sounds/warp.wav");
699         tux->back_direction = D_NONE;
700         move_to_spawnpoint(teleporter->spawnpoint, true);
701       }
702     }
703
704     // check for auto-play levels
705     LevelTile* level = at_level();
706     if (level && (level->auto_play) && (!level->solved) && (!tux->is_moving())) {
707       enter_level = true;
708     }
709
710     if (enter_level && !tux->is_moving())
711     {
712       /* Check level action */
713       LevelTile* level = at_level();
714       if (!level) {
715         //Respawn if player on a tile with no level and nowhere to go.
716         int tile_data = tile_data_at(tux->get_tile_pos());
717         if(!( tile_data & ( Tile::WORLDMAP_NORTH |  Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST ))){
718           log_warning << "Player at illegal position " << tux->get_tile_pos().x << ", " << tux->get_tile_pos().y << " respawning." << std::endl;
719           move_to_spawnpoint("main");
720           return;
721         }
722         log_warning << "No level to enter at: " << tux->get_tile_pos().x << ", " << tux->get_tile_pos().y << std::endl;
723         return;
724       }
725
726       if (level->pos == tux->get_tile_pos()) {
727         try {
728           Vector shrinkpos = Vector(level->pos.x*32 + 16 - camera_offset.x,
729                                     level->pos.y*32 +  8 - camera_offset.y);
730           std::string levelfile = levels_path + level->get_name();
731
732           // update state and savegame
733           save_state();
734
735           g_main_loop->push_screen(new GameSession(levelfile, &level->statistics),
736                                    new ShrinkFade(shrinkpos, 1.0f));
737           in_level = true;
738         } catch(std::exception& e) {
739           log_fatal << "Couldn't load level: " << e.what() << std::endl;
740         }
741       }
742     }
743     else
744     {
745       //      tux->set_direction(input_direction);
746     }
747   }
748 }
749
750 int
751 WorldMap::tile_data_at(Vector p)
752 {
753   int dirs = 0;
754
755   for(std::list<TileMap*>::const_iterator i = solid_tilemaps.begin(); i != solid_tilemaps.end(); i++) {
756     TileMap* tilemap = *i;
757     const Tile* tile = tilemap->get_tile((int)p.x, (int)p.y);
758     int dirdata = tile->getData();
759     dirs |= dirdata;
760   }
761
762   return dirs;
763 }
764
765 int
766 WorldMap::available_directions_at(Vector p)
767 {
768   return tile_data_at(p) & Tile::WORLDMAP_DIR_MASK;
769 }
770
771 LevelTile*
772 WorldMap::at_level()
773 {
774   for(LevelTiles::iterator i = levels.begin(); i != levels.end(); ++i) {
775     LevelTile* level = *i;
776     if (level->pos == tux->get_tile_pos())
777       return level;
778   }
779
780   return NULL;
781 }
782
783 SpecialTile*
784 WorldMap::at_special_tile()
785 {
786   for(SpecialTiles::iterator i = special_tiles.begin();
787       i != special_tiles.end(); ++i) {
788     SpecialTile* special_tile = *i;
789     if (special_tile->pos == tux->get_tile_pos())
790       return special_tile;
791   }
792
793   return NULL;
794 }
795
796 SpriteChange*
797 WorldMap::at_sprite_change(const Vector& pos)
798 {
799   for(SpriteChanges::iterator i = sprite_changes.begin();
800       i != sprite_changes.end(); ++i) {
801     SpriteChange* sprite_change = *i;
802     if(sprite_change->pos == pos)
803       return sprite_change;
804   }
805
806   return NULL;
807 }
808
809 Teleporter*
810 WorldMap::at_teleporter(const Vector& pos)
811 {
812   for(std::vector<Teleporter*>::iterator i = teleporters.begin(); i != teleporters.end(); ++i) {
813     Teleporter* teleporter = *i;
814     if(teleporter->pos == pos) return teleporter;
815   }
816
817   return NULL;
818 }
819
820 void
821 WorldMap::draw(DrawingContext& context)
822 {
823   if (int(get_width()*32) < SCREEN_WIDTH || int(get_height()*32) < SCREEN_HEIGHT)
824     context.draw_filled_rect(Vector(0, 0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT),
825                              Color(0.0f, 0.0f, 0.0f, 1.0f), LAYER_BACKGROUND0);
826
827   context.set_ambient_color( ambient_light );
828   context.push_transform();
829   context.set_translation(camera_offset);
830
831   for(GameObjects::iterator i = game_objects.begin();
832       i != game_objects.end(); ++i) {
833     GameObject* object = *i;
834     if(!panning || object != tux) {
835       object->draw(context);
836     }
837   }
838
839   /*
840   // FIXME: make this a runtime switch similar to draw_collrects/show_collrects?
841   // draw visual indication of possible walk directions
842   static int flipme = 0; 
843   if (flipme++ & 0x04)
844   for (int x = 0; x < get_width(); x++) {
845   for (int y = 0; y < get_height(); y++) {
846   int data = tile_data_at(Vector(x,y));
847   int px = x * 32;
848   int py = y * 32;
849   const int W = 4;
850   if (data & Tile::WORLDMAP_NORTH)    context.draw_filled_rect(Rect(px + 16-W, py       , px + 16+W, py + 16-W), Color(0.2f, 0.2f, 0.2f, 0.7f), LAYER_FOREGROUND1 + 1000);
851   if (data & Tile::WORLDMAP_SOUTH)    context.draw_filled_rect(Rect(px + 16-W, py + 16+W, px + 16+W, py + 32  ), Color(0.2f, 0.2f, 0.2f, 0.7f), LAYER_FOREGROUND1 + 1000);
852   if (data & Tile::WORLDMAP_EAST)     context.draw_filled_rect(Rect(px + 16+W, py + 16-W, px + 32  , py + 16+W), Color(0.2f, 0.2f, 0.2f, 0.7f), LAYER_FOREGROUND1 + 1000);
853   if (data & Tile::WORLDMAP_WEST)     context.draw_filled_rect(Rect(px       , py + 16-W, px + 16-W, py + 16+W), Color(0.2f, 0.2f, 0.2f, 0.7f), LAYER_FOREGROUND1 + 1000);
854   if (data & Tile::WORLDMAP_DIR_MASK) context.draw_filled_rect(Rect(px + 16-W, py + 16-W, px + 16+W, py + 16+W), Color(0.2f, 0.2f, 0.2f, 0.7f), LAYER_FOREGROUND1 + 1000);
855   if (data & Tile::WORLDMAP_STOP)     context.draw_filled_rect(Rect(px + 4   , py + 4   , px + 28  , py + 28  ), Color(0.2f, 0.2f, 0.2f, 0.7f), LAYER_FOREGROUND1 + 1000);
856   }
857   }
858   */
859
860   draw_status(context);
861   context.pop_transform();
862 }
863
864 void
865 WorldMap::draw_status(DrawingContext& context)
866 {
867   context.push_transform();
868   context.set_translation(Vector(0, 0));
869
870   player_status->draw(context);
871
872   if (!tux->is_moving()) {
873     for(LevelTiles::iterator i = levels.begin(); i != levels.end(); ++i) {
874       LevelTile* level = *i;
875
876       if (level->pos == tux->get_tile_pos()) {
877         if(level->title == "")
878           get_level_title(*level);
879
880         context.draw_text(normal_font, level->title,
881                           Vector(SCREEN_WIDTH/2,
882                                  SCREEN_HEIGHT - normal_font->get_height() - 30),
883                           ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::level_title_color);
884
885         // if level is solved, draw level picture behind stats
886         /*
887           if (level->solved) {
888           if (const Surface* picture = level->get_picture()) {
889           Vector pos = Vector(SCREEN_WIDTH - picture->get_width(), SCREEN_HEIGHT - picture->get_height());
890           context.push_transform();
891           context.set_alpha(0.5);
892           context.draw_surface(picture, pos, LAYER_FOREGROUND1-1);
893           context.pop_transform();
894           }
895           }
896         */
897
898         level->statistics.draw_worldmap_info(context);
899         break;
900       }
901     }
902
903     for(SpecialTiles::iterator i = special_tiles.begin();
904         i != special_tiles.end(); ++i) {
905       SpecialTile* special_tile = *i;
906
907       if (special_tile->pos == tux->get_tile_pos()) {
908         /* Display an in-map message in the map, if any as been selected */
909         if(!special_tile->map_message.empty() && !special_tile->passive_message)
910           context.draw_text(normal_font, special_tile->map_message,
911                             Vector(SCREEN_WIDTH/2,
912                                    SCREEN_HEIGHT - normal_font->get_height() - 60),
913                             ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::message_color);
914         break;
915       }
916     }
917
918     // display teleporter messages
919     Teleporter* teleporter = at_teleporter(tux->get_tile_pos());
920     if (teleporter && (teleporter->message != "")) {
921       Vector pos = Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT - normal_font->get_height() - 30);
922       context.draw_text(normal_font, teleporter->message, pos, ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::teleporter_message_color);
923     }
924
925   }
926
927   /* Display a passive message in the map, if needed */
928   if(passive_message_timer.started())
929     context.draw_text(normal_font, passive_message,
930                       Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT - normal_font->get_height() - 60),
931                       ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::message_color);
932
933   context.pop_transform();
934 }
935
936 void
937 WorldMap::setup()
938 {
939   sound_manager->play_music(music);
940   Menu::set_current(NULL);
941
942   current_ = this;
943   load_state();
944
945   // if force_spawnpoint was set, move Tux there, then clear force_spawnpoint
946   if (force_spawnpoint != "") {
947     move_to_spawnpoint(force_spawnpoint);
948     force_spawnpoint = "";
949   }
950
951   tux->setup();
952
953   // register worldmap_table as worldmap in scripting
954   using namespace Scripting;
955
956   sq_pushroottable(global_vm);
957   sq_pushstring(global_vm, "worldmap", -1);
958   sq_pushobject(global_vm, worldmap_table);
959   if(SQ_FAILED(sq_createslot(global_vm, -3)))
960     throw SquirrelError(global_vm, "Couldn't set worldmap in roottable");
961   sq_pop(global_vm, 1);
962
963   //Run default.nut just before init script
964   try {
965     IFileStream in(levels_path + "/default.nut");
966     run_script(in, "WorldMap::default.nut");
967   } catch(std::exception& ) {
968     // doesn't exist or erroneous; do nothing
969   }
970
971   if(init_script != "") {
972     std::istringstream in(init_script);
973     run_script(in, "WorldMap::init");
974   }
975 }
976
977 void
978 WorldMap::leave()
979 {
980   using namespace Scripting;
981
982   // save state of world and player
983   save_state();
984
985   // remove worldmap_table from roottable
986   sq_pushroottable(global_vm);
987   sq_pushstring(global_vm, "worldmap", -1);
988   if(SQ_FAILED(sq_deleteslot(global_vm, -2, SQFalse)))
989     throw SquirrelError(global_vm, "Couldn't unset worldmap in roottable");
990   sq_pop(global_vm, 1);
991 }
992
993 void
994 WorldMap::save_state()
995 {
996   using namespace Scripting;
997
998   HSQUIRRELVM vm = global_vm;
999   int oldtop = sq_gettop(vm);
1000
1001   try {
1002     // get state table
1003     sq_pushroottable(vm);
1004     sq_pushstring(vm, "state", -1);
1005     if(SQ_FAILED(sq_get(vm, -2)))
1006       throw Scripting::SquirrelError(vm, "Couldn't get state table");
1007
1008     // get or create worlds table
1009     sq_pushstring(vm, "worlds", -1);
1010     if(SQ_FAILED(sq_get(vm, -2))) {
1011       sq_pushstring(vm, "worlds", -1);
1012       sq_newtable(vm);
1013       if(SQ_FAILED(sq_createslot(vm, -3)))
1014         throw Scripting::SquirrelError(vm, "Couldn't create state.worlds");
1015
1016       sq_pushstring(vm, "worlds", -1);
1017       if(SQ_FAILED(sq_get(vm, -2)))
1018         throw Scripting::SquirrelError(vm, "Couldn't create.get state.worlds");
1019     }
1020
1021     sq_pushstring(vm, map_filename.c_str(), map_filename.length());
1022     if(SQ_FAILED(sq_deleteslot(vm, -2, SQFalse)))
1023       sq_pop(vm, 1);
1024
1025     // construct new table for this worldmap
1026     sq_pushstring(vm, map_filename.c_str(), map_filename.length());
1027     sq_newtable(vm);
1028
1029     // store tux
1030     sq_pushstring(vm, "tux", -1);
1031     sq_newtable(vm);
1032
1033     store_float(vm, "x", tux->get_tile_pos().x);
1034     store_float(vm, "y", tux->get_tile_pos().y);
1035     store_string(vm, "back", direction_to_string(tux->back_direction));
1036
1037     sq_createslot(vm, -3);
1038
1039     // levels...
1040     sq_pushstring(vm, "levels", -1);
1041     sq_newtable(vm);
1042
1043     for(LevelTiles::iterator i = levels.begin(); i != levels.end(); ++i) {
1044       LevelTile* level = *i;
1045
1046       sq_pushstring(vm, level->get_name().c_str(), -1);
1047       sq_newtable(vm);
1048
1049       store_bool(vm, "solved", level->solved);
1050       level->statistics.serialize_to_squirrel(vm);
1051
1052       sq_createslot(vm, -3);
1053     }
1054
1055     sq_createslot(vm, -3);
1056
1057     // overall statistics...
1058     total_stats.serialize_to_squirrel(vm);
1059
1060     // push world into worlds table
1061     sq_createslot(vm, -3);
1062   } catch(std::exception& ) {
1063     sq_settop(vm, oldtop);
1064   }
1065
1066   sq_settop(vm, oldtop);
1067
1068   if(World::current() != NULL)
1069     World::current()->save_state();
1070 }
1071
1072 void
1073 WorldMap::load_state()
1074 {
1075   using namespace Scripting;
1076
1077   HSQUIRRELVM vm = global_vm;
1078   int oldtop = sq_gettop(vm);
1079
1080   try {
1081     // get state table
1082     sq_pushroottable(vm);
1083     sq_pushstring(vm, "state", -1);
1084     if(SQ_FAILED(sq_get(vm, -2)))
1085       throw Scripting::SquirrelError(vm, "Couldn't get state table");
1086
1087     // get worlds table
1088     sq_pushstring(vm, "worlds", -1);
1089     if(SQ_FAILED(sq_get(vm, -2)))
1090       throw Scripting::SquirrelError(vm, "Couldn't get state.worlds");
1091
1092     // get table for our world
1093     sq_pushstring(vm, map_filename.c_str(), map_filename.length());
1094     if(SQ_FAILED(sq_get(vm, -2)))
1095       throw Scripting::SquirrelError(vm, "Couldn't get state.worlds.mapfilename");
1096
1097     // load tux
1098     sq_pushstring(vm, "tux", -1);
1099     if(SQ_FAILED(sq_get(vm, -2)))
1100       throw Scripting::SquirrelError(vm, "Couldn't get tux");
1101
1102     Vector p;
1103     p.x = read_float(vm, "x");
1104     p.y = read_float(vm, "y");
1105     std::string back_str = read_string(vm, "back");
1106     tux->back_direction = string_to_direction(back_str);
1107     tux->set_tile_pos(p);
1108
1109     sq_pop(vm, 1);
1110
1111     // load levels
1112     sq_pushstring(vm, "levels", -1);
1113     if(SQ_FAILED(sq_get(vm, -2)))
1114       throw Scripting::SquirrelError(vm, "Couldn't get levels");
1115
1116     for(LevelTiles::iterator i = levels.begin(); i != levels.end(); ++i) {
1117       LevelTile* level = *i;
1118       sq_pushstring(vm, level->get_name().c_str(), -1);
1119       if(SQ_SUCCEEDED(sq_get(vm, -2))) {
1120         level->solved = read_bool(vm, "solved");
1121         level->sprite->set_action(level->solved ? "solved" : "default");
1122         level->statistics.unserialize_from_squirrel(vm);
1123         sq_pop(vm, 1);
1124       }
1125     }
1126
1127     // leave state table
1128     sq_pop(vm, 1);
1129
1130     // load overall statistics
1131     total_stats.unserialize_from_squirrel(vm);
1132
1133   } catch(std::exception& e) {
1134     log_debug << "Not loading worldmap state: " << e.what() << std::endl;
1135   }
1136   sq_settop(vm, oldtop);
1137
1138   in_level = false;
1139 }
1140
1141 size_t
1142 WorldMap::level_count()
1143 {
1144   return levels.size();
1145 }
1146
1147 size_t
1148 WorldMap::solved_level_count()
1149 {
1150   size_t count = 0;
1151   for(LevelTiles::iterator i = levels.begin(); i != levels.end(); ++i) {
1152     LevelTile* level = *i;
1153
1154     if(level->solved)
1155       count++;
1156   }
1157
1158   return count;
1159 }
1160
1161 HSQUIRRELVM
1162 WorldMap::run_script(std::istream& in, const std::string& sourcename)
1163 {
1164   using namespace Scripting;
1165
1166   // garbage collect thread list
1167   for(ScriptList::iterator i = scripts.begin();
1168       i != scripts.end(); ) {
1169     HSQOBJECT& object = *i;
1170     HSQUIRRELVM vm = object_to_vm(object);
1171
1172     if(sq_getvmstate(vm) != SQ_VMSTATE_SUSPENDED) {
1173       sq_release(global_vm, &object);
1174       i = scripts.erase(i);
1175       continue;
1176     }
1177
1178     ++i;
1179   }
1180
1181   HSQOBJECT object = create_thread(global_vm);
1182   scripts.push_back(object);
1183
1184   HSQUIRRELVM vm = object_to_vm(object);
1185
1186   // set worldmap_table as roottable for the thread
1187   sq_pushobject(vm, worldmap_table);
1188   sq_setroottable(vm);
1189
1190   compile_and_run(vm, in, sourcename);
1191
1192   return vm;
1193 }
1194
1195 float
1196 WorldMap::get_width() const
1197 {
1198   float width = 0;
1199   for(std::list<TileMap*>::const_iterator i = solid_tilemaps.begin(); i != solid_tilemaps.end(); i++) {
1200     TileMap* solids = *i;
1201     if (solids->get_width() > width) width = solids->get_width();
1202   }
1203   return width;
1204 }
1205
1206 float
1207 WorldMap::get_height() const
1208 {
1209   float height = 0;
1210   for(std::list<TileMap*>::const_iterator i = solid_tilemaps.begin(); i != solid_tilemaps.end(); i++) {
1211     TileMap* solids = *i;
1212     if (solids->get_height() > height) height = solids->get_height();
1213   }
1214   return height;
1215 }
1216
1217 } // namespace WorldMapNS
1218
1219 /* EOF */