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