savegame.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. // SuperTux
  2. // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
  3. // 2014 Ingo Ruhnke <grumbel@gmail.com>
  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. #include "supertux/savegame.hpp"
  18. #include <algorithm>
  19. #include <physfs.h>
  20. #include "control/input_manager.hpp"
  21. #include "physfs/physfs_file_system.hpp"
  22. #include "physfs/util.hpp"
  23. #include "squirrel/serialize.hpp"
  24. #include "squirrel/squirrel_util.hpp"
  25. #include "squirrel/squirrel_virtual_machine.hpp"
  26. #include "supertux/player_status.hpp"
  27. #include "supertux/profile_manager.hpp"
  28. #include "util/file_system.hpp"
  29. #include "util/log.hpp"
  30. #include "util/reader_document.hpp"
  31. #include "util/reader_mapping.hpp"
  32. #include "util/writer.hpp"
  33. #include "worldmap/worldmap.hpp"
  34. namespace {
  35. std::vector<LevelState> get_level_states(SquirrelVM& vm)
  36. {
  37. std::vector<LevelState> results;
  38. sq_pushnull(vm.get_vm());
  39. while (SQ_SUCCEEDED(sq_next(vm.get_vm(), -2)))
  40. {
  41. //here -1 is the value and -2 is the key
  42. const char* result;
  43. if (SQ_FAILED(sq_getstring(vm.get_vm(), -2, &result)))
  44. {
  45. std::ostringstream msg;
  46. msg << "Couldn't get string value";
  47. throw SquirrelError(vm.get_vm(), msg.str());
  48. }
  49. else
  50. {
  51. LevelState level_state;
  52. level_state.filename = result;
  53. vm.get_bool("solved", level_state.solved);
  54. vm.get_bool("perfect", level_state.perfect);
  55. results.push_back(level_state);
  56. }
  57. // pops key and val before the next iteration
  58. sq_pop(vm.get_vm(), 2);
  59. }
  60. return results;
  61. }
  62. } // namespace
  63. void
  64. LevelsetState::store_level_state(const LevelState& in_state)
  65. {
  66. auto it = std::find_if(level_states.begin(), level_states.end(),
  67. [&in_state](const LevelState& state)
  68. {
  69. return state.filename == in_state.filename;
  70. });
  71. if (it != level_states.end())
  72. {
  73. *it = in_state;
  74. }
  75. else
  76. {
  77. level_states.push_back(in_state);
  78. }
  79. }
  80. LevelState
  81. LevelsetState::get_level_state(const std::string& filename) const
  82. {
  83. auto it = std::find_if(level_states.begin(), level_states.end(),
  84. [filename](const LevelState& state)
  85. {
  86. return state.filename == filename;
  87. });
  88. if (it != level_states.end())
  89. {
  90. return *it;
  91. }
  92. else
  93. {
  94. log_info << "creating new level state for " << filename << std::endl;
  95. LevelState state;
  96. state.filename = filename;
  97. return state;
  98. }
  99. }
  100. std::unique_ptr<Savegame>
  101. Savegame::from_profile(int profile, const std::string& world_name, bool base_data)
  102. {
  103. auto savegame = std::make_unique<Savegame>(ProfileManager::current()->get_profile(profile), world_name);
  104. savegame->load(base_data);
  105. return savegame;
  106. }
  107. std::unique_ptr<Savegame>
  108. Savegame::from_current_profile(const std::string& world_name, bool base_data)
  109. {
  110. auto savegame = std::make_unique<Savegame>(ProfileManager::current()->get_current_profile(), world_name);
  111. savegame->load(base_data);
  112. return savegame;
  113. }
  114. Savegame::Savegame(Profile& profile, const std::string& world_name) :
  115. m_profile(profile),
  116. m_world_name(world_name),
  117. m_player_status(new PlayerStatus(InputManager::current()->get_num_users()))
  118. {
  119. }
  120. std::string
  121. Savegame::get_filename() const
  122. {
  123. return FileSystem::join(m_profile.get_basedir(), m_world_name + ".stsg");
  124. }
  125. bool
  126. Savegame::is_title_screen() const
  127. {
  128. // bit of a hack, TitleScreen uses a dummy savegame without a world name
  129. return m_world_name.empty();
  130. }
  131. void
  132. Savegame::load(bool base_data)
  133. {
  134. if (m_world_name.empty())
  135. {
  136. log_debug << "no world name provided for savegame, skipping load" << std::endl;
  137. return;
  138. }
  139. clear_state_table();
  140. const std::string filename = get_filename();
  141. if (!PHYSFS_exists(filename.c_str()))
  142. {
  143. log_info << filename << " doesn't exist, not loading state" << std::endl;
  144. }
  145. else
  146. {
  147. if (physfsutil::is_directory(filename))
  148. {
  149. log_info << filename << " is a directory, not loading state" << std::endl;
  150. return;
  151. }
  152. log_debug << "loading savegame from " << filename << std::endl;
  153. try
  154. {
  155. SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
  156. auto doc = ReaderDocument::from_file(filename);
  157. auto root = doc.get_root();
  158. if (root.get_name() != "supertux-savegame")
  159. {
  160. throw std::runtime_error("file is not a supertux-savegame file");
  161. }
  162. else
  163. {
  164. auto mapping = root.get_mapping();
  165. int version = 1;
  166. mapping.get("version", version);
  167. if (version != 1)
  168. {
  169. throw std::runtime_error("incompatible savegame version");
  170. }
  171. else
  172. {
  173. /** Load Tux */
  174. std::optional<ReaderMapping> tux;
  175. if (!mapping.get("tux", tux))
  176. {
  177. throw std::runtime_error("No tux section in savegame");
  178. }
  179. {
  180. m_player_status->read(*tux);
  181. }
  182. if (base_data)
  183. return;
  184. /** Load "state" table */
  185. std::optional<ReaderMapping> state;
  186. if (!mapping.get("state", state))
  187. {
  188. throw std::runtime_error("No state section in savegame");
  189. }
  190. else
  191. {
  192. sq_pushroottable(vm.get_vm());
  193. vm.get_table_entry("state");
  194. load_squirrel_table(vm.get_vm(), -1, *state);
  195. sq_pop(vm.get_vm(), 2);
  196. }
  197. }
  198. }
  199. }
  200. catch(const std::exception& e)
  201. {
  202. log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
  203. }
  204. }
  205. }
  206. void
  207. Savegame::clear_state_table()
  208. {
  209. SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
  210. // delete existing state table, if it exists
  211. sq_pushroottable(vm.get_vm());
  212. {
  213. // create a new empty state table
  214. vm.create_empty_table("state");
  215. }
  216. sq_pop(vm.get_vm(), 1);
  217. }
  218. void
  219. Savegame::save()
  220. {
  221. if (m_world_name.empty())
  222. {
  223. log_debug << "no world name set for savegame, skipping save" << std::endl;
  224. return;
  225. }
  226. const std::string filename = get_filename();
  227. log_debug << "saving savegame to " << filename << std::endl;
  228. m_profile.save(); // Make sure profile directory exists, save profile info
  229. SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
  230. Writer writer(filename);
  231. writer.start_list("supertux-savegame");
  232. writer.write("version", 1);
  233. using namespace worldmap;
  234. if (WorldMap::current() != nullptr)
  235. {
  236. std::ostringstream title;
  237. title << WorldMap::current()->get_title();
  238. title << " (" << WorldMap::current()->solved_level_count()
  239. << "/" << WorldMap::current()->level_count() << ")";
  240. writer.write("title", title.str());
  241. }
  242. writer.start_list("tux");
  243. m_player_status->write(writer);
  244. writer.end_list("tux");
  245. writer.start_list("state");
  246. sq_pushroottable(vm.get_vm());
  247. try
  248. {
  249. vm.get_table_entry("state"); // Push "state"
  250. save_squirrel_table(vm.get_vm(), -1, writer);
  251. sq_pop(vm.get_vm(), 1); // Pop "state"
  252. }
  253. catch(const std::exception&)
  254. {
  255. }
  256. sq_pop(vm.get_vm(), 1); // Pop root table
  257. writer.end_list("state");
  258. writer.end_list("supertux-savegame");
  259. }
  260. std::vector<std::string>
  261. Savegame::get_worldmaps()
  262. {
  263. std::vector<std::string> worlds;
  264. SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
  265. SQInteger oldtop = sq_gettop(vm.get_vm());
  266. try
  267. {
  268. sq_pushroottable(vm.get_vm());
  269. vm.get_table_entry("state");
  270. vm.get_or_create_table_entry("worlds");
  271. worlds = vm.get_table_keys();
  272. }
  273. catch(const std::exception& err)
  274. {
  275. log_warning << err.what() << std::endl;
  276. }
  277. sq_settop(vm.get_vm(), oldtop);
  278. // ensure that the loaded worldmap names have their canonical form
  279. std::transform(worlds.begin(), worlds.end(), worlds.begin(), physfsutil::realpath);
  280. return worlds;
  281. }
  282. WorldmapState
  283. Savegame::get_worldmap_state(const std::string& name)
  284. {
  285. WorldmapState result;
  286. SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
  287. SQInteger oldtop = sq_gettop(vm.get_vm());
  288. try
  289. {
  290. sq_pushroottable(vm.get_vm());
  291. vm.get_table_entry("state");
  292. vm.get_or_create_table_entry("worlds");
  293. // if a non-canonical entry is present, replace them with a canonical one
  294. if (name != "/levels/world2/worldmap.stwm") {
  295. std::string old_map_filename = name.substr(1);
  296. if (vm.has_property(old_map_filename.c_str())) {
  297. vm.rename_table_entry(old_map_filename.c_str(), name.c_str());
  298. }
  299. }
  300. vm.get_or_create_table_entry(name);
  301. vm.get_or_create_table_entry("levels");
  302. result.level_states = get_level_states(vm);
  303. }
  304. catch(const std::exception& err)
  305. {
  306. log_warning << err.what() << std::endl;
  307. }
  308. sq_settop(vm.get_vm(), oldtop);
  309. return result;
  310. }
  311. std::vector<std::string>
  312. Savegame::get_levelsets()
  313. {
  314. std::vector<std::string> results;
  315. SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
  316. SQInteger oldtop = sq_gettop(vm.get_vm());
  317. try
  318. {
  319. sq_pushroottable(vm.get_vm());
  320. vm.get_table_entry("state");
  321. vm.get_or_create_table_entry("levelsets");
  322. results = vm.get_table_keys();
  323. }
  324. catch(const std::exception& err)
  325. {
  326. log_warning << err.what() << std::endl;
  327. }
  328. sq_settop(vm.get_vm(), oldtop);
  329. return results;
  330. }
  331. LevelsetState
  332. Savegame::get_levelset_state(const std::string& basedir)
  333. {
  334. LevelsetState result;
  335. SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
  336. SQInteger oldtop = sq_gettop(vm.get_vm());
  337. try
  338. {
  339. sq_pushroottable(vm.get_vm());
  340. vm.get_table_entry("state");
  341. vm.get_or_create_table_entry("levelsets");
  342. vm.get_or_create_table_entry(basedir);
  343. vm.get_or_create_table_entry("levels");
  344. result.level_states = get_level_states(vm);
  345. }
  346. catch(const std::exception& err)
  347. {
  348. log_warning << err.what() << std::endl;
  349. }
  350. sq_settop(vm.get_vm(), oldtop);
  351. return result;
  352. }
  353. void
  354. Savegame::set_levelset_state(const std::string& basedir,
  355. const std::string& level_filename,
  356. bool solved)
  357. {
  358. LevelsetState state = get_levelset_state(basedir);
  359. SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
  360. SQInteger oldtop = sq_gettop(vm.get_vm());
  361. try
  362. {
  363. sq_pushroottable(vm.get_vm());
  364. vm.get_table_entry("state");
  365. vm.get_or_create_table_entry("levelsets");
  366. vm.get_or_create_table_entry(basedir);
  367. vm.get_or_create_table_entry("levels");
  368. vm.get_or_create_table_entry(level_filename);
  369. bool old_solved = false;
  370. vm.get_bool("solved", old_solved);
  371. vm.store_bool("solved", solved || old_solved);
  372. }
  373. catch(const std::exception& err)
  374. {
  375. log_warning << err.what() << std::endl;
  376. }
  377. sq_settop(vm.get_vm(), oldtop);
  378. }
  379. /* EOF */