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
31namespace worldmap {
32
33WorldMapState::WorldMapState(WorldMap& worldmap) :
34 m_worldmap(worldmap)
35{
36}
37
38void
39WorldMapState::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
194void
195WorldMapState::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