Auto-run levels are automatically marked as solved.
[supertux.git] / src / worldmap / worldmap.cpp
index 1321dff..c74e945 100644 (file)
 #include "audio/sound_manager.hpp"
 #include "control/joystickkeyboardcontroller.hpp"
 #include "gui/menu.hpp"
+#include "gui/menu_manager.hpp"
 #include "gui/mousecursor.hpp"
 #include "lisp/lisp.hpp"
 #include "lisp/list_iterator.hpp"
 #include "lisp/parser.hpp"
 #include "object/background.hpp"
+#include "object/decal.hpp"
 #include "object/tilemap.hpp"
-#include "physfs/ifile_stream.hpp"
+#include "physfs/ifile_streambuf.hpp"
 #include "scripting/squirrel_error.hpp"
 #include "scripting/squirrel_util.hpp"
 #include "sprite/sprite.hpp"
 #include "sprite/sprite_manager.hpp"
 #include "supertux/game_session.hpp"
-#include "supertux/main.hpp"
-#include "supertux/mainloop.hpp"
-#include "supertux/options_menu.hpp"
+#include "supertux/globals.hpp"
+#include "supertux/screen_manager.hpp"
+#include "supertux/menu/menu_storage.hpp"
+#include "supertux/menu/options_menu.hpp"
+#include "supertux/menu/worldmap_menu.hpp"
 #include "supertux/player_status.hpp"
 #include "supertux/resources.hpp"
 #include "supertux/sector.hpp"
@@ -58,7 +62,7 @@
 #include "util/file_system.hpp"
 #include "util/gettext.hpp"
 #include "util/log.hpp"
-#include "util/log.hpp"
+#include "util/reader.hpp"
 #include "video/drawing_context.hpp"
 #include "video/surface.hpp"
 #include "worldmap/level.hpp"
 
 static const float CAMERA_PAN_SPEED = 5.0;
 
-namespace WorldMapNS {
-
-enum WorldMapMenuIDs {
-  MNID_RETURNWORLDMAP,
-  MNID_QUITWORLDMAP
-};
+namespace worldmap {
 
 WorldMap* WorldMap::current_ = NULL;
 
-Direction reverse_dir(Direction direction)
-{
-  switch(direction)
-  {
-    case D_WEST:
-      return D_EAST;
-    case D_EAST:
-      return D_WEST;
-    case D_NORTH:
-      return D_SOUTH;
-    case D_SOUTH:
-      return D_NORTH;
-    case D_NONE:
-      return D_NONE;
-  }
-  return D_NONE;
-}
-
-std::string
-direction_to_string(Direction direction)
-{
-  switch(direction)
-  {
-    case D_WEST:
-      return "west";
-    case D_EAST:
-      return "east";
-    case D_NORTH:
-      return "north";
-    case D_SOUTH:
-      return "south";
-    default:
-      return "none";
-  }
-}
-
-Direction
-string_to_direction(const std::string& directory)
-{
-  if (directory == "west")
-    return D_WEST;
-  else if (directory == "east")
-    return D_EAST;
-  else if (directory == "north")
-    return D_NORTH;
-  else if (directory == "south")
-    return D_SOUTH;
-  else if (directory == "none")
-    return D_NONE;
-  else {
-    log_warning << "unknown direction: \"" << directory << "\"" << std::endl;
-    return D_NONE;
-  }
-}
-
-//---------------------------------------------------------------------------
-
-WorldMap::WorldMap(const std::string& filename, const std::string& force_spawnpoint) :
+WorldMap::WorldMap(const std::string& filename, PlayerStatus* player_status, const std::string& force_spawnpoint) :
   tux(0),
-  tileset(NULL), 
+  player_status(player_status),
+  tileset(NULL),
   free_tileset(false),
   worldmap_menu(),
   camera_offset(),
@@ -158,9 +101,9 @@ WorldMap::WorldMap(const std::string& filename, const std::string& force_spawnpo
   total_stats(),
   worldmap_table(),
   scripts(),
-  ambient_light( 1.0f, 1.0f, 1.0f, 1.0f ), 
+  ambient_light( 1.0f, 1.0f, 1.0f, 1.0f ),
   force_spawnpoint(force_spawnpoint),
-  in_level(false), 
+  in_level(false),
   pan_pos(),
   panning(false)
 {
@@ -172,39 +115,33 @@ WorldMap::WorldMap(const std::string& filename, const std::string& force_spawnpo
 
   total_stats.reset();
 
-  worldmap_menu.reset(new Menu());
-  worldmap_menu->add_label(_("Pause"));
-  worldmap_menu->add_hl();
-  worldmap_menu->add_entry(MNID_RETURNWORLDMAP, _("Continue"));
-  worldmap_menu->add_submenu(_("Options"), get_options_menu());
-  worldmap_menu->add_hl();
-  worldmap_menu->add_entry(MNID_QUITWORLDMAP, _("Quit World"));
+  worldmap_menu.reset(new WorldmapMenu());
 
   // create a new squirrel table for the worldmap
-  using namespace Scripting;
+  using namespace scripting;
 
   sq_collectgarbage(global_vm);
   sq_newtable(global_vm);
   sq_pushroottable(global_vm);
   if(SQ_FAILED(sq_setdelegate(global_vm, -2)))
-    throw Scripting::SquirrelError(global_vm, "Couldn't set worldmap_table delegate");
+    throw scripting::SquirrelError(global_vm, "Couldn't set worldmap_table delegate");
 
   sq_resetobject(&worldmap_table);
   if(SQ_FAILED(sq_getstackobj(global_vm, -1, &worldmap_table)))
-    throw Scripting::SquirrelError(global_vm, "Couldn't get table from stack");
+    throw scripting::SquirrelError(global_vm, "Couldn't get table from stack");
 
   sq_addref(global_vm, &worldmap_table);
   sq_pop(global_vm, 1);
 
   sound_manager->preload("sounds/warp.wav");
-  
+
   // load worldmap objects
   load(filename);
 }
 
 WorldMap::~WorldMap()
 {
-  using namespace Scripting;
+  using namespace scripting;
 
   if(free_tileset)
     delete tileset;
@@ -250,11 +187,11 @@ WorldMap::add_object(GameObject* object)
 void
 WorldMap::try_expose(GameObject* object)
 {
-  ScriptInterface* interface = dynamic_cast<ScriptInterface*> (object);
-  if(interface != NULL) {
-    HSQUIRRELVM vm = Scripting::global_vm;
+  ScriptInterface* object_ = dynamic_cast<ScriptInterface*> (object);
+  if(object_ != NULL) {
+    HSQUIRRELVM vm = scripting::global_vm;
     sq_pushobject(vm, worldmap_table);
-    interface->expose(vm, -1);
+    object_->expose(vm, -1);
     sq_pop(vm, 1);
   }
 }
@@ -262,13 +199,13 @@ WorldMap::try_expose(GameObject* object)
 void
 WorldMap::try_unexpose(GameObject* object)
 {
-  ScriptInterface* interface = dynamic_cast<ScriptInterface*> (object);
-  if(interface != NULL) {
-    HSQUIRRELVM vm = Scripting::global_vm;
+  ScriptInterface* object_ = dynamic_cast<ScriptInterface*> (object);
+  if(object_ != NULL) {
+    HSQUIRRELVM vm = scripting::global_vm;
     SQInteger oldtop = sq_gettop(vm);
     sq_pushobject(vm, worldmap_table);
     try {
-      interface->unexpose(vm, -1);
+      object_->unexpose(vm, -1);
     } catch(std::exception& e) {
       log_warning << "Couldn't unregister object: " << e.what() << std::endl;
     }
@@ -302,8 +239,8 @@ WorldMap::move_to_spawnpoint(const std::string& spawnpoint, bool pan)
 void
 WorldMap::change(const std::string& filename, const std::string& force_spawnpoint)
 {
-  g_main_loop->exit_screen();
-  g_main_loop->push_screen(new WorldMap(filename, force_spawnpoint));
+  g_screen_manager->exit_screen();
+  g_screen_manager->push_screen(new WorldMap(filename, player_status, force_spawnpoint));
 }
 
 void
@@ -356,24 +293,27 @@ WorldMap::load(const std::string& filename)
       } else if(iter.item() == "init-script") {
         iter.value()->get(init_script);
       } else if(iter.item() == "worldmap-spawnpoint") {
-        SpawnPoint* sp = new SpawnPoint(iter.lisp());
+        SpawnPoint* sp = new SpawnPoint(*iter.lisp());
         spawn_points.push_back(sp);
       } else if(iter.item() == "level") {
-        LevelTile* level = new LevelTile(levels_path, iter.lisp());
+        LevelTile* level = new LevelTile(levels_path, *iter.lisp());
         levels.push_back(level);
         add_object(level);
       } else if(iter.item() == "special-tile") {
-        SpecialTile* special_tile = new SpecialTile(iter.lisp());
+        SpecialTile* special_tile = new SpecialTile(*iter.lisp());
         special_tiles.push_back(special_tile);
         add_object(special_tile);
       } else if(iter.item() == "sprite-change") {
-        SpriteChange* sprite_change = new SpriteChange(iter.lisp());
+        SpriteChange* sprite_change = new SpriteChange(*iter.lisp());
         sprite_changes.push_back(sprite_change);
         add_object(sprite_change);
       } else if(iter.item() == "teleporter") {
-        Teleporter* teleporter = new Teleporter(iter.lisp());
+        Teleporter* teleporter = new Teleporter(*iter.lisp());
         teleporters.push_back(teleporter);
         add_object(teleporter);
+      } else if(iter.item() == "decal") {
+        Decal* decal = new Decal(*iter.lisp());
+        add_object(decal);
       } else if(iter.item() == "ambient-light") {
         std::vector<float> vColor;
         sector->get( "ambient-light", vColor );
@@ -424,6 +364,32 @@ WorldMap::get_level_title(LevelTile& level)
   }
 }
 
+void
+WorldMap::get_level_target_time(LevelTile& level)
+{
+  if(last_position == tux->get_tile_pos()) {
+    level.target_time = last_target_time;
+    return;
+  }
+
+  try {
+    lisp::Parser parser;
+    const lisp::Lisp* root = parser.parse(levels_path + level.get_name());
+
+    const lisp::Lisp* level_lisp = root->get_lisp("supertux-level");
+    if(!level_lisp)
+      return;
+
+    level_lisp->get("target-time", level.target_time);
+
+    last_position = level.pos;
+    last_target_time = level.target_time;
+  } catch(std::exception& e) {
+    log_warning << "Problem when reading level target time: " << e.what() << std::endl;
+    return;
+  }
+}
+
 void WorldMap::calculate_total_stats()
 {
   total_stats.zero();
@@ -439,11 +405,11 @@ void
 WorldMap::on_escape_press()
 {
   // Show or hide the menu
-  if(!Menu::current()) {
-    Menu::set_current(worldmap_menu.get());
+  if(!MenuManager::current()) {
+    MenuManager::set_current(worldmap_menu.get());
     tux->set_direction(D_NONE);  // stop tux movement when menu is called
   } else {
-    Menu::set_current(NULL);
+    MenuManager::set_current(NULL);
   }
 }
 
@@ -521,6 +487,12 @@ WorldMap::finished_level(Level* gamelevel)
   // deal with statistics
   level->statistics.merge(gamelevel->stats);
   calculate_total_stats();
+  get_level_target_time(*level);
+  if(level->statistics.completed(level->statistics, level->target_time)) {
+    level->perfect = true;
+    if(level->sprite->has_action("perfect"))
+      level->sprite->set_action("perfect");
+  }
 
   save_state();
 
@@ -596,16 +568,16 @@ void
 WorldMap::update(float delta)
 {
   if(!in_level) {
-    Menu* menu = Menu::current();
+    Menu* menu = MenuManager::current();
     if(menu != NULL) {
       if(menu == worldmap_menu.get()) {
         switch (worldmap_menu->check())
         {
           case MNID_RETURNWORLDMAP: // Return to game
-            Menu::set_current(0);
+            MenuManager::set_current(0);
             break;
           case MNID_QUITWORLDMAP: // Quit Worldmap
-            g_main_loop->exit_screen();
+            g_screen_manager->exit_screen();
             break;
         }
       }
@@ -675,15 +647,16 @@ WorldMap::update(float delta)
     }
 
     // handle input
+    Controller *controller = g_jk_controller->get_main_controller();
     bool enter_level = false;
-    if(g_main_controller->pressed(Controller::ACTION)
-       || g_main_controller->pressed(Controller::JUMP)
-       || g_main_controller->pressed(Controller::MENU_SELECT)) {
+    if(controller->pressed(Controller::ACTION)
+       || controller->pressed(Controller::JUMP)
+       || controller->pressed(Controller::MENU_SELECT)) {
       /* some people define UP and JUMP on the same key... */
-      if(!g_main_controller->pressed(Controller::UP))
+      if(!controller->pressed(Controller::UP))
         enter_level = true;
     }
-    if(g_main_controller->pressed(Controller::PAUSE_MENU))
+    if(controller->pressed(Controller::PAUSE_MENU))
       on_escape_press();
 
     // check for teleporters
@@ -704,6 +677,8 @@ WorldMap::update(float delta)
     LevelTile* level = at_level();
     if (level && (level->auto_play) && (!level->solved) && (!tux->is_moving())) {
       enter_level = true;
+      // automatically mark these levels as solved in case player aborts
+      level->solved = true;
     }
 
     if (enter_level && !tux->is_moving())
@@ -731,7 +706,7 @@ WorldMap::update(float delta)
           // update state and savegame
           save_state();
 
-          g_main_loop->push_screen(new GameSession(levelfile, &level->statistics),
+          g_screen_manager->push_screen(new GameSession(levelfile, player_status, &level->statistics),
                                    new ShrinkFade(shrinkpos, 1.0f));
           in_level = true;
         } catch(std::exception& e) {
@@ -838,7 +813,7 @@ WorldMap::draw(DrawingContext& context)
   /*
   // FIXME: make this a runtime switch similar to draw_collrects/show_collrects?
   // draw visual indication of possible walk directions
-  static int flipme = 0; 
+  static int flipme = 0;
   if (flipme++ & 0x04)
   for (int x = 0; x < get_width(); x++) {
   for (int y = 0; y < get_height(); y++) {
@@ -876,10 +851,10 @@ WorldMap::draw_status(DrawingContext& context)
         if(level->title == "")
           get_level_title(*level);
 
-        context.draw_text(normal_font, level->title,
+        context.draw_text(Resources::normal_font, level->title,
                           Vector(SCREEN_WIDTH/2,
-                                 SCREEN_HEIGHT - normal_font->get_height() - 30),
-                          ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::level_title_color);
+                                 SCREEN_HEIGHT - Resources::normal_font->get_height() - 10),
+                          ALIGN_CENTER, LAYER_HUD, WorldMap::level_title_color);
 
         // if level is solved, draw level picture behind stats
         /*
@@ -894,7 +869,8 @@ WorldMap::draw_status(DrawingContext& context)
           }
         */
 
-        level->statistics.draw_worldmap_info(context);
+        get_level_target_time(*level);
+        level->statistics.draw_worldmap_info(context, level->target_time);
         break;
       }
     }
@@ -906,9 +882,9 @@ WorldMap::draw_status(DrawingContext& context)
       if (special_tile->pos == tux->get_tile_pos()) {
         /* Display an in-map message in the map, if any as been selected */
         if(!special_tile->map_message.empty() && !special_tile->passive_message)
-          context.draw_text(normal_font, special_tile->map_message,
+          context.draw_text(Resources::normal_font, special_tile->map_message,
                             Vector(SCREEN_WIDTH/2,
-                                   SCREEN_HEIGHT - normal_font->get_height() - 60),
+                                   SCREEN_HEIGHT - Resources::normal_font->get_height() - 60),
                             ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::message_color);
         break;
       }
@@ -917,16 +893,16 @@ WorldMap::draw_status(DrawingContext& context)
     // display teleporter messages
     Teleporter* teleporter = at_teleporter(tux->get_tile_pos());
     if (teleporter && (teleporter->message != "")) {
-      Vector pos = Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT - normal_font->get_height() - 30);
-      context.draw_text(normal_font, teleporter->message, pos, ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::teleporter_message_color);
+      Vector pos = Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT - Resources::normal_font->get_height() - 30);
+      context.draw_text(Resources::normal_font, teleporter->message, pos, ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::teleporter_message_color);
     }
 
   }
 
   /* Display a passive message in the map, if needed */
   if(passive_message_timer.started())
-    context.draw_text(normal_font, passive_message,
-                      Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT - normal_font->get_height() - 60),
+    context.draw_text(Resources::normal_font, passive_message,
+                      Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT - Resources::normal_font->get_height() - 60),
                       ALIGN_CENTER, LAYER_FOREGROUND1, WorldMap::message_color);
 
   context.pop_transform();
@@ -936,7 +912,7 @@ void
 WorldMap::setup()
 {
   sound_manager->play_music(music);
-  Menu::set_current(NULL);
+  MenuManager::set_current(NULL);
 
   current_ = this;
   load_state();
@@ -950,7 +926,7 @@ WorldMap::setup()
   tux->setup();
 
   // register worldmap_table as worldmap in scripting
-  using namespace Scripting;
+  using namespace scripting;
 
   sq_pushroottable(global_vm);
   sq_pushstring(global_vm, "worldmap", -1);
@@ -961,7 +937,8 @@ WorldMap::setup()
 
   //Run default.nut just before init script
   try {
-    IFileStream in(levels_path + "/default.nut");
+    IFileStreambuf ins(levels_path + "default.nut");
+    std::istream in(&ins);
     run_script(in, "WorldMap::default.nut");
   } catch(std::exception& ) {
     // doesn't exist or erroneous; do nothing
@@ -976,7 +953,7 @@ WorldMap::setup()
 void
 WorldMap::leave()
 {
-  using namespace Scripting;
+  using namespace scripting;
 
   // save state of world and player
   save_state();
@@ -992,7 +969,7 @@ WorldMap::leave()
 void
 WorldMap::save_state()
 {
-  using namespace Scripting;
+  using namespace scripting;
 
   HSQUIRRELVM vm = global_vm;
   int oldtop = sq_gettop(vm);
@@ -1002,7 +979,7 @@ WorldMap::save_state()
     sq_pushroottable(vm);
     sq_pushstring(vm, "state", -1);
     if(SQ_FAILED(sq_get(vm, -2)))
-      throw Scripting::SquirrelError(vm, "Couldn't get state table");
+      throw scripting::SquirrelError(vm, "Couldn't get state table");
 
     // get or create worlds table
     sq_pushstring(vm, "worlds", -1);
@@ -1010,11 +987,11 @@ WorldMap::save_state()
       sq_pushstring(vm, "worlds", -1);
       sq_newtable(vm);
       if(SQ_FAILED(sq_createslot(vm, -3)))
-        throw Scripting::SquirrelError(vm, "Couldn't create state.worlds");
+        throw scripting::SquirrelError(vm, "Couldn't create state.worlds");
 
       sq_pushstring(vm, "worlds", -1);
       if(SQ_FAILED(sq_get(vm, -2)))
-        throw Scripting::SquirrelError(vm, "Couldn't create.get state.worlds");
+        throw scripting::SquirrelError(vm, "Couldn't create.get state.worlds");
     }
 
     sq_pushstring(vm, map_filename.c_str(), map_filename.length());
@@ -1046,6 +1023,7 @@ WorldMap::save_state()
       sq_newtable(vm);
 
       store_bool(vm, "solved", level->solved);
+      store_bool(vm, "perfect", level->perfect);
       level->statistics.serialize_to_squirrel(vm);
 
       sq_createslot(vm, -3);
@@ -1071,7 +1049,7 @@ WorldMap::save_state()
 void
 WorldMap::load_state()
 {
-  using namespace Scripting;
+  using namespace scripting;
 
   HSQUIRRELVM vm = global_vm;
   int oldtop = sq_gettop(vm);
@@ -1081,22 +1059,22 @@ WorldMap::load_state()
     sq_pushroottable(vm);
     sq_pushstring(vm, "state", -1);
     if(SQ_FAILED(sq_get(vm, -2)))
-      throw Scripting::SquirrelError(vm, "Couldn't get state table");
+      throw scripting::SquirrelError(vm, "Couldn't get state table");
 
     // get worlds table
     sq_pushstring(vm, "worlds", -1);
     if(SQ_FAILED(sq_get(vm, -2)))
-      throw Scripting::SquirrelError(vm, "Couldn't get state.worlds");
+      throw scripting::SquirrelError(vm, "Couldn't get state.worlds");
 
     // get table for our world
     sq_pushstring(vm, map_filename.c_str(), map_filename.length());
     if(SQ_FAILED(sq_get(vm, -2)))
-      throw Scripting::SquirrelError(vm, "Couldn't get state.worlds.mapfilename");
+      throw scripting::SquirrelError(vm, "Couldn't get state.worlds.mapfilename");
 
     // load tux
     sq_pushstring(vm, "tux", -1);
     if(SQ_FAILED(sq_get(vm, -2)))
-      throw Scripting::SquirrelError(vm, "Couldn't get tux");
+      throw scripting::SquirrelError(vm, "Couldn't get tux");
 
     Vector p;
     p.x = read_float(vm, "x");
@@ -1110,14 +1088,18 @@ WorldMap::load_state()
     // load levels
     sq_pushstring(vm, "levels", -1);
     if(SQ_FAILED(sq_get(vm, -2)))
-      throw Scripting::SquirrelError(vm, "Couldn't get levels");
+      throw scripting::SquirrelError(vm, "Couldn't get levels");
 
     for(LevelTiles::iterator i = levels.begin(); i != levels.end(); ++i) {
       LevelTile* level = *i;
       sq_pushstring(vm, level->get_name().c_str(), -1);
       if(SQ_SUCCEEDED(sq_get(vm, -2))) {
         level->solved = read_bool(vm, "solved");
-        level->sprite->set_action(level->solved ? "solved" : "default");
+        level->perfect = read_bool(vm, "perfect");
+        if(!level->solved)
+          level->sprite->set_action("default");
+        else
+          level->sprite->set_action((level->sprite->has_action("perfect") && level->perfect) ? "perfect" : "solved");
         level->statistics.unserialize_from_squirrel(vm);
         sq_pop(vm, 1);
       }
@@ -1160,7 +1142,7 @@ WorldMap::solved_level_count()
 HSQUIRRELVM
 WorldMap::run_script(std::istream& in, const std::string& sourcename)
 {
-  using namespace Scripting;
+  using namespace scripting;
 
   // garbage collect thread list
   for(ScriptList::iterator i = scripts.begin();
@@ -1213,6 +1195,6 @@ WorldMap::get_height() const
   return height;
 }
 
-} // namespace WorldMapNS
+} // namespace worldmap
 
 /* EOF */