123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- // SuperTux
- // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
- // 2014 Ingo Ruhnke <grumbel@gmail.com>
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program. If not, see <http://www.gnu.org/licenses/>.
- #include "supertux/savegame.hpp"
- #include <algorithm>
- #include <physfs.h>
- #include "control/input_manager.hpp"
- #include "physfs/physfs_file_system.hpp"
- #include "physfs/util.hpp"
- #include "squirrel/serialize.hpp"
- #include "squirrel/squirrel_util.hpp"
- #include "squirrel/squirrel_virtual_machine.hpp"
- #include "supertux/player_status.hpp"
- #include "supertux/profile_manager.hpp"
- #include "util/file_system.hpp"
- #include "util/log.hpp"
- #include "util/reader_document.hpp"
- #include "util/reader_mapping.hpp"
- #include "util/writer.hpp"
- #include "worldmap/worldmap.hpp"
- namespace {
- std::vector<LevelState> get_level_states(SquirrelVM& vm)
- {
- std::vector<LevelState> results;
- sq_pushnull(vm.get_vm());
- while (SQ_SUCCEEDED(sq_next(vm.get_vm(), -2)))
- {
- //here -1 is the value and -2 is the key
- const char* result;
- if (SQ_FAILED(sq_getstring(vm.get_vm(), -2, &result)))
- {
- std::ostringstream msg;
- msg << "Couldn't get string value";
- throw SquirrelError(vm.get_vm(), msg.str());
- }
- else
- {
- LevelState level_state;
- level_state.filename = result;
- vm.get_bool("solved", level_state.solved);
- vm.get_bool("perfect", level_state.perfect);
- results.push_back(level_state);
- }
- // pops key and val before the next iteration
- sq_pop(vm.get_vm(), 2);
- }
- return results;
- }
- } // namespace
- void
- LevelsetState::store_level_state(const LevelState& in_state)
- {
- auto it = std::find_if(level_states.begin(), level_states.end(),
- [&in_state](const LevelState& state)
- {
- return state.filename == in_state.filename;
- });
- if (it != level_states.end())
- {
- *it = in_state;
- }
- else
- {
- level_states.push_back(in_state);
- }
- }
- LevelState
- LevelsetState::get_level_state(const std::string& filename) const
- {
- auto it = std::find_if(level_states.begin(), level_states.end(),
- [filename](const LevelState& state)
- {
- return state.filename == filename;
- });
- if (it != level_states.end())
- {
- return *it;
- }
- else
- {
- log_info << "creating new level state for " << filename << std::endl;
- LevelState state;
- state.filename = filename;
- return state;
- }
- }
- std::unique_ptr<Savegame>
- Savegame::from_profile(int profile, const std::string& world_name, bool base_data)
- {
- auto savegame = std::make_unique<Savegame>(ProfileManager::current()->get_profile(profile), world_name);
- savegame->load(base_data);
- return savegame;
- }
- std::unique_ptr<Savegame>
- Savegame::from_current_profile(const std::string& world_name, bool base_data)
- {
- auto savegame = std::make_unique<Savegame>(ProfileManager::current()->get_current_profile(), world_name);
- savegame->load(base_data);
- return savegame;
- }
- Savegame::Savegame(Profile& profile, const std::string& world_name) :
- m_profile(profile),
- m_world_name(world_name),
- m_player_status(new PlayerStatus(InputManager::current()->get_num_users()))
- {
- }
- std::string
- Savegame::get_filename() const
- {
- return FileSystem::join(m_profile.get_basedir(), m_world_name + ".stsg");
- }
- bool
- Savegame::is_title_screen() const
- {
- // bit of a hack, TitleScreen uses a dummy savegame without a world name
- return m_world_name.empty();
- }
- void
- Savegame::load(bool base_data)
- {
- if (m_world_name.empty())
- {
- log_debug << "no world name provided for savegame, skipping load" << std::endl;
- return;
- }
- clear_state_table();
- const std::string filename = get_filename();
- if (!PHYSFS_exists(filename.c_str()))
- {
- log_info << filename << " doesn't exist, not loading state" << std::endl;
- }
- else
- {
- if (physfsutil::is_directory(filename))
- {
- log_info << filename << " is a directory, not loading state" << std::endl;
- return;
- }
- log_debug << "loading savegame from " << filename << std::endl;
- try
- {
- SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
- auto doc = ReaderDocument::from_file(filename);
- auto root = doc.get_root();
- if (root.get_name() != "supertux-savegame")
- {
- throw std::runtime_error("file is not a supertux-savegame file");
- }
- else
- {
- auto mapping = root.get_mapping();
- int version = 1;
- mapping.get("version", version);
- if (version != 1)
- {
- throw std::runtime_error("incompatible savegame version");
- }
- else
- {
- /** Load Tux */
- std::optional<ReaderMapping> tux;
- if (!mapping.get("tux", tux))
- {
- throw std::runtime_error("No tux section in savegame");
- }
- {
- m_player_status->read(*tux);
- }
- if (base_data)
- return;
- /** Load "state" table */
- std::optional<ReaderMapping> state;
- if (!mapping.get("state", state))
- {
- throw std::runtime_error("No state section in savegame");
- }
- else
- {
- sq_pushroottable(vm.get_vm());
- vm.get_table_entry("state");
- load_squirrel_table(vm.get_vm(), -1, *state);
- sq_pop(vm.get_vm(), 2);
- }
- }
- }
- }
- catch(const std::exception& e)
- {
- log_fatal << "Couldn't load savegame: " << e.what() << std::endl;
- }
- }
- }
- void
- Savegame::clear_state_table()
- {
- SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
- // delete existing state table, if it exists
- sq_pushroottable(vm.get_vm());
- {
- // create a new empty state table
- vm.create_empty_table("state");
- }
- sq_pop(vm.get_vm(), 1);
- }
- void
- Savegame::save()
- {
- if (m_world_name.empty())
- {
- log_debug << "no world name set for savegame, skipping save" << std::endl;
- return;
- }
- const std::string filename = get_filename();
- log_debug << "saving savegame to " << filename << std::endl;
- m_profile.save(); // Make sure profile directory exists, save profile info
- SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
- Writer writer(filename);
- writer.start_list("supertux-savegame");
- writer.write("version", 1);
- using namespace worldmap;
- if (WorldMap::current() != nullptr)
- {
- std::ostringstream title;
- title << WorldMap::current()->get_title();
- title << " (" << WorldMap::current()->solved_level_count()
- << "/" << WorldMap::current()->level_count() << ")";
- writer.write("title", title.str());
- }
- writer.start_list("tux");
- m_player_status->write(writer);
- writer.end_list("tux");
- writer.start_list("state");
- sq_pushroottable(vm.get_vm());
- try
- {
- vm.get_table_entry("state"); // Push "state"
- save_squirrel_table(vm.get_vm(), -1, writer);
- sq_pop(vm.get_vm(), 1); // Pop "state"
- }
- catch(const std::exception&)
- {
- }
- sq_pop(vm.get_vm(), 1); // Pop root table
- writer.end_list("state");
- writer.end_list("supertux-savegame");
- }
- std::vector<std::string>
- Savegame::get_worldmaps()
- {
- std::vector<std::string> worlds;
- SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
- SQInteger oldtop = sq_gettop(vm.get_vm());
- try
- {
- sq_pushroottable(vm.get_vm());
- vm.get_table_entry("state");
- vm.get_or_create_table_entry("worlds");
- worlds = vm.get_table_keys();
- }
- catch(const std::exception& err)
- {
- log_warning << err.what() << std::endl;
- }
- sq_settop(vm.get_vm(), oldtop);
- // ensure that the loaded worldmap names have their canonical form
- std::transform(worlds.begin(), worlds.end(), worlds.begin(), physfsutil::realpath);
- return worlds;
- }
- WorldmapState
- Savegame::get_worldmap_state(const std::string& name)
- {
- WorldmapState result;
- SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
- SQInteger oldtop = sq_gettop(vm.get_vm());
- try
- {
- sq_pushroottable(vm.get_vm());
- vm.get_table_entry("state");
- vm.get_or_create_table_entry("worlds");
- // if a non-canonical entry is present, replace them with a canonical one
- if (name != "/levels/world2/worldmap.stwm") {
- std::string old_map_filename = name.substr(1);
- if (vm.has_property(old_map_filename.c_str())) {
- vm.rename_table_entry(old_map_filename.c_str(), name.c_str());
- }
- }
- vm.get_or_create_table_entry(name);
- vm.get_or_create_table_entry("levels");
- result.level_states = get_level_states(vm);
- }
- catch(const std::exception& err)
- {
- log_warning << err.what() << std::endl;
- }
- sq_settop(vm.get_vm(), oldtop);
- return result;
- }
- std::vector<std::string>
- Savegame::get_levelsets()
- {
- std::vector<std::string> results;
- SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
- SQInteger oldtop = sq_gettop(vm.get_vm());
- try
- {
- sq_pushroottable(vm.get_vm());
- vm.get_table_entry("state");
- vm.get_or_create_table_entry("levelsets");
- results = vm.get_table_keys();
- }
- catch(const std::exception& err)
- {
- log_warning << err.what() << std::endl;
- }
- sq_settop(vm.get_vm(), oldtop);
- return results;
- }
- LevelsetState
- Savegame::get_levelset_state(const std::string& basedir)
- {
- LevelsetState result;
- SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
- SQInteger oldtop = sq_gettop(vm.get_vm());
- try
- {
- sq_pushroottable(vm.get_vm());
- vm.get_table_entry("state");
- vm.get_or_create_table_entry("levelsets");
- vm.get_or_create_table_entry(basedir);
- vm.get_or_create_table_entry("levels");
- result.level_states = get_level_states(vm);
- }
- catch(const std::exception& err)
- {
- log_warning << err.what() << std::endl;
- }
- sq_settop(vm.get_vm(), oldtop);
- return result;
- }
- void
- Savegame::set_levelset_state(const std::string& basedir,
- const std::string& level_filename,
- bool solved)
- {
- LevelsetState state = get_levelset_state(basedir);
- SquirrelVM& vm = SquirrelVirtualMachine::current()->get_vm();
- SQInteger oldtop = sq_gettop(vm.get_vm());
- try
- {
- sq_pushroottable(vm.get_vm());
- vm.get_table_entry("state");
- vm.get_or_create_table_entry("levelsets");
- vm.get_or_create_table_entry(basedir);
- vm.get_or_create_table_entry("levels");
- vm.get_or_create_table_entry(level_filename);
- bool old_solved = false;
- vm.get_bool("solved", old_solved);
- vm.store_bool("solved", solved || old_solved);
- }
- catch(const std::exception& err)
- {
- log_warning << err.what() << std::endl;
- }
- sq_settop(vm.get_vm(), oldtop);
- }
- /* EOF */
|