| 1 | // SuperTux |
| 2 | // Copyright (C) 2004-2018 Ingo Ruhnke <grumbel@gmail.com> |
| 3 | // 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_state.hpp" |
| 19 | |
| 20 | #include "math/vector.hpp" |
| 21 | #include "object/tilemap.hpp" |
| 22 | #include "squirrel/squirrel_util.hpp" |
| 23 | #include "supertux/savegame.hpp" |
| 24 | #include "supertux/tile.hpp" |
| 25 | #include "util/log.hpp" |
| 26 | #include "worldmap/level_tile.hpp" |
| 27 | #include "worldmap/sprite_change.hpp" |
| 28 | #include "worldmap/tux.hpp" |
| 29 | #include "worldmap/worldmap.hpp" |
| 30 | |
| 31 | namespace worldmap { |
| 32 | |
| 33 | WorldMapState::WorldMapState(WorldMap& worldmap) : |
| 34 | m_worldmap(worldmap) |
| 35 | { |
| 36 | } |
| 37 | |
| 38 | void |
| 39 | WorldMapState::load_state() |
| 40 | { |
| 41 | log_debug << "loading worldmap state" << std::endl; |
| 42 | |
| 43 | SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm(); |
| 44 | SQInteger oldtop = sq_gettop(vm.get_vm()); |
| 45 | |
| 46 | try { |
| 47 | // get state table |
| 48 | sq_pushroottable(vm.get_vm()); |
| 49 | vm.get_table_entry("state" ); |
| 50 | vm.get_table_entry("worlds" ); |
| 51 | |
| 52 | // if a non-canonical entry is present, replace them with a canonical one |
| 53 | if (m_worldmap.m_map_filename != "/levels/world2/worldmap.stwm" ) { |
| 54 | std::string old_map_filename = m_worldmap.m_map_filename.substr(1); |
| 55 | if (vm.has_property(old_map_filename.c_str())) { |
| 56 | vm.rename_table_entry(old_map_filename.c_str(), m_worldmap.m_map_filename.c_str()); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | vm.get_table_entry(m_worldmap.m_map_filename); |
| 61 | |
| 62 | // load tux |
| 63 | vm.get_table_entry("tux" ); |
| 64 | |
| 65 | Vector p; |
| 66 | if (!vm.get_float("x" , p.x) || !vm.get_float("y" , p.y)) |
| 67 | { |
| 68 | log_warning << "Player position not set, respawning." << std::endl; |
| 69 | m_worldmap.move_to_spawnpoint("main" ); |
| 70 | } |
| 71 | std::string back_str = vm.read_string("back" ); |
| 72 | m_worldmap.m_tux->m_back_direction = string_to_direction(back_str); |
| 73 | m_worldmap.m_tux->set_tile_pos(p); |
| 74 | |
| 75 | int tile_data = m_worldmap.tile_data_at(p); |
| 76 | if (!( tile_data & ( Tile::WORLDMAP_NORTH | Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST ))) { |
| 77 | log_warning << "Player at illegal position " << p.x << ", " << p.y << " respawning." << std::endl; |
| 78 | m_worldmap.move_to_spawnpoint("main" ); |
| 79 | } |
| 80 | |
| 81 | sq_pop(vm.get_vm(), 1); |
| 82 | |
| 83 | // load levels |
| 84 | vm.get_table_entry("levels" ); |
| 85 | for (auto& level : m_worldmap.get_objects_by_type<LevelTile>()) { |
| 86 | sq_pushstring(vm.get_vm(), level.get_level_filename().c_str(), -1); |
| 87 | if (SQ_SUCCEEDED(sq_get(vm.get_vm(), -2))) |
| 88 | { |
| 89 | bool solved = false; |
| 90 | vm.get_bool("solved" , solved); |
| 91 | level.set_solved(solved); |
| 92 | |
| 93 | bool perfect = false; |
| 94 | vm.get_bool("perfect" , perfect); |
| 95 | level.set_perfect(perfect); |
| 96 | |
| 97 | level.update_sprite_action(); |
| 98 | level.get_statistics().unserialize_from_squirrel(vm); |
| 99 | sq_pop(vm.get_vm(), 1); |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | // leave levels table |
| 104 | sq_pop(vm.get_vm(), 1); |
| 105 | |
| 106 | try { |
| 107 | vm.get_table_entry("tilemaps" ); |
| 108 | sq_pushnull(vm.get_vm()); // Null-iterator |
| 109 | while (SQ_SUCCEEDED(sq_next(vm.get_vm(), -2))) |
| 110 | { |
| 111 | const char* key; // Name of specific tilemap table |
| 112 | if (SQ_SUCCEEDED(sq_getstring(vm.get_vm(), -2, &key))) |
| 113 | { |
| 114 | auto tilemap = m_worldmap.get_object_by_name<TileMap>(key); |
| 115 | if (tilemap != nullptr) |
| 116 | { |
| 117 | sq_pushnull(vm.get_vm()); // null iterator (inner); |
| 118 | while (SQ_SUCCEEDED(sq_next(vm.get_vm(), -2))) |
| 119 | { |
| 120 | const char* property_key; |
| 121 | if (SQ_SUCCEEDED(sq_getstring(vm.get_vm(), -2, &property_key))) |
| 122 | { |
| 123 | auto propKey = std::string(property_key); |
| 124 | if (propKey == "alpha" ) |
| 125 | { |
| 126 | float alpha_value = 1.0; |
| 127 | if (SQ_SUCCEEDED(sq_getfloat(vm.get_vm(), -1, &alpha_value))) |
| 128 | { |
| 129 | tilemap->set_alpha(alpha_value); |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | sq_pop(vm.get_vm(), 2); // Pop key/value from the stack |
| 134 | } |
| 135 | sq_pop(vm.get_vm(), 1); // Pop null iterator |
| 136 | } |
| 137 | } |
| 138 | sq_pop(vm.get_vm(), 2); // Pop key value pair from stack |
| 139 | } |
| 140 | sq_pop(vm.get_vm(), 1); // Pop null |
| 141 | sq_pop(vm.get_vm(), 1); // leave tilemaps table |
| 142 | } |
| 143 | catch(const SquirrelError&) |
| 144 | { |
| 145 | // Failed to get tilemap entry. This could indicate |
| 146 | // that no savable tilemaps have been found. In any |
| 147 | // case: This is not severe at all. |
| 148 | } |
| 149 | |
| 150 | if (m_worldmap.get_object_count<SpriteChange>() > 0) |
| 151 | { |
| 152 | // load sprite change action: |
| 153 | vm.get_table_entry("sprite-changes" ); |
| 154 | for (auto& sc : m_worldmap.get_objects_by_type<SpriteChange>()) |
| 155 | { |
| 156 | auto key = std::to_string(int(sc.get_pos().x)) + "_" + |
| 157 | std::to_string(int(sc.get_pos().y)); |
| 158 | sq_pushstring(vm.get_vm(), key.c_str(), -1); |
| 159 | if (SQ_SUCCEEDED(sq_get(vm.get_vm(), -2))) { |
| 160 | bool show_stay_action = false; |
| 161 | if (!vm.get_bool("show-stay-action" , show_stay_action)) |
| 162 | { |
| 163 | sc.clear_stay_action(/* propagate = */ false); |
| 164 | } |
| 165 | else |
| 166 | { |
| 167 | if (show_stay_action) |
| 168 | { |
| 169 | sc.set_stay_action(); |
| 170 | } |
| 171 | else |
| 172 | { |
| 173 | sc.clear_stay_action(/* propagate = */ false); |
| 174 | } |
| 175 | } |
| 176 | sq_pop(vm.get_vm(), 1); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | // Leave sprite changes table |
| 181 | sq_pop(vm.get_vm(), 1); |
| 182 | } |
| 183 | |
| 184 | } catch(std::exception& e) { |
| 185 | log_debug << "Not loading worldmap state: " << e.what() << std::endl; |
| 186 | save_state(); // make new initial save |
| 187 | m_worldmap.move_to_spawnpoint("main" ); // set tux to main spawnpoint |
| 188 | } |
| 189 | sq_settop(vm.get_vm(), oldtop); |
| 190 | |
| 191 | m_worldmap.m_in_level = false; |
| 192 | } |
| 193 | |
| 194 | void |
| 195 | WorldMapState::save_state() const |
| 196 | { |
| 197 | SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm(); |
| 198 | SQInteger oldtop = sq_gettop(vm.get_vm()); |
| 199 | |
| 200 | try { |
| 201 | // get state table |
| 202 | sq_pushroottable(vm.get_vm()); |
| 203 | vm.get_table_entry("state" ); |
| 204 | vm.get_or_create_table_entry("worlds" ); |
| 205 | |
| 206 | vm.delete_table_entry(m_worldmap.m_map_filename.c_str()); |
| 207 | |
| 208 | // construct new table for this worldmap |
| 209 | vm.begin_table(m_worldmap.m_map_filename.c_str()); |
| 210 | |
| 211 | // store tux |
| 212 | vm.begin_table("tux" ); |
| 213 | |
| 214 | vm.store_float("x" , m_worldmap.m_tux->get_tile_pos().x); |
| 215 | vm.store_float("y" , m_worldmap.m_tux->get_tile_pos().y); |
| 216 | vm.store_string("back" , direction_to_string(m_worldmap.m_tux->m_back_direction)); |
| 217 | |
| 218 | vm.end_table("tux" ); |
| 219 | |
| 220 | // sprite change objects: |
| 221 | if (m_worldmap.get_object_count<SpriteChange>() > 0) |
| 222 | { |
| 223 | vm.begin_table("sprite-changes" ); |
| 224 | |
| 225 | for (const auto& sc : m_worldmap.get_objects_by_type<SpriteChange>()) |
| 226 | { |
| 227 | auto key = std::to_string(int(sc.get_pos().x)) + "_" + |
| 228 | std::to_string(int(sc.get_pos().y)); |
| 229 | vm.begin_table(key.c_str()); |
| 230 | vm.store_bool("show-stay-action" , sc.show_stay_action()); |
| 231 | vm.end_table(key.c_str()); |
| 232 | } |
| 233 | |
| 234 | vm.end_table("sprite-changes" ); |
| 235 | } |
| 236 | |
| 237 | // tilemap visibility |
| 238 | sq_pushstring(vm.get_vm(), "tilemaps" , -1); |
| 239 | sq_newtable(vm.get_vm()); |
| 240 | for (auto& tilemap : m_worldmap.get_objects_by_type<::TileMap>()) |
| 241 | { |
| 242 | if (!tilemap.get_name().empty()) |
| 243 | { |
| 244 | sq_pushstring(vm.get_vm(), tilemap.get_name().c_str(), -1); |
| 245 | sq_newtable(vm.get_vm()); |
| 246 | vm.store_float("alpha" , tilemap.get_alpha()); |
| 247 | if (SQ_FAILED(sq_createslot(vm.get_vm(), -3))) |
| 248 | { |
| 249 | throw std::runtime_error("failed to create '" + m_worldmap.m_name + "' table entry" ); |
| 250 | } |
| 251 | } |
| 252 | } |
| 253 | if (SQ_FAILED(sq_createslot(vm.get_vm(), -3))) |
| 254 | { |
| 255 | throw std::runtime_error("failed to create '" + m_worldmap.m_name + "' table entry" ); |
| 256 | } |
| 257 | |
| 258 | // levels... |
| 259 | vm.begin_table("levels" ); |
| 260 | |
| 261 | for (const auto& level : m_worldmap.get_objects_by_type<LevelTile>()) |
| 262 | { |
| 263 | vm.begin_table(level.get_level_filename().c_str()); |
| 264 | |
| 265 | vm.store_bool("solved" , level.is_solved()); |
| 266 | vm.store_bool("perfect" , level.is_perfect()); |
| 267 | |
| 268 | level.get_statistics().serialize_to_squirrel(vm); |
| 269 | vm.end_table(level.get_level_filename().c_str()); |
| 270 | } |
| 271 | vm.end_table("levels" ); |
| 272 | |
| 273 | // push world into worlds table |
| 274 | vm.end_table(m_worldmap.m_map_filename.c_str()); |
| 275 | } catch(std::exception& ) { |
| 276 | sq_settop(vm.get_vm(), oldtop); |
| 277 | } |
| 278 | |
| 279 | sq_settop(vm.get_vm(), oldtop); |
| 280 | |
| 281 | m_worldmap.m_savegame.save(); |
| 282 | } |
| 283 | |
| 284 | } // namespace worldmap |
| 285 | |
| 286 | /* EOF */ |
| 287 | |