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 <physfs.h>
21 
22 #include "audio/sound_manager.hpp"
23 #include "control/input_manager.hpp"
24 #include "gui/menu_manager.hpp"
25 #include "object/ambient_light.hpp"
26 #include "object/decal.hpp"
27 #include "object/display_effect.hpp"
28 #include "object/music_object.hpp"
29 #include "object/tilemap.hpp"
30 #include "physfs/ifile_stream.hpp"
31 #include "physfs/physfs_file_system.hpp"
32 #include "scripting/worldmap.hpp"
33 #include "sprite/sprite.hpp"
34 #include "squirrel/squirrel_environment.hpp"
35 #include "supertux/d_scope.hpp"
36 #include "supertux/debug.hpp"
37 #include "supertux/fadetoblack.hpp"
38 #include "supertux/game_manager.hpp"
39 #include "supertux/game_session.hpp"
40 #include "supertux/gameconfig.hpp"
41 #include "supertux/level.hpp"
42 #include "supertux/menu/menu_storage.hpp"
43 #include "supertux/player_status_hud.hpp"
44 #include "supertux/resources.hpp"
45 #include "supertux/savegame.hpp"
46 #include "supertux/screen_manager.hpp"
47 #include "supertux/shrinkfade.hpp"
48 #include "supertux/tile.hpp"
49 #include "supertux/tile_manager.hpp"
50 #include "util/file_system.hpp"
51 #include "util/reader.hpp"
52 #include "util/reader_document.hpp"
53 #include "util/reader_mapping.hpp"
54 #include "video/compositor.hpp"
55 #include "video/video_system.hpp"
56 #include "video/video_system.hpp"
57 #include "video/viewport.hpp"
58 #include "worldmap/camera.hpp"
59 #include "worldmap/level_tile.hpp"
60 #include "worldmap/spawn_point.hpp"
61 #include "worldmap/special_tile.hpp"
62 #include "worldmap/sprite_change.hpp"
63 #include "worldmap/teleporter.hpp"
64 #include "worldmap/tux.hpp"
65 #include "worldmap/worldmap_parser.hpp"
66 #include "worldmap/worldmap_screen.hpp"
67 #include "worldmap/worldmap_state.hpp"
68 
69 namespace worldmap {
70 
WorldMap(const std::string & filename,Savegame & savegame,const std::string & force_spawnpoint_)71 WorldMap::WorldMap(const std::string& filename, Savegame& savegame, const std::string& force_spawnpoint_) :
72   m_squirrel_environment(new SquirrelEnvironment(SquirrelVirtualMachine::current()->get_vm(), "worldmap")),
73   m_camera(new Camera),
74   m_enter_level(false),
75   m_tux(),
76   m_savegame(savegame),
77   m_tileset(nullptr),
78   m_name("<no title>"),
79   m_init_script(),
80   m_passive_message_timer(),
81   m_passive_message(),
82   m_map_filename(),
83   m_levels_path(),
84   m_spawn_points(),
85   m_force_spawnpoint(force_spawnpoint_),
86   m_main_is_default(true),
87   m_initial_fade_tilemap(),
88   m_fade_direction(),
89   m_in_level(false)
90 {
91   m_tux = &add<Tux>(this);
92   add<PlayerStatusHUD>(m_savegame.get_player_status());
93 
94   SoundManager::current()->preload("sounds/warp.wav");
95 
96   BIND_WORLDMAP(*this);
97 
98   // load worldmap objects
99   WorldMapParser parser(*this);
100   parser.load_worldmap(filename);
101 }
102 
~WorldMap()103 WorldMap::~WorldMap()
104 {
105   clear_objects();
106   m_spawn_points.clear();
107 }
108 
109 void
finish_construction()110 WorldMap::finish_construction()
111 {
112   if (!get_object_by_type<AmbientLight>()) {
113     add<AmbientLight>(Color::WHITE);
114   }
115 
116   if (!get_object_by_type<MusicObject>()) {
117     add<MusicObject>();
118   }
119 
120   if (!get_object_by_type<DisplayEffect>()) {
121     add<DisplayEffect>("Effect");
122   }
123 
124   flush_game_objects();
125 }
126 
127 bool
before_object_add(GameObject & object)128 WorldMap::before_object_add(GameObject& object)
129 {
130   m_squirrel_environment->try_expose(object);
131   return true;
132 }
133 
134 void
before_object_remove(GameObject & object)135 WorldMap::before_object_remove(GameObject& object)
136 {
137   m_squirrel_environment->try_unexpose(object);
138 }
139 
140 void
move_to_spawnpoint(const std::string & spawnpoint,bool pan,bool main_as_default)141 WorldMap::move_to_spawnpoint(const std::string& spawnpoint, bool pan, bool main_as_default)
142 {
143   auto sp = get_spawnpoint_by_name(spawnpoint);
144   if (sp != nullptr) {
145     Vector p = sp->get_pos();
146     m_tux->set_tile_pos(p);
147     m_tux->set_direction(sp->get_auto_dir());
148     if (pan) {
149       m_camera->pan();
150     }
151     return;
152   }
153 
154   log_warning << "Spawnpoint '" << spawnpoint << "' not found." << std::endl;
155   if (spawnpoint != "main" && main_as_default) {
156     move_to_spawnpoint("main");
157   }
158 }
159 
160 void
change(const std::string & filename,const std::string & force_spawnpoint_)161 WorldMap::change(const std::string& filename, const std::string& force_spawnpoint_)
162 {
163   m_savegame.get_player_status().last_worldmap = filename;
164   ScreenManager::current()->pop_screen();
165   ScreenManager::current()->push_screen(std::make_unique<WorldMapScreen>(
166                                           std::make_unique<WorldMap>(filename, m_savegame, force_spawnpoint_)));
167 }
168 
169 void
on_escape_press()170 WorldMap::on_escape_press()
171 {
172   // Show or hide the menu
173   if (!MenuManager::instance().is_active()) {
174     MenuManager::instance().set_menu(MenuStorage::WORLDMAP_MENU);
175     m_tux->set_direction(Direction::NONE);  // stop tux movement when menu is called
176   } else {
177     MenuManager::instance().clear_menu_stack();
178   }
179 }
180 
181 Vector
get_next_tile(const Vector & pos,const Direction & direction) const182 WorldMap::get_next_tile(const Vector& pos, const Direction& direction) const
183 {
184   auto position = pos;
185   switch (direction) {
186     case Direction::WEST:
187       position.x -= 1;
188       break;
189     case Direction::EAST:
190       position.x += 1;
191       break;
192     case Direction::NORTH:
193       position.y -= 1;
194       break;
195     case Direction::SOUTH:
196       position.y += 1;
197       break;
198     case Direction::NONE:
199       break;
200   }
201   return position;
202 }
203 
204 bool
path_ok(const Direction & direction,const Vector & old_pos,Vector * new_pos) const205 WorldMap::path_ok(const Direction& direction, const Vector& old_pos, Vector* new_pos) const
206 {
207   *new_pos = get_next_tile(old_pos, direction);
208 
209   if (!(new_pos->x >= 0 && new_pos->x < get_tiles_width()
210         && new_pos->y >= 0 && new_pos->y < get_tiles_height()))
211   { // New position is outsite the tilemap
212     return false;
213   }
214   else
215   { // Check if the tile allows us to go to new_pos
216     int old_tile_data = tile_data_at(old_pos);
217     int new_tile_data = tile_data_at(*new_pos);
218     switch (direction)
219     {
220       case Direction::WEST:
221         return (old_tile_data & Tile::WORLDMAP_WEST
222                 && new_tile_data & Tile::WORLDMAP_EAST);
223 
224       case Direction::EAST:
225         return (old_tile_data & Tile::WORLDMAP_EAST
226                 && new_tile_data & Tile::WORLDMAP_WEST);
227 
228       case Direction::NORTH:
229         return (old_tile_data & Tile::WORLDMAP_NORTH
230                 && new_tile_data & Tile::WORLDMAP_SOUTH);
231 
232       case Direction::SOUTH:
233         return (old_tile_data & Tile::WORLDMAP_SOUTH
234                 && new_tile_data & Tile::WORLDMAP_NORTH);
235 
236       case Direction::NONE:
237         log_warning << "path_ok() can't walk if direction is NONE" << std::endl;
238         assert(false);
239     }
240     return false;
241   }
242 }
243 
244 void
finished_level(Level * gamelevel)245 WorldMap::finished_level(Level* gamelevel)
246 {
247   // TODO use Level* parameter here?
248   auto level = at_level();
249 
250   if (level == nullptr) {
251     return;
252   }
253 
254   bool old_level_state = level->is_solved();
255   level->set_solved(true);
256 
257   // deal with statistics
258   level->get_statistics().update(gamelevel->m_stats);
259 
260   if (level->get_statistics().completed(level->get_statistics(), level->get_target_time())) {
261     level->set_perfect(true);
262   }
263 
264   save_state();
265 
266   if (old_level_state != level->is_solved()) {
267     // Try to detect the next direction to which we should walk
268     // FIXME: Mostly a hack
269     Direction dir = Direction::NONE;
270 
271     int dirdata = available_directions_at(m_tux->get_tile_pos());
272     // first, test for crossroads
273     if (dirdata == Tile::WORLDMAP_CNSE ||
274         dirdata == Tile::WORLDMAP_CNSW ||
275         dirdata == Tile::WORLDMAP_CNEW ||
276         dirdata == Tile::WORLDMAP_CSEW ||
277         dirdata == Tile::WORLDMAP_CNSEW)
278       dir = Direction::NONE;
279     else if (dirdata & Tile::WORLDMAP_NORTH
280              && m_tux->m_back_direction != Direction::NORTH)
281       dir = Direction::NORTH;
282     else if (dirdata & Tile::WORLDMAP_SOUTH
283              && m_tux->m_back_direction != Direction::SOUTH)
284       dir = Direction::SOUTH;
285     else if (dirdata & Tile::WORLDMAP_EAST
286              && m_tux->m_back_direction != Direction::EAST)
287       dir = Direction::EAST;
288     else if (dirdata & Tile::WORLDMAP_WEST
289              && m_tux->m_back_direction != Direction::WEST)
290       dir = Direction::WEST;
291 
292     if (dir != Direction::NONE) {
293       m_tux->set_direction(dir);
294     }
295   }
296 
297   if (!level->get_extro_script().empty()) {
298     try {
299       run_script(level->get_extro_script(), "worldmap:extro_script");
300     } catch(std::exception& e) {
301       log_warning << "Couldn't run level-extro-script: " << e.what() << std::endl;
302     }
303   }
304 }
305 
306 void
process_input(const Controller & controller)307 WorldMap::process_input(const Controller& controller)
308 {
309   m_enter_level = false;
310 
311   if (controller.pressed(Control::ACTION) ||
312       controller.pressed(Control::JUMP) ||
313       controller.pressed(Control::MENU_SELECT))
314   {
315     // some people define UP and JUMP on the same key...
316     if (!controller.pressed(Control::UP)) {
317       m_enter_level = true;
318     }
319   }
320 
321   if (controller.pressed(Control::START) ||
322       controller.pressed(Control::ESCAPE))
323   {
324     on_escape_press();
325   }
326 
327   if (controller.pressed(Control::CHEAT_MENU) &&
328       g_config->developer_mode)
329   {
330     MenuManager::instance().set_menu(MenuStorage::WORLDMAP_CHEAT_MENU);
331   }
332 
333   if (controller.pressed(Control::DEBUG_MENU) &&
334       g_config->developer_mode)
335   {
336     MenuManager::instance().set_menu(MenuStorage::DEBUG_MENU);
337   }
338 }
339 
340 void
update(float dt_sec)341 WorldMap::update(float dt_sec)
342 {
343   BIND_WORLDMAP(*this);
344 
345   if (m_in_level) return;
346   if (MenuManager::instance().is_active()) return;
347 
348   GameObjectManager::update(dt_sec);
349 
350   m_camera->update(dt_sec);
351 
352   {
353     // check for teleporters
354     auto teleporter = at_teleporter(m_tux->get_tile_pos());
355     if (teleporter && (teleporter->is_automatic() || (m_enter_level && (!m_tux->is_moving())))) {
356       m_enter_level = false;
357       if (!teleporter->get_worldmap().empty()) {
358         change(teleporter->get_worldmap(), teleporter->get_spawnpoint());
359       } else {
360         // TODO: an animation, camera scrolling or a fading would be a nice touch
361         SoundManager::current()->play("sounds/warp.wav");
362         m_tux->m_back_direction = Direction::NONE;
363         move_to_spawnpoint(teleporter->get_spawnpoint(), true);
364       }
365     }
366   }
367 
368   {
369     // check for auto-play levels
370     auto level = at_level();
371     if (level && level->is_auto_play() && !level->is_solved() && !m_tux->is_moving()) {
372       m_enter_level = true;
373       // automatically mark these levels as solved in case player aborts
374       level->set_solved(true);
375     }
376   }
377 
378   {
379     if (m_enter_level && !m_tux->is_moving())
380     {
381       /* Check level action */
382       auto level_ = at_level();
383       if (!level_) {
384         //Respawn if player on a tile with no level and nowhere to go.
385         int tile_data = tile_data_at(m_tux->get_tile_pos());
386         if (!( tile_data & ( Tile::WORLDMAP_NORTH |  Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST ))){
387           log_warning << "Player at illegal position " << m_tux->get_tile_pos().x << ", " << m_tux->get_tile_pos().y << " respawning." << std::endl;
388           move_to_spawnpoint("main");
389           return;
390         }
391         log_warning << "No level to enter at: " << m_tux->get_tile_pos().x << ", " << m_tux->get_tile_pos().y << std::endl;
392         return;
393       }
394 
395       if (level_->get_pos() == m_tux->get_tile_pos()) {
396         try {
397           Vector shrinkpos = Vector(level_->get_pos().x * 32 + 16 - m_camera->get_offset().x,
398                                     level_->get_pos().y * 32 +  8 - m_camera->get_offset().y);
399           std::string levelfile = m_levels_path + level_->get_level_filename();
400 
401           // update state and savegame
402           save_state();
403           ScreenManager::current()->push_screen(std::make_unique<GameSession>(levelfile, m_savegame, &level_->get_statistics()),
404                                                 std::make_unique<ShrinkFade>(shrinkpos, 1.0f));
405 
406           m_in_level = true;
407         } catch(std::exception& e) {
408           log_fatal << "Couldn't load level: " << e.what() << std::endl;
409         }
410       }
411     }
412     else
413     {
414       // tux->set_direction(input_direction);
415     }
416   }
417 }
418 
419 int
tile_data_at(const Vector & p) const420 WorldMap::tile_data_at(const Vector& p) const
421 {
422   int dirs = 0;
423 
424   for (const auto& tilemap : get_solid_tilemaps()) {
425     const Tile& tile = tilemap->get_tile(static_cast<int>(p.x), static_cast<int>(p.y));
426     int dirdata = tile.get_data();
427     dirs |= dirdata;
428   }
429 
430   return dirs;
431 }
432 
433 int
available_directions_at(const Vector & p) const434 WorldMap::available_directions_at(const Vector& p) const
435 {
436   return tile_data_at(p) & Tile::WORLDMAP_DIR_MASK;
437 }
438 
439 LevelTile*
at_level() const440 WorldMap::at_level() const
441 {
442   for (auto& level : get_objects_by_type<LevelTile>()) {
443     if (level.get_pos() == m_tux->get_tile_pos())
444       return &level;
445   }
446 
447   return nullptr;
448 }
449 
450 SpecialTile*
at_special_tile() const451 WorldMap::at_special_tile() const
452 {
453   for (auto& special_tile : get_objects_by_type<SpecialTile>()) {
454     if (special_tile.get_pos() == m_tux->get_tile_pos())
455       return &special_tile;
456   }
457 
458   return nullptr;
459 }
460 
461 SpriteChange*
at_sprite_change(const Vector & pos) const462 WorldMap::at_sprite_change(const Vector& pos) const
463 {
464   for (auto& sprite_change : get_objects_by_type<SpriteChange>()) {
465     if (sprite_change.get_pos() == pos)
466       return &sprite_change;
467   }
468 
469   return nullptr;
470 }
471 
472 Teleporter*
at_teleporter(const Vector & pos) const473 WorldMap::at_teleporter(const Vector& pos) const
474 {
475   for (auto& teleporter : get_objects_by_type<Teleporter>()) {
476     if (teleporter.get_pos() == pos)
477       return &teleporter;
478   }
479 
480   return nullptr;
481 }
482 
483 void
draw(DrawingContext & context)484 WorldMap::draw(DrawingContext& context)
485 {
486   BIND_WORLDMAP(*this);
487 
488   if (get_width() < static_cast<float>(context.get_width()) ||
489       get_height() < static_cast<float>(context.get_height()))
490   {
491     context.color().draw_filled_rect(context.get_rect(),
492                                      Color(0.0f, 0.0f, 0.0f, 1.0f), LAYER_BACKGROUND0);
493   }
494 
495   context.push_transform();
496   context.set_translation(m_camera->get_offset());
497 
498   GameObjectManager::draw(context);
499 
500   if (g_debug.show_worldmap_path)
501   {
502     for (int x = 0; x < static_cast<int>(get_tiles_width()); x++) {
503       for (int y = 0; y < static_cast<int>(get_tiles_height()); y++) {
504         const int data = tile_data_at(Vector(static_cast<float>(x), static_cast<float>(y)));
505         const int px = x * 32;
506         const int py = y * 32;
507         const int W = 4;
508         const int layer = LAYER_FOREGROUND1 + 1000;
509         const Color color(1.0f, 0.0f, 1.0f, 0.5f);
510         if (data & Tile::WORLDMAP_NORTH)    context.color().draw_filled_rect(Rect(px + 16-W, py       , px + 16+W, py + 16-W), color, layer);
511         if (data & Tile::WORLDMAP_SOUTH)    context.color().draw_filled_rect(Rect(px + 16-W, py + 16+W, px + 16+W, py + 32  ), color, layer);
512         if (data & Tile::WORLDMAP_EAST)     context.color().draw_filled_rect(Rect(px + 16+W, py + 16-W, px + 32  , py + 16+W), color, layer);
513         if (data & Tile::WORLDMAP_WEST)     context.color().draw_filled_rect(Rect(px       , py + 16-W, px + 16-W, py + 16+W), color, layer);
514         if (data & Tile::WORLDMAP_DIR_MASK) context.color().draw_filled_rect(Rect(px + 16-W, py + 16-W, px + 16+W, py + 16+W), color, layer);
515         if (data & Tile::WORLDMAP_STOP)     context.color().draw_filled_rect(Rect(px + 4   , py + 4   , px + 28  , py + 28  ), color, layer);
516       }
517     }
518   }
519 
520   draw_status(context);
521   context.pop_transform();
522 }
523 
524 void
draw_status(DrawingContext & context)525 WorldMap::draw_status(DrawingContext& context)
526 {
527   context.push_transform();
528   context.set_translation(Vector(0, 0));
529 
530   if (!m_tux->is_moving()) {
531     for (auto& level : get_objects_by_type<LevelTile>()) {
532       if (level.get_pos() == m_tux->get_tile_pos()) {
533         context.color().draw_text(Resources::normal_font, level.get_title(),
534                                   Vector(static_cast<float>(context.get_width()) / 2.0f,
535                                          static_cast<float>(context.get_height()) - Resources::normal_font->get_height() - 10),
536                                   ALIGN_CENTER, LAYER_HUD, level.get_title_color());
537 
538         if (g_config->developer_mode) {
539           context.color().draw_text(Resources::small_font, FileSystem::join(level.get_basedir(), level.get_level_filename()),
540                                     Vector(static_cast<float>(context.get_width()) / 2.0f,
541                                            static_cast<float>(context.get_height()) - Resources::normal_font->get_height() - 25),
542                                     ALIGN_CENTER, LAYER_HUD, level.get_title_color());
543         }
544 
545         // if level is solved, draw level picture behind stats
546         /*
547           if (level.solved) {
548           if (const Surface* picture = level.get_picture()) {
549           Vector pos = Vector(context.get_width() - picture->get_width(), context.get_height() - picture->get_height());
550           context.push_transform();
551           context.set_alpha(0.5);
552           context.color().draw_surface(picture, pos, LAYER_FOREGROUND1-1);
553           context.pop_transform();
554           }
555           }
556         */
557         level.get_statistics().draw_worldmap_info(context, level.get_target_time());
558         break;
559       }
560     }
561 
562     for (auto& special_tile : get_objects_by_type<SpecialTile>()) {
563       if (special_tile.get_pos() == m_tux->get_tile_pos()) {
564         /* Display an in-map message in the map, if any as been selected */
565         if (!special_tile.get_map_message().empty() && !special_tile.is_passive_message())
566           context.color().draw_text(Resources::normal_font, special_tile.get_map_message(),
567                                     Vector(static_cast<float>(context.get_width()) / 2.0f,
568                                            static_cast<float>(context.get_height()) - static_cast<float>(Resources::normal_font->get_height()) - 60.0f),
569                                     ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::message_color);
570         break;
571       }
572     }
573 
574     // display teleporter messages
575     auto teleporter = at_teleporter(m_tux->get_tile_pos());
576     if (teleporter && (!teleporter->get_message().empty())) {
577       Vector pos = Vector(static_cast<float>(context.get_width()) / 2.0f,
578                           static_cast<float>(context.get_height()) - Resources::normal_font->get_height() - 30.0f);
579       context.color().draw_text(Resources::normal_font, teleporter->get_message(), pos, ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::teleporter_message_color);
580     }
581   }
582 
583   /* Display a passive message in the map, if needed */
584   if (m_passive_message_timer.started())
585     context.color().draw_text(Resources::normal_font, m_passive_message,
586                               Vector(static_cast<float>(context.get_width()) / 2.0f,
587                                      static_cast<float>(context.get_height()) - Resources::normal_font->get_height() - 60.0f),
588                               ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::message_color);
589 
590   context.pop_transform();
591 }
592 
593 void
setup()594 WorldMap::setup()
595 {
596   auto& music_object = get_singleton_by_type<MusicObject>();
597   music_object.play_music(MusicType::LEVEL_MUSIC);
598 
599   MenuManager::instance().clear_menu_stack();
600   ScreenManager::current()->set_screen_fade(std::make_unique<FadeToBlack>(FadeToBlack::FADEIN, 1.0f));
601 
602   load_state();
603 
604   // if force_spawnpoint was set, move Tux there, then clear force_spawnpoint
605   if (!m_force_spawnpoint.empty()) {
606     move_to_spawnpoint(m_force_spawnpoint, false, m_main_is_default);
607     m_force_spawnpoint = "";
608     m_main_is_default = true;
609   }
610 
611   // If we specified a fade tilemap, let's fade it:
612   if (!m_initial_fade_tilemap.empty())
613   {
614     auto tilemap = get_object_by_name<TileMap>(m_initial_fade_tilemap);
615     if (tilemap != nullptr)
616     {
617       if (m_fade_direction == 0)
618       {
619         tilemap->fade(1.0, 1);
620       }
621       else
622       {
623         tilemap->fade(0.0, 1);
624       }
625     }
626     m_initial_fade_tilemap = "";
627   }
628 
629   m_tux->setup();
630 
631   // register worldmap_table as worldmap in scripting
632   m_squirrel_environment->expose_self();
633 
634   m_squirrel_environment->expose("settings", std::make_unique<scripting::WorldMap>(this));
635 
636   //Run default.nut just before init script
637   try {
638     IFileStream in(m_levels_path + "default.nut");
639     m_squirrel_environment->run_script(in, "WorldMap::default.nut");
640   } catch(std::exception& ) {
641     // doesn't exist or erroneous; do nothing
642   }
643 
644   if (!m_init_script.empty()) {
645     m_squirrel_environment->run_script(m_init_script, "WorldMap::init");
646   }
647   m_tux->process_special_tile( at_special_tile() );
648 }
649 
650 void
leave()651 WorldMap::leave()
652 {
653   // save state of world and player
654   save_state();
655 
656   // remove worldmap_table from roottable
657   m_squirrel_environment->unexpose_self();
658 
659   GameManager::current()->load_next_worldmap();
660 }
661 
662 void
set_levels_solved(bool solved,bool perfect)663 WorldMap::set_levels_solved(bool solved, bool perfect)
664 {
665   for (auto& level : get_objects_by_type<LevelTile>())
666   {
667     level.set_solved(solved);
668     level.set_perfect(perfect);
669   }
670 }
671 
672 size_t
level_count() const673 WorldMap::level_count() const
674 {
675   return get_object_count<LevelTile>();
676 }
677 
678 size_t
solved_level_count() const679 WorldMap::solved_level_count() const
680 {
681   size_t count = 0;
682   for (auto& level : get_objects_by_type<LevelTile>()) {
683     if (level.is_solved()) {
684       count++;
685     }
686   }
687 
688   return count;
689 }
690 
691 void
load_state()692 WorldMap::load_state()
693 {
694   WorldMapState state(*this);
695   state.load_state();
696 }
697 
698 void
save_state()699 WorldMap::save_state()
700 {
701   WorldMapState state(*this);
702   state.save_state();
703 }
704 
705 void
run_script(const std::string & script,const std::string & sourcename)706 WorldMap::run_script(const std::string& script, const std::string& sourcename)
707 {
708   m_squirrel_environment->run_script(script, sourcename);
709 }
710 
711 void
set_passive_message(const std::string & message,float time)712 WorldMap::set_passive_message(const std::string& message, float time)
713 {
714    m_passive_message = message;
715    m_passive_message_timer.start(time);
716 }
717 
718 Vector
get_tux_pos()719 WorldMap::get_tux_pos()
720 {
721   return m_tux->get_pos();
722 }
723 
724 } // namespace worldmap
725 
726 /* EOF */
727