From 374bd34e02365aaa95eda36748f674e291aed225 Mon Sep 17 00:00:00 2001 From: David Li Date: Sat, 7 Dec 2024 13:50:45 -0800 Subject: [PATCH] refactor: Clean up world saving/loading codepath (Phase 1 of v2 save format) (#5774) * fix: Add 'cataclysm-bn-tiles' and 'json_formatter' to .gitignore All the other binaries are listed except for the ones that cmake actually generates for tiles builds. Signed-off-by: David Li * refactor: Begin consolidating world loading Clean up and refactor worldfactory and world. * The old 'WORLD' struct is now renamed `WORLDINFO` to better reflect its actual purpose. * Get rid of the WORLDPTR = WORLD* convention. This isn't the Windows API, and nothing else does this. * Move file saving logic into a new class called `world`, meant to encapsulate the idea of a loaded world. I will begin consolidating more world loading logic into this class. Signed-off-by: David Li * refactor: Simplify file I/O utility methods Replace the 2 write and 6 read methods with 1 write and 2 read. This simplification is needed because future save loaders will need to provide their own implementations of these methods. * The `_optional` variants can be replaced with default arguments. * The `JsonDeserializer` variants are never used. * The `_json` variants do save some boilerplate (`path`) so I'll keep that in for now. Signed-off-by: David Li * refactor: Consolidate more world loading methods into `world` Player file IO is still janky, particularly `mm` files - WIP. However, the game does work and the on-disk format should be unchanged. Signed-off-by: David Li * refactor: Consolidate player-specific save IO Clean up how the game determines where to place player- specific files, in preparation for the V2 save format. NOTE: This removes backwards compatibility for the pre-2020 map memory ('mm') format. This logic only triggered when loading a map that has not seen a save event since 2020 - I think it's safe to remove this now. Signed-off-by: David Li * style(autofix.ci): automated formatting * fix: Forgot to migrate some code in non-tiles build Signed-off-by: David Li * fix: Bug in initializing world dir structure Signed-off-by: David Li * fix: Subtle change in write_to_file error handling The consolidation of write_to_file methods introduced a subtle API change where passing nullptr to the third argument no longer caused write_to_file to eat exceptions. This largely only affected tests and a small number of edge cases. Alter the API so that the right way to eat exceptions is via passing in the empty string as the message. Signed-off-by: David Li * refactor: Remember save format in the WORLDINFO struct Plus misc bug fixes Signed-off-by: David Li * refactor: Address code style and const in save refactor Signed-off-by: David Li --------- Signed-off-by: David Li Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .gitignore | 2 + src/artifact.cpp | 11 +- src/artifact.h | 5 +- src/auto_note.cpp | 14 +- src/auto_note.h | 4 - src/auto_pickup.cpp | 37 ++-- src/cata_utility.cpp | 75 +++---- src/catalua.cpp | 20 +- src/catalua.h | 5 +- src/clzones.cpp | 11 +- src/color.cpp | 4 +- src/debug.cpp | 2 +- src/diary.cpp | 22 +- src/editmap.cpp | 4 +- src/filesystem.cpp | 4 +- src/fstream_utils.h | 26 ++- src/game.cpp | 95 ++++----- src/game.h | 16 +- src/handle_action.cpp | 2 +- src/help.cpp | 4 +- src/init.cpp | 11 +- src/init.h | 3 +- src/input.cpp | 4 +- src/language.cpp | 2 +- src/main_menu.cpp | 16 +- src/map_memory.cpp | 52 +---- src/mapbuffer.cpp | 47 +---- src/mapbuffer.h | 3 +- src/mod_manager.cpp | 20 +- src/mod_manager.h | 10 +- src/newcharacter.cpp | 7 +- src/options.cpp | 10 +- src/overmap.cpp | 17 +- src/overmapbuffer.cpp | 12 +- src/overmapbuffer.h | 3 - src/panels.cpp | 4 +- src/safemode_ui.cpp | 47 ++--- src/sdltiles.cpp | 2 +- src/world.cpp | 426 ++++++++++++++++++++++++++++++++++++++ src/world.h | 170 +++++++++++++++ src/worldfactory.cpp | 277 ++++--------------------- src/worldfactory.h | 88 ++------ tests/filesystem_test.cpp | 3 +- tests/test_main.cpp | 6 +- 44 files changed, 921 insertions(+), 682 deletions(-) create mode 100644 src/world.cpp create mode 100644 src/world.h diff --git a/.gitignore b/.gitignore index ce592c9df7ae..25e3ffc1e7b9 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,8 @@ cata_test cata_test-tiles cataclysm cataclysm-tiles +cataclysm-bn-tiles +json_formatter *.exe.manifest cataclysm-vcpkg cataclysmdda-* diff --git a/src/artifact.cpp b/src/artifact.cpp index 3b8bb53f928c..3525a83159fb 100644 --- a/src/artifact.cpp +++ b/src/artifact.cpp @@ -27,6 +27,7 @@ #include "type_id.h" #include "units.h" #include "value_ptr.h" +#include "world.h" template inline units::quantity rng( const units::quantity &min, @@ -1086,9 +1087,9 @@ std::string artifact_name( const std::string &type ) /* Json Loading and saving */ -void load_artifacts( const std::string &path ) +void load_artifacts( const world *world, const std::string &path ) { - read_from_file_optional_json( path, []( JsonIn & artifact_json ) { + world->read_from_file_json( path, []( JsonIn & artifact_json ) { artifact_json.start_array(); while( !artifact_json.end_array() ) { JsonObject jo = artifact_json.get_object(); @@ -1101,7 +1102,7 @@ void load_artifacts( const std::string &path ) jo.throw_error( "unrecognized artifact type.", "type" ); } } - } ); + }, true ); } void it_artifact_tool::deserialize( const JsonObject &jo ) @@ -1266,9 +1267,9 @@ void it_artifact_armor::deserialize( const JsonObject &jo ) } } -bool save_artifacts( const std::string &path ) +bool save_artifacts( const world *world, const std::string &path ) { - return write_to_file( path, [&]( std::ostream & fout ) { + return world->write_to_file( path, [&]( std::ostream & fout ) { JsonOut json( fout, true ); json.start_array(); // We only want runtime types, otherwise static artifacts are loaded twice (on init and then on game load) diff --git a/src/artifact.h b/src/artifact.h index 1e83231e5e3d..e02a773e67c4 100644 --- a/src/artifact.h +++ b/src/artifact.h @@ -7,6 +7,7 @@ #include "enums.h" #include "itype.h" #include "relic.h" +#include "world.h" class JsonObject; class JsonOut; @@ -126,9 +127,9 @@ itype_id new_artifact(); itype_id new_natural_artifact( artifact_natural_property prop ); // note: needs to be called by main() before MAPBUFFER.load -void load_artifacts( const std::string &path ); +void load_artifacts( const world *world, const std::string &path ); // save artifact definitions to json, path must be the same as for loading. -bool save_artifacts( const std::string &path ); +bool save_artifacts( const world *world, const std::string &path ); bool check_art_charge_req( item &it ); diff --git a/src/auto_note.cpp b/src/auto_note.cpp index 8f7cf470608d..d5bc2ed83781 100644 --- a/src/auto_note.cpp +++ b/src/auto_note.cpp @@ -18,14 +18,10 @@ #include "point.h" #include "translations.h" #include "ui_manager.h" +#include "world.h" namespace auto_notes { -std::string auto_note_settings::build_save_path() const -{ - return g->get_player_base_save_path() + ".ano.json"; -} - void auto_note_settings::clear() { autoNoteEnabled.clear(); @@ -33,11 +29,12 @@ void auto_note_settings::clear() bool auto_note_settings::save() { - if( !file_exist( g->get_player_base_save_path() + ".sav" ) ) { + world *world = g->get_active_world(); + if( !world->player_file_exist( ".sav" ) ) { return true; } - return write_to_file( build_save_path(), [&]( std::ostream & fstr ) { + return world->write_to_player_file( ".ano.json", [&]( std::ostream & fstr ) { JsonOut jout{ fstr, true }; jout.start_object(); @@ -92,7 +89,8 @@ void auto_note_settings::load() } }; - if( !read_from_file_optional_json( build_save_path(), parseJson ) ) { + if( !g->get_active_world()->read_from_player_file_json( ".ano.json", parseJson, + true ) ) { default_initialize(); save(); } diff --git a/src/auto_note.h b/src/auto_note.h index 94605598fffa..61a559f80089 100644 --- a/src/auto_note.h +++ b/src/auto_note.h @@ -72,10 +72,6 @@ class auto_note_settings /// registered map extras in order to determine their enable status. void default_initialize(); - private: - /// Build string containing path to the auto notes save file for the active player. - std::string build_save_path() const; - private: /// This set contains the ID strings of all map extras that have auto note enabled. std::unordered_set> autoNoteEnabled; diff --git a/src/auto_pickup.cpp b/src/auto_pickup.cpp index ff9a0a305ffe..d04da0894049 100644 --- a/src/auto_pickup.cpp +++ b/src/auto_pickup.cpp @@ -29,6 +29,7 @@ #include "translations.h" #include "type_id.h" #include "ui_manager.h" +#include "world.h" using namespace auto_pickup; @@ -714,22 +715,22 @@ bool player_settings::save_global() bool player_settings::save( const bool bCharacter ) { - auto savefile = PATH_INFO::autopickup(); - if( bCharacter ) { - savefile = g->get_player_base_save_path() + ".apu.json"; - - const std::string player_save = g->get_player_base_save_path() + ".sav"; //Character not saved yet. - if( !file_exist( player_save ) ) { + if( !g->get_active_world()->player_file_exist( ".sav" ) ) { return true; } - } - return write_to_file( savefile, [&]( std::ostream & fout ) { - JsonOut jout( fout, true ); - ( bCharacter ? character_rules : global_rules ).serialize( jout ); - }, _( "autopickup configuration" ) ); + return g->get_active_world()->write_to_player_file( ".apu.json", [&]( std::ostream & fout ) { + JsonOut jout( fout, true ); + ( bCharacter ? character_rules : global_rules ).serialize( jout ); + }, _( "autopickup configuration" ) ); + } else { + return write_to_file( PATH_INFO::autopickup(), [&]( std::ostream & fout ) { + JsonOut jout( fout, true ); + ( bCharacter ? character_rules : global_rules ).serialize( jout ); + }, _( "autopickup configuration" ) ); + } } void player_settings::load_character() @@ -744,15 +745,15 @@ void player_settings::load_global() void player_settings::load( const bool bCharacter ) { - std::string sFile = PATH_INFO::autopickup(); if( bCharacter ) { - sFile = g->get_player_base_save_path() + ".apu.json"; + g->get_active_world()->read_from_player_file_json( ".apu.json", [&]( JsonIn & jsin ) { + ( bCharacter ? character_rules : global_rules ).deserialize( jsin ); + }, true ); + } else { + read_from_file_json( PATH_INFO::autopickup(), [&]( JsonIn & jsin ) { + ( bCharacter ? character_rules : global_rules ).deserialize( jsin ); + }, true ); } - - read_from_file_optional_json( sFile, [&]( JsonIn & jsin ) { - ( bCharacter ? character_rules : global_rules ).deserialize( jsin ); - } ); - invalidate(); } diff --git a/src/cata_utility.cpp b/src/cata_utility.cpp index 28d0f765de4c..dae54e950d47 100644 --- a/src/cata_utility.cpp +++ b/src/cata_utility.cpp @@ -457,24 +457,31 @@ std::istream *cata_ifstream::operator->() return &*_stream; } -void write_to_file( const std::string &path, const std::function &writer ) -{ - // Any of the below may throw. ofstream_wrapper will clean up the temporary path on its own. - ofstream_wrapper fout( path, cata_ios_mode::binary ); - writer( fout.stream() ); - fout.close(); -} - -bool write_to_file( const std::string &path, const std::function &writer, - const char *const fail_message ) +/** + * If fail_message is provided, this method will eat any exceptions and display a popup with the + * exception detail and the message. If fail_message is not provided, the exception will be + * propagated. + * + * To eat any exceptions and not display a popup, pass the empty string as fail_message. + * + * @param path The path to write to. + * @param writer The function that writes to the file. + * @param fail_message The message to display if the write fails. + * @return True if the write was successful, false otherwise. + */ +bool write_to_file( const std::string &path, file_write_fn &writer, const char *const fail_message ) { try { - write_to_file( path, writer ); + // Any of the below may throw. ofstream_wrapper will clean up the temporary path on its own. + ofstream_wrapper fout( path, cata_ios_mode::binary ); + writer( fout.stream() ); + fout.close(); return true; - } catch( const std::exception &err ) { - if( fail_message ) { + if( fail_message && fail_message[0] != '\0' ) { popup( _( "Failed to write %1$s to \"%2$s\": %3$s" ), fail_message, path.c_str(), err.what() ); + } else if( fail_message == nullptr ) { + std::throw_with_nested( std::runtime_error( "file write failed: " + path ) ); } return false; } @@ -523,8 +530,12 @@ std::istream &safe_getline( std::istream &ins, std::string &str ) } } -bool read_from_file( const std::string &path, const std::function &reader ) +bool read_from_file( const std::string &path, file_read_fn reader, bool optional ) { + if( optional && !file_exist( path ) ) { + return false; + } + try { cata_ifstream fin = std::move( cata_ifstream().mode( cata_ios_mode::binary ).open( path ) ); if( !fin.is_open() ) { @@ -542,44 +553,12 @@ bool read_from_file( const std::string &path, const std::function &reader ) +bool read_from_file_json( const std::string &path, file_read_json_fn reader, bool optional ) { return read_from_file( path, [&]( std::istream & fin ) { JsonIn jsin( fin, path ); reader( jsin ); - } ); -} - -bool read_from_file( const std::string &path, JsonDeserializer &reader ) -{ - return read_from_file_json( path, [&reader]( JsonIn & jsin ) { - reader.deserialize( jsin ); - } ); -} - -bool read_from_file_optional( const std::string &path, - const std::function &reader ) -{ - // Note: slight race condition here, but we'll ignore it. Worst case: the file - // exists and got removed before reading it -> reading fails with a message - // Or file does not exists, than everything works fine because it's optional anyway. - return file_exist( path ) && read_from_file( path, reader ); -} - -bool read_from_file_optional_json( const std::string &path, - const std::function &reader ) -{ - return read_from_file_optional( path, [&]( std::istream & fin ) { - JsonIn jsin( fin, path ); - reader( jsin ); - } ); -} - -bool read_from_file_optional( const std::string &path, JsonDeserializer &reader ) -{ - return read_from_file_optional_json( path, [&reader]( JsonIn & jsin ) { - reader.deserialize( jsin ); - } ); + }, optional ); } void ofstream_wrapper::open( cata_ios_mode mode ) diff --git a/src/catalua.cpp b/src/catalua.cpp index 3a13eda0fe57..60f470d43f0e 100644 --- a/src/catalua.cpp +++ b/src/catalua.cpp @@ -61,12 +61,12 @@ void debug_write_lua_backtrace( std::ostream &/*out*/ ) // Nothing to do here } -bool save_world_lua_state( const std::string & ) +bool save_world_lua_state( const world *world, const std::string & ) { return true; } -bool load_world_lua_state( const std::string & ) +bool load_world_lua_state( const world *world, const std::string & ) { return true; } @@ -166,7 +166,7 @@ void show_lua_console() void reload_lua_code() { cata::lua_state &state = *DynamicDataLoader::get_instance().lua; - const auto &packs = world_generator->active_world->active_mod_order; + const auto &packs = world_generator->active_world->info->active_mod_order; try { init::load_main_lua_scripts( state, packs ); } catch( std::runtime_error &e ) { @@ -194,14 +194,14 @@ static sol::table get_mod_storage_table( lua_state &state ) return state.lua.globals()["game"]["cata_internal"]["mod_storage"]; } -bool save_world_lua_state( const std::string &path ) +bool save_world_lua_state( const world *world, const std::string &path ) { lua_state &state = *DynamicDataLoader::get_instance().lua; - const mod_management::t_mod_list &mods = world_generator->active_world->active_mod_order; + const mod_management::t_mod_list &mods = world_generator->active_world->info->active_mod_order; sol::table t = get_mod_storage_table( state ); run_on_game_save_hooks( state ); - bool ret = write_to_file( path, [&]( std::ostream & stream ) { + bool ret = world->write_to_file( path, [&]( std::ostream & stream ) { JsonOut jsout( stream ); jsout.start_object(); for( const mod_id &mod : mods ) { @@ -217,13 +217,13 @@ bool save_world_lua_state( const std::string &path ) return ret; } -bool load_world_lua_state( const std::string &path ) +bool load_world_lua_state( const world *world, const std::string &path ) { lua_state &state = *DynamicDataLoader::get_instance().lua; - const mod_management::t_mod_list &mods = world_generator->active_world->active_mod_order; + const mod_management::t_mod_list &mods = world_generator->active_world->info->active_mod_order; sol::table t = get_mod_storage_table( state ); - bool ret = read_from_file_optional( path, [&]( std::istream & stream ) { + bool ret = world->read_from_file( path, [&]( std::istream & stream ) { JsonIn jsin( stream ); JsonObject jsobj = jsin.get_object(); @@ -239,7 +239,7 @@ bool load_world_lua_state( const std::string &path ) JsonObject mod_obj = jsobj.get_object( mod.str() ); deserialize_lua_table( t[mod.str()], mod_obj ); } - } ); + }, true ); run_on_game_load_hooks( state ); return ret; diff --git a/src/catalua.h b/src/catalua.h index 1a7642a82ae4..531d7ff3118f 100644 --- a/src/catalua.h +++ b/src/catalua.h @@ -10,6 +10,7 @@ class Item_factory; class map; class time_point; struct tripoint; +class world; namespace cata { @@ -27,8 +28,8 @@ void show_lua_console(); void reload_lua_code(); void debug_write_lua_backtrace( std::ostream &out ); -bool save_world_lua_state( const std::string &path ); -bool load_world_lua_state( const std::string &path ); +bool save_world_lua_state( const world *world, const std::string &path ); +bool load_world_lua_state( const world *world, const std::string &path ); std::unique_ptr make_wrapped_state(); diff --git a/src/clzones.cpp b/src/clzones.cpp index cabb68daf6be..d573e0da5d09 100644 --- a/src/clzones.cpp +++ b/src/clzones.cpp @@ -40,6 +40,7 @@ #include "vehicle.h" #include "vehicle_part.h" #include "vpart_position.h" +#include "world.h" static const item_category_id itcat_food( "food" ); @@ -1182,12 +1183,10 @@ void zone_data::deserialize( JsonIn &jsin ) bool zone_manager::save_zones() { - std::string savefile = g->get_player_base_save_path() + ".zones.json"; - added_vzones.clear(); changed_vzones.clear(); removed_vzones.clear(); - return write_to_file( savefile, [&]( std::ostream & fout ) { + return g->get_active_world()->write_to_player_file( ".zones.json", [&]( std::ostream & fout ) { JsonOut jsout( fout ); serialize( jsout ); }, _( "zones date" ) ); @@ -1195,12 +1194,10 @@ bool zone_manager::save_zones() void zone_manager::load_zones() { - std::string savefile = g->get_player_base_save_path() + ".zones.json"; - - read_from_file_optional( savefile, [&]( std::istream & fin ) { + g->get_active_world()->read_from_player_file( ".zones.json", [&]( std::istream & fin ) { JsonIn jsin( fin ); deserialize( jsin ); - } ); + }, true ); revert_vzones(); added_vzones.clear(); changed_vzones.clear(); diff --git a/src/color.cpp b/src/color.cpp index a51265b826ca..49505e473891 100644 --- a/src/color.cpp +++ b/src/color.cpp @@ -1040,9 +1040,9 @@ void color_manager::load_custom( const std::string &sPath ) { const auto file = sPath.empty() ? PATH_INFO::custom_colors() : sPath; - read_from_file_optional_json( file, [this]( JsonIn & jsonin ) { + read_from_file_json( file, [this]( JsonIn & jsonin ) { deserialize( jsonin ); - } ); + }, true ); finalize(); // Need to finalize regardless of success } diff --git a/src/debug.cpp b/src/debug.cpp index 7d65274b9833..dffc1478092d 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -1774,7 +1774,7 @@ std::string game_info::mods_loaded() return "No active world"; } - const std::vector &mod_ids = world_generator->active_world->active_mod_order; + const std::vector &mod_ids = world_generator->active_world->info->active_mod_order; if( mod_ids.empty() ) { return "No loaded mods"; } diff --git a/src/diary.cpp b/src/diary.cpp index 50364ef6fae7..eeef268c7424 100644 --- a/src/diary.cpp +++ b/src/diary.cpp @@ -28,6 +28,7 @@ #include "type_id.h" #include "overmap_ui.h" #include "units.h" +#include "world.h" diary_page::diary_page() = default; @@ -747,7 +748,9 @@ void diary::delete_page() void diary::export_to_md( bool last_export ) { std::ofstream myfile; - std::string path = last_export ? PATH_INFO::memorialdir() : g->get_world_base_save_path(); + std::string path = last_export + ? PATH_INFO::memorialdir() + : ( g->get_active_world() ? g->get_active_world()->info->folder_path() : PATH_INFO::savedir() ); path += "/" + owner + "s_diary.md"; myfile.open( path ); @@ -775,9 +778,13 @@ void diary::export_to_md( bool last_export ) bool diary::store() { + if( !g->get_active_world() ) { + return false; + } + std::string name = base64_encode( get_avatar().get_save_id() + "_diary" ); - std::string path = g->get_world_base_save_path() + "/" + name + ".json"; - const bool is_writen = write_to_file( path, [&]( std::ostream & fout ) { + const bool is_writen = g->get_active_world()->write_to_file( name + ".json", [&]( + std::ostream & fout ) { serialize( fout ); }, _( "diary data" ) ); return is_writen; @@ -826,10 +833,13 @@ void diary::serialize( JsonOut &jsout ) void diary::load() { + if( !g->get_active_world() ) { + return; + } + std::string name = base64_encode( get_avatar().get_save_id() + "_diary" ); - std::string path = g->get_world_base_save_path() + "/" + name + ".json"; - if( file_exist( path ) ) { - read_from_file( path, [&]( std::istream & fin ) { + if( g->get_active_world()->file_exist( name + ".json" ) ) { + g->get_active_world()->read_from_file( name + ".json", [&]( std::istream & fin ) { deserialize( fin ); } ); } diff --git a/src/editmap.cpp b/src/editmap.cpp index 80f4430a8372..32a3810a9b01 100644 --- a/src/editmap.cpp +++ b/src/editmap.cpp @@ -150,10 +150,10 @@ void edit_json( SAVEOBJ &it ) } else if( tmret == 2 ) { write_to_file( "save/jtest-1j.txt", [&]( std::ostream & fout ) { fout << osave1; - }, nullptr ); + }, "" ); write_to_file( "save/jtest-2j.txt", [&]( std::ostream & fout ) { fout << serialize( it ); - }, nullptr ); + }, "" ); } tm.addentry( 0, true, 'r', pgettext( "item manipulation debug menu entry", "rehash" ) ); tm.addentry( 1, true, 'e', pgettext( "item manipulation debug menu entry", "edit" ) ); diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 60f2f6acddae..41fdf9abb4da 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -468,7 +468,7 @@ bool copy_file( const std::string &source_path, const std::string &dest_path ) } bool res = write_to_file( dest_path, [&]( std::ostream & dest_stream ) { dest_stream << source_stream->rdbuf(); - }, nullptr ); + }, "" ); return res && !source_stream.fail(); } @@ -513,7 +513,7 @@ bool can_write_to_dir( const std::string &dir_path ) s << CBN << '\n'; }; - if( !write_to_file( dummy_file, writer, nullptr ) ) { + if( !write_to_file( dummy_file, writer, "" ) ) { return false; } diff --git a/src/fstream_utils.h b/src/fstream_utils.h index 2f0800368fd6..5b3878eb1f34 100644 --- a/src/fstream_utils.h +++ b/src/fstream_utils.h @@ -100,6 +100,11 @@ struct cata_ifstream { #endif }; + +using file_read_fn = const std::function &; +using file_read_json_fn = const std::function &; +using file_write_fn = const std::function &; + /** * Open a file for writing, calls the writer on that stream. * @@ -111,11 +116,8 @@ struct cata_ifstream { * @throw The void function throws when writing failes or when the @p writer throws. * The other function catches all exceptions and returns false. */ -///@{ -bool write_to_file( const std::string &path, const std::function &writer, - const char *fail_message ); -void write_to_file( const std::string &path, const std::function &writer ); -///@} +bool write_to_file( const std::string &path, file_write_fn &writer, + const char *fail_message = nullptr ); class JsonDeserializer; @@ -139,15 +141,11 @@ class JsonDeserializer; * @return `true` is the file was read without any errors, `false` upon any error. */ /**@{*/ -bool read_from_file( const std::string &path, const std::function &reader ); -bool read_from_file_json( const std::string &path, const std::function &reader ); -bool read_from_file( const std::string &path, JsonDeserializer &reader ); - -bool read_from_file_optional( const std::string &path, - const std::function &reader ); -bool read_from_file_optional_json( const std::string &path, - const std::function &reader ); -bool read_from_file_optional( const std::string &path, JsonDeserializer &reader ); +bool read_from_file( const std::string &path, file_read_fn reader, + bool optional = false ); +bool read_from_file_json( const std::string &path, file_read_json_fn reader, + bool optional = false ); + /**@}*/ /** * Wrapper around std::ofstream that handles error checking and throws on errors. diff --git a/src/game.cpp b/src/game.cpp index 460c6f458566..75e6e429dc31 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -432,7 +432,7 @@ void game::reload_tileset( [[maybe_unused]] const std::function dummy; tilecontext->load_tileset( get_option( "TILES" ), - world_generator->active_world ? world_generator->active_world->active_mod_order : dummy, + world_generator->active_world ? world_generator->active_world->info->active_mod_order : dummy, /*precheck=*/false, /*force=*/true, /*pump_events=*/true @@ -474,7 +474,7 @@ void game::setup() { loading_ui ui( true ); - init::load_world_modfiles( ui, get_world_base_save_path() + "/" + SAVE_ARTIFACTS ); + init::load_world_modfiles( ui, get_active_world(), SAVE_ARTIFACTS ); m = map(); @@ -1183,10 +1183,10 @@ bool game::cleanup_at_end() } if( queryDelete || get_option( "WORLD_END" ) == "delete" ) { - world_generator->delete_world( world_generator->active_world->world_name, true ); + world_generator->delete_world( world_generator->active_world->info->world_name, true ); } else if( queryReset || get_option( "WORLD_END" ) == "reset" ) { - world_generator->delete_world( world_generator->active_world->world_name, false ); + world_generator->delete_world( world_generator->active_world->info->world_name, false ); } } else if( get_option( "WORLD_END" ) != "keep" ) { std::string tmpmessage; @@ -2467,7 +2467,7 @@ void game::win_screen() void game::move_save_to_graveyard( const std::string &dirname ) { - const std::string save_dir = get_world_base_save_path(); + const std::string save_dir = get_active_world()->info->folder_path(); const std::string graveyard_dir = PATH_INFO::graveyarddir() + "/"; const std::string graveyard_save_dir = graveyard_dir + dirname + "/"; const std::string &prefix = base64_encode( u.get_save_id() ) + "."; @@ -2506,14 +2506,14 @@ void game::move_save_to_graveyard( const std::string &dirname ) void game::load_master() { using namespace std::placeholders; - const auto datafile = get_world_base_save_path() + "/" + SAVE_MASTER; - read_from_file_optional( datafile, std::bind( &game::unserialize_master, this, _1 ) ); + get_active_world()->read_from_file( SAVE_MASTER, std::bind( &game::unserialize_master, this, _1 ), + true ); } bool game::load( const std::string &world ) { world_generator->init(); - const WORLDPTR wptr = world_generator->get_world( world ); + WORLDINFO *wptr = world_generator->get_world( world ); if( !wptr ) { return false; } @@ -2544,16 +2544,14 @@ bool game::load( const save_t &name ) using namespace std::placeholders; - const std::string worldpath = get_world_base_save_path() + "/"; - const std::string playerpath = worldpath + name.base_path(); - // Now load up the master game data; factions (and more?) load_master(); u = avatar(); u.recalc_hp(); u.set_save_id( name.decoded_name() ); u.name = name.decoded_name(); - if( !read_from_file( playerpath + SAVE_EXTENSION, std::bind( &game::unserialize, this, _1 ) ) ) { + if( !get_active_world()->read_from_file( name.base_path() + SAVE_EXTENSION, + std::bind( &game::unserialize, this, _1 ) ) ) { return false; } @@ -2562,12 +2560,12 @@ bool game::load( const save_t &name ) get_weather().nextweather = calendar::turn; - read_from_file_optional( worldpath + name.base_path() + SAVE_EXTENSION_LOG, - std::bind( &memorial_logger::load, &memorial(), _1 ) ); + get_active_world()->read_from_file( name.base_path() + SAVE_EXTENSION_LOG, + std::bind( &memorial_logger::load, &memorial(), _1 ), true ); #if defined(__ANDROID__) - read_from_file_optional( worldpath + name.base_path() + SAVE_EXTENSION_SHORTCUTS, - std::bind( &game::load_shortcuts, this, _1 ) ); + get_active_world()->read_from_file( name.base_path() + SAVE_EXTENSION_SHORTCUTS, + std::bind( &game::load_shortcuts, this, _1 ), true ); #endif // Now that the player's worn items are updated, their sight limits need to be @@ -2586,10 +2584,10 @@ bool game::load( const save_t &name ) get_auto_notes_settings().load(); // Load character auto notes settings get_safemode().load_character(); // Load character safemode rules zone_manager::get_manager().load_zones(); // Load character world zones - read_from_file_optional( get_world_base_save_path() + "/uistate.json", []( std::istream & stream ) { + get_active_world()->read_from_file( "uistate.json", []( std::istream & stream ) { JsonIn jsin( stream ); uistate.deserialize( jsin ); - } ); + }, true ); reload_npcs(); validate_npc_followers(); validate_mounted_npcs(); @@ -2619,7 +2617,7 @@ bool game::load( const save_t &name ) u.reset(); - cata::load_world_lua_state( get_world_base_save_path() + "/lua_state.json" ); + cata::load_world_lua_state( get_active_world(), "lua_state.json" ); cata::run_on_game_load_hooks( *DynamicDataLoader::get_instance().lua ); @@ -2656,16 +2654,14 @@ void game::reset_npc_dispositions() //Saves all factions and missions and npcs. bool game::save_factions_missions_npcs() { - std::string masterfile = get_world_base_save_path() + "/" + SAVE_MASTER; - return write_to_file( masterfile, [&]( std::ostream & fout ) { + return get_active_world()->write_to_file( SAVE_MASTER, [&]( std::ostream & fout ) { serialize_master( fout ); }, _( "factions data" ) ); } bool game::save_artifacts() { - std::string artfilename = get_world_base_save_path() + "/" + SAVE_ARTIFACTS; - return ::save_artifacts( artfilename ); + return ::save_artifacts( get_active_world(), SAVE_ARTIFACTS ); } bool game::save_maps() @@ -2683,18 +2679,17 @@ bool game::save_maps() bool game::save_player_data() { - const std::string playerfile = get_player_base_save_path(); - - const bool saved_data = write_to_file( playerfile + SAVE_EXTENSION, [&]( std::ostream & fout ) { + world *world = get_active_world(); + const bool saved_data = world->write_to_player_file( SAVE_EXTENSION, [&]( std::ostream & fout ) { serialize( fout ); }, _( "player data" ) ); const bool saved_map_memory = u.save_map_memory(); - const bool saved_log = write_to_file( playerfile + SAVE_EXTENSION_LOG, [&]( + const bool saved_log = world->write_to_player_file( SAVE_EXTENSION_LOG, [&]( std::ostream & fout ) { fout << memorial().dump(); }, _( "player memorial" ) ); #if defined(__ANDROID__) - const bool saved_shortcuts = write_to_file( playerfile + SAVE_EXTENSION_SHORTCUTS, [&]( + const bool saved_shortcuts = world->write_to_player_file( SAVE_EXTENSION_SHORTCUTS, [&]( std::ostream & fout ) { save_shortcuts( fout ); }, _( "quick shortcuts" ) ); @@ -2732,9 +2727,9 @@ spell_events &game::spell_events_subscriber() return *spell_events_ptr; } -static bool save_uistate_data( const game &g ) +bool game::save_uistate_data() const { - return write_to_file( g.get_world_base_save_path() + "/uistate.json", [&]( std::ostream & fout ) { + return get_active_world()->write_to_file( "uistate.json", [&]( std::ostream & fout ) { JsonOut jsout( fout ); uistate.serialize( jsout ); }, _( "uistate data" ) ); @@ -2742,6 +2737,13 @@ static bool save_uistate_data( const game &g ) bool game::save( bool quitting ) { + world *world = get_active_world(); + if( !world ) { + return false; + } + + world->start_save_tx(); + cata::run_on_game_save_hooks( *DynamicDataLoader::get_instance().lua ); try { reset_save_ids( time( nullptr ), quitting ); @@ -2752,15 +2754,18 @@ bool game::save( bool quitting ) !get_auto_pickup().save_character() || !get_auto_notes_settings().save() || !get_safemode().save_character() || - !cata::save_world_lua_state( g->get_world_base_save_path() + "/lua_state.json" ) || - !save_uistate_data( *this ) + !cata::save_world_lua_state( get_active_world(), "lua_state.json" ) || + !save_uistate_data() ) { return false; } else { - world_generator->last_world_name = world_generator->active_world->world_name; + world_generator->last_world_name = world_generator->active_world->info->world_name; world_generator->last_character_name = u.name; world_generator->save_last_world_info(); - world_generator->active_world->add_save( save_t::from_save_id( u.get_save_id() ) ); + world_generator->active_world->info->add_save( save_t::from_save_id( u.get_save_id() ) ); + + auto duration = world->commit_save_tx(); + add_msg( m_info, _( "World Saved (took %dms)." ), duration ); return true; } } catch( std::ios::failure &err ) { @@ -2772,7 +2777,7 @@ bool game::save( bool quitting ) std::vector game::list_active_saves() { std::vector saves; - for( auto &worldsave : world_generator->active_world->world_saves ) { + for( auto &worldsave : world_generator->active_world->info->world_saves ) { saves.push_back( worldsave.decoded_name() ); } return saves; @@ -2788,7 +2793,7 @@ void game::write_memorial_file( const std::string &filename, std::string sLastWo { const std::string &memorial_dir = PATH_INFO::memorialdir(); const std::string &memorial_active_world_dir = memorial_dir + - world_generator->active_world->world_name + "/"; + world_generator->active_world->info->world_name + "/"; //Check if both dirs exist. Nested assure_dir_exist fails if the first dir of the nested dir does not exist. if( !assure_dir_exist( memorial_dir ) ) { @@ -7153,7 +7158,7 @@ bool game::take_screenshot( const std::string &path ) const bool game::take_screenshot() const { // check that the current '/screenshots' directory exists - std::string map_directory = get_world_base_save_path() + "/screenshots/"; + std::string map_directory = get_active_world()->info->folder_path() + "/screenshots/"; assure_dir_exist( map_directory ); // build file name: /screenshots/[]_.png @@ -11413,12 +11418,12 @@ void game::quicksave() void game::quickload() { - const WORLDPTR active_world = world_generator->active_world; + world *active_world = get_active_world(); if( active_world == nullptr ) { return; } - if( active_world->save_exists( save_t::from_save_id( u.get_save_id() ) ) ) { + if( active_world->info->save_exists( save_t::from_save_id( u.get_save_id() ) ) ) { if( moves_since_last_save != 0 ) { // See if we need to reload anything MAPBUFFER.clear(); overmap_buffer.clear(); @@ -12128,17 +12133,9 @@ Creature *game::get_creature_if( const std::function & return nullptr; } -std::string game::get_player_base_save_path() const -{ - return get_world_base_save_path() + "/" + base64_encode( get_avatar().get_save_id() ); -} - -std::string game::get_world_base_save_path() const +world *game::get_active_world() const { - if( world_generator->active_world == nullptr ) { - return PATH_INFO::savedir(); - } - return world_generator->active_world->folder_path(); + return world_generator->active_world.get(); } void game::shift_destination_preview( point delta ) diff --git a/src/game.h b/src/game.h index 5ce0b31a05e0..d136efca5d74 100644 --- a/src/game.h +++ b/src/game.h @@ -99,9 +99,8 @@ class stats_tracker; template class tripoint_range; class vehicle; -struct WORLD; - -using WORLDPTR = WORLD *; +struct WORLDINFO; +class world; class live_view; class loading_ui; class overmap; @@ -158,15 +157,9 @@ class game void load_static_data(); /** - * Base path for saving player data. Just add a suffix (unique for - * the thing you want to save) and use the resulting path. - * Example: `save_ui_data(get_player_base_save_path()+".ui")` - */ - std::string get_player_base_save_path() const; - /** - * Base path for saving world data. This yields a path to a folder. + * @return The current world database, or nullptr if no world is loaded. */ - std::string get_world_base_save_path() const; + world *get_active_world() const; /** * @brief Should be invoked whenever options change. @@ -928,6 +921,7 @@ class game void move_save_to_graveyard( const std::string &dirname ); bool save_player_data(); + bool save_uistate_data() const; // ########################## DATA ################################ private: // May be a bit hacky, but it's probably better than the header spaghetti diff --git a/src/handle_action.cpp b/src/handle_action.cpp index c01f7c6fa85c..8b4c94fb2565 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -2379,7 +2379,7 @@ bool game::handle_action() break; case ACTION_WORLD_MODS: - world_generator->show_active_world_mods( world_generator->active_world->active_mod_order ); + world_generator->show_active_world_mods( world_generator->active_world->info->active_mod_order ); break; case ACTION_DEBUG: diff --git a/src/help.cpp b/src/help.cpp index 026c658d6a29..912fdf4b3c72 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -36,9 +36,9 @@ help &get_help() void help::load() { - read_from_file_optional_json( PATH_INFO::help(), [&]( JsonIn & jsin ) { + read_from_file_json( PATH_INFO::help(), [&]( JsonIn & jsin ) { deserialize( jsin ); - } ); + }, true ); } void help::deserialize( JsonIn &jsin ) diff --git a/src/init.cpp b/src/init.cpp index cb232e08d4c7..625c9cc6fed3 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -942,11 +942,12 @@ void init::load_core_bn_modfiles() ); } -void init::load_world_modfiles( loading_ui &ui, const std::string &artifacts_file ) +void init::load_world_modfiles( loading_ui &ui, const world *world, + const std::string &artifacts_file ) { clear_loaded_data(); - mod_management::t_mod_list &mods = world_generator->active_world->active_mod_order; + mod_management::t_mod_list &mods = world->info->active_mod_order; // remove any duplicates whilst preserving order (fixes #19385) std::set found; @@ -967,7 +968,7 @@ void init::load_world_modfiles( loading_ui &ui, const std::string &artifacts_fil } // TODO: get rid of artifacts - load_artifacts( artifacts_file ); + load_artifacts( world, artifacts_file ); // this code does not care about mod dependencies, // it assumes that those dependencies are static and @@ -1025,7 +1026,7 @@ bool init::check_mods_for_errors( loading_ui &ui, const std::vector &opt world_generator->set_active_world( nullptr ); world_generator->init(); const std::vector mods_empty; - WORLDPTR test_world = world_generator->make_new_world( mods_empty ); + WORLDINFO *test_world = world_generator->make_new_world( mods_empty ); if( !test_world ) { std::cerr << "Failed to generate test world." << '\n'; return false; @@ -1045,7 +1046,7 @@ bool init::check_mods_for_errors( loading_ui &ui, const std::vector &opt std::cerr << "Error loading data: " << err.what() << '\n'; } - std::string world_name = world_generator->active_world->world_name; + std::string world_name = world_generator->active_world->info->world_name; world_generator->delete_world( world_name, true ); // TODO: Why would we need these calls? diff --git a/src/init.h b/src/init.h index 1ef129753dac..bab6f6c89139 100644 --- a/src/init.h +++ b/src/init.h @@ -17,6 +17,7 @@ class loading_ui; class JsonObject; class JsonIn; +class world; /** * This class is used to load (and unload) the dynamic @@ -199,7 +200,7 @@ void load_core_bn_modfiles(); * @param artifact_file file with per-world artifact definitions * @throw std::exception if the loaded data is not valid. */ -void load_world_modfiles( loading_ui &ui, const std::string &artifacts_file ); +void load_world_modfiles( loading_ui &ui, const world *world, const std::string &artifacts_file ); /** * Load soundpack. diff --git a/src/input.cpp b/src/input.cpp index 9e28e641388e..622018f72e5b 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -84,7 +84,7 @@ bool is_mouse_enabled() std::string get_input_string_from_file( const std::string &fname ) { std::string ret; - read_from_file_optional( fname, [&ret]( std::istream & fin ) { + read_from_file( fname, [&ret]( std::istream & fin ) { getline( fin, ret ); //remove utf8 bmm if( !ret.empty() && static_cast( ret[0] ) == 0xef ) { @@ -93,7 +93,7 @@ std::string get_input_string_from_file( const std::string &fname ) while( !ret.empty() && ( ret.back() == '\r' || ret.back() == '\n' ) ) { ret.erase( ret.size() - 1, 1 ); } - } ); + }, true ); return ret; } diff --git a/src/language.cpp b/src/language.cpp index 76004c5acd22..2cfc0ba9e922 100644 --- a/src/language.cpp +++ b/src/language.cpp @@ -504,7 +504,7 @@ static bool add_mod_catalogues( std::vector &list, const std::s return false; } - const std::vector &mods = world_generator->active_world->active_mod_order; + const std::vector &mods = world_generator->active_world->info->active_mod_order; for( const mod_id &mod : mods ) { add_mod_catalogue_if_exists( list, lang_id, mod->path ); } diff --git a/src/main_menu.cpp b/src/main_menu.cpp index 0927c34fae4c..ffc420780957 100644 --- a/src/main_menu.cpp +++ b/src/main_menu.cpp @@ -218,7 +218,7 @@ void main_menu::display_sub_menu( int sel, const point &bottom_left, int sel_lin } std::vector all_worldnames = world_generator->all_worldnames(); for( int i = 0; static_cast( i ) < all_worldnames.size(); i++ ) { - WORLDPTR world = world_generator->get_world( all_worldnames[i] ); + WORLDINFO *world = world_generator->get_world( all_worldnames[i] ); int savegames_count = world->world_saves.size(); nc_color clr = c_white; std::string txt = all_worldnames[i]; @@ -364,7 +364,7 @@ std::vector main_menu::load_file( const std::string &path, const std::string &alt_text ) const { std::vector result; - read_from_file_optional( path, [&result]( std::istream & fin ) { + read_from_file( path, [&result]( std::istream & fin ) { std::string line; while( std::getline( fin, line ) ) { if( !line.empty() && line[0] == '#' ) { @@ -372,7 +372,7 @@ std::vector main_menu::load_file( const std::string &path, } result.push_back( line ); } - } ); + }, true ); if( result.empty() ) { result.push_back( alt_text ); } @@ -427,14 +427,14 @@ void main_menu::init_strings() // Credits mmenu_credits.clear(); - read_from_file_optional( PATH_INFO::credits(), [&]( std::istream & stream ) { + read_from_file( PATH_INFO::credits(), [&]( std::istream & stream ) { std::string line; while( std::getline( stream, line ) ) { if( line[0] != '#' ) { mmenu_credits += ( line.empty() ? " " : line ) + "\n"; } } - } ); + }, true ); if( mmenu_credits.empty() ) { mmenu_credits = _( "No credits information found." ); @@ -894,7 +894,7 @@ bool main_menu::new_character_tab() } ); g->gamemode.reset(); - WORLDPTR world; + WORLDINFO *world; if( sel2 == 5 ) { g->gamemode = get_special_game( special_game_type::TUTORIAL ); world = world_generator->make_new_world( special_game_type::TUTORIAL ); @@ -961,7 +961,7 @@ bool main_menu::new_character_tab() bool main_menu::load_character_tab( const std::string &worldname ) { - WORLDPTR world = world_generator->get_world( worldname ); + WORLDINFO *world = world_generator->get_world( worldname ); savegames = world->world_saves; if( MAP_SHARING::isSharing() ) { auto new_end = std::remove_if( savegames.begin(), savegames.end(), []( const save_t &str ) { @@ -1083,7 +1083,7 @@ void main_menu::world_tab( const std::string &worldname ) "If you have just started playing, consider creating new world instead.\n" "Proceed?" ) ) ) { - WORLDPTR world = world_generator->get_world( worldname ); + WORLDINFO *world = world_generator->get_world( worldname ); world_generator->edit_active_world_mods( world ); } break; diff --git a/src/map_memory.cpp b/src/map_memory.cpp index 836bbe7b8405..a96601f3725e 100644 --- a/src/map_memory.cpp +++ b/src/map_memory.cpp @@ -9,6 +9,7 @@ #include "line.h" #include "translations.h" #include "map.h" +#include "world.h" const memorized_terrain_tile mm_submap::default_tile { "", 0, 0 }; const int mm_submap::default_symbol = 0; @@ -16,21 +17,6 @@ const int mm_submap::default_symbol = 0; #define dbg(x) DebugLog((x),DC::MapMem) -static std::string find_legacy_mm_file() -{ - return g->get_player_base_save_path() + ".mm"; -} - -static std::string find_mm_dir() -{ - return string_format( "%s.mm1", g->get_player_base_save_path() ); -} - -static std::string find_region_path( const std::string &dirname, const tripoint &p ) -{ - return string_format( "%s/%d.%d.%d.mmr", dirname, p.x, p.y, p.z ); -} - /** * Helper class for converting global sm coord into * global mm_region coord + sm coord within the region. @@ -235,14 +221,7 @@ shared_ptr_fast map_memory::load_submap( const tripoint &sm_pos ) return nullptr; } - const std::string dirname = find_mm_dir(); reg_coord_pair p( sm_pos ); - const std::string path = find_region_path( dirname, p.reg ); - - if( !dir_exist( dirname ) ) { - // Old saves don't have [plname].mm1 folder - return nullptr; - } mm_region mmr; const auto loader = [&]( JsonIn & jsin ) { @@ -250,7 +229,7 @@ shared_ptr_fast map_memory::load_submap( const tripoint &sm_pos ) }; try { - if( !read_from_file_optional_json( path, loader ) ) { + if( !g->get_active_world()->read_player_mm_quad( p.reg, loader ) ) { // Region not found return nullptr; } @@ -299,25 +278,8 @@ mm_submap &map_memory::get_submap( const tripoint &sm_pos ) void map_memory::load( const tripoint &pos ) { - const std::string dirname = find_mm_dir(); - clear_cache(); - if( !dir_exist( dirname ) ) { - // Old saves have [plname].mm file and no [plname].mm1 folder - const std::string legacy_file = find_legacy_mm_file(); - if( file_exist( legacy_file ) ) { - try { - read_from_file_optional_json( legacy_file, [&]( JsonIn & jsin ) { - this->load_legacy( jsin ); - } ); - } catch( const std::exception &err ) { - debugmsg( "Failed to load legacy memory map file: %s", err.what() ); - } - } - return; - } - coord_pair p( pos ); tripoint start = p.sm - tripoint( MM_SIZE / 2, MM_SIZE / 2, 0 ); dbg( DL::Info ) << "[LOAD] Loading memory map around " << p.sm << ". Loading submaps within " @@ -333,8 +295,6 @@ void map_memory::load( const tripoint &pos ) bool map_memory::save( const tripoint &pos ) { tripoint sm_center = coord_pair( pos ).sm; - const std::string dirname = find_mm_dir(); - assure_dir_exist( dirname ); clear_cache(); @@ -361,12 +321,6 @@ bool map_memory::save( const tripoint &pos ) const tripoint ®p = it.first; mm_region ® = it.second; if( !reg.is_empty() ) { - const std::string path = find_region_path( dirname, regp ); - const std::string descr = string_format( - _( "memory map region for (%d,%d,%d)" ), - regp.x, regp.y, regp.z - ); - const auto writer = [&]( std::ostream & fout ) -> void { fout << serialize_wrapper( [&]( JsonOut & jsout ) { @@ -374,7 +328,7 @@ bool map_memory::save( const tripoint &pos ) } ); }; - const bool res = write_to_file( path, writer, descr.c_str() ); + const bool res = g->get_active_world()->write_player_mm_quad( regp, writer ); result = result & res; } tripoint regp_sm = mmr_to_sm_copy( regp ); diff --git a/src/mapbuffer.cpp b/src/mapbuffer.cpp index bc017f9a7e3d..e5f1ae5c0132 100644 --- a/src/mapbuffer.cpp +++ b/src/mapbuffer.cpp @@ -24,18 +24,7 @@ #include "submap.h" #include "translations.h" #include "ui_manager.h" - -static std::string find_quad_path( const std::string &dirname, const tripoint &om_addr ) -{ - return string_format( "%s/%d.%d.%d.map", dirname, om_addr.x, om_addr.y, om_addr.z ); -} - -static std::string find_dirname( const tripoint &om_addr ) -{ - const tripoint segment_addr = omt_to_seg_copy( om_addr ); - return string_format( "%s/maps/%d.%d.%d", g->get_world_base_save_path(), segment_addr.x, - segment_addr.y, segment_addr.z ); -} +#include "world.h" mapbuffer MAPBUFFER; @@ -97,8 +86,6 @@ submap *mapbuffer::lookup_submap( const tripoint &p ) void mapbuffer::save( bool delete_after_save ) { - assure_dir_exist( g->get_world_base_save_path() + "/maps" ); - int num_saved_submaps = 0; int num_total_submaps = submaps.size(); @@ -138,13 +125,11 @@ void mapbuffer::save( bool delete_after_save ) // A segment is a chunk of 32x32 submap quads. // We're breaking them into subdirectories so there aren't too many files per directory. // Might want to make a set for this one too so it's only checked once per save(). - const std::string dirname = find_dirname( om_addr ); - const std::string quad_path = find_quad_path( dirname, om_addr ); // delete_on_save deletes everything, otherwise delete submaps // outside the current map. const bool zlev_del = !map_has_zlevels && om_addr.z != g->get_levz(); - save_quad( dirname, quad_path, om_addr, submaps_to_delete, + save_quad( om_addr, submaps_to_delete, delete_after_save || zlev_del || om_addr.x < map_origin.x || om_addr.y < map_origin.y || om_addr.x > map_origin.x + HALF_MAPSIZE || @@ -158,8 +143,7 @@ void mapbuffer::save( bool delete_after_save ) get_distribution_grid_tracker().on_saved(); } -void mapbuffer::save_quad( const std::string &dirname, const std::string &filename, - const tripoint &om_addr, std::list &submaps_to_delete, +void mapbuffer::save_quad( const tripoint &om_addr, std::list &submaps_to_delete, bool delete_after_save ) { std::vector offsets; @@ -198,9 +182,7 @@ void mapbuffer::save_quad( const std::string &dirname, const std::string &filena return; } - // Don't create the directory if it would be empty - assure_dir_exist( dirname ); - write_to_file( filename, [&]( std::ostream & fout ) { + g->get_active_world()->write_map_quad( om_addr, [&]( std::ostream & fout ) { JsonOut jsout( fout ); jsout.start_array(); for( auto &submap_addr : submap_addrs ) { @@ -244,29 +226,16 @@ submap *mapbuffer::unserialize_submaps( const tripoint &p ) { // Map the tripoint to the submap quad that stores it. const tripoint om_addr = sm_to_omt_copy( p ); - const std::string dirname = find_dirname( om_addr ); - std::string quad_path = find_quad_path( dirname, om_addr ); - - if( !file_exist( quad_path ) ) { - // Fix for old saves where the path was generated using std::stringstream, which - // did format the number using the current locale. That formatting may insert - // thousands separators, so the resulting path is "map/1,234.7.8.map" instead - // of "map/1234.7.8.map". - std::ostringstream buffer; - buffer << dirname << "/" << om_addr.x << "." << om_addr.y << "." << om_addr.z << ".map"; - if( file_exist( buffer.str() ) ) { - quad_path = buffer.str(); - } - } using namespace std::placeholders; - if( !read_from_file_optional_json( quad_path, std::bind( &mapbuffer::deserialize, this, _1 ) ) ) { + if( !g->get_active_world()->read_map_quad( om_addr, std::bind( &mapbuffer::deserialize, + this, _1 ) ) ) { // If it doesn't exist, trigger generating it. return nullptr; } if( !submaps.contains( p ) ) { - debugmsg( "file %s did not contain the expected submap %d,%d,%d", - quad_path, p.x, p.y, p.z ); + debugmsg( "file did not contain the expected submap %d,%d,%d", + p.x, p.y, p.z ); return nullptr; } return submaps[ p ].get(); diff --git a/src/mapbuffer.h b/src/mapbuffer.h index 0b18b6c7efa5..cab4f9532380 100644 --- a/src/mapbuffer.h +++ b/src/mapbuffer.h @@ -79,8 +79,7 @@ class mapbuffer void remove_submap( tripoint addr ); submap *unserialize_submaps( const tripoint &p ); void deserialize( JsonIn &jsin ); - void save_quad( const std::string &dirname, const std::string &filename, - const tripoint &om_addr, std::list &submaps_to_delete, + void save_quad( const tripoint &om_addr, std::list &submaps_to_delete, bool delete_after_save ); submap_map_t submaps; }; diff --git a/src/mod_manager.cpp b/src/mod_manager.cpp index 4d5a0fc5c19e..9076d5fe7d40 100644 --- a/src/mod_manager.cpp +++ b/src/mod_manager.cpp @@ -102,14 +102,14 @@ const std::map &get_mod_list_cat_tab() void mod_manager::load_replacement_mods( const std::string &path ) { - read_from_file_optional_json( path, [&]( JsonIn & jsin ) { + read_from_file_json( path, [&]( JsonIn & jsin ) { jsin.start_array(); while( !jsin.end_array() ) { auto arr = jsin.get_array(); mod_replacements.emplace( mod_id( arr.get_string( 0 ) ), mod_id( arr.size() > 1 ? arr.get_string( 1 ) : "" ) ); } - } ); + }, true ); } mod_manager::mod_manager() @@ -308,7 +308,7 @@ std::optional load_modfile( const JsonObject &jo, const std::st void load_mod_info( const std::string &info_file_path, std::vector &out ) { const std::string main_path = info_file_path.substr( 0, info_file_path.find_last_of( "/\\" ) ); - read_from_file_optional_json( info_file_path, [&]( JsonIn & jsin ) { + read_from_file_json( info_file_path, [&]( JsonIn & jsin ) { if( jsin.test_object() ) { // find type and dispatch single object JsonObject jo = jsin.get_object(); @@ -334,7 +334,7 @@ void load_mod_info( const std::string &info_file_path, std::vector load_mod_list( const std::string &path ) jsin.read( res, true ); }; - if( read_from_file_optional_json( path, reader ) ) { + if( read_from_file_json( path, reader, true ) ) { return { std::move( res ) }; } else { return std::nullopt; @@ -413,12 +413,12 @@ bool mod_manager::set_default_mods( const t_mod_list &mods ) return mod_management::save_mod_list( mods, PATH_INFO::mods_user_default() ); } -std::string mod_manager::get_mods_list_file( const WORLDPTR world ) +std::string mod_manager::get_mods_list_file( WORLDINFO *world ) { return world->folder_path() + "/mods.json"; } -void mod_manager::save_mods_list( WORLDPTR world ) const +void mod_manager::save_mods_list( WORLDINFO *world ) const { if( world == nullptr ) { return; @@ -436,7 +436,7 @@ void mod_manager::save_mods_list( WORLDPTR world ) const }, _( "list of mods" ) ); } -void mod_manager::load_mods_list( WORLDPTR world ) const +void mod_manager::load_mods_list( WORLDINFO *world ) const { if( world == nullptr ) { return; @@ -444,7 +444,7 @@ void mod_manager::load_mods_list( WORLDPTR world ) const std::vector &amo = world->active_mod_order; amo.clear(); bool obsolete_mod_found = false; - read_from_file_optional_json( get_mods_list_file( world ), [&]( JsonIn & jsin ) { + read_from_file_json( get_mods_list_file( world ), [&]( JsonIn & jsin ) { for( const std::string line : jsin.get_array() ) { const mod_id mod( line ); if( std::find( amo.begin(), amo.end(), mod ) != amo.end() ) { @@ -460,7 +460,7 @@ void mod_manager::load_mods_list( WORLDPTR world ) const } amo.push_back( mod ); } - } ); + }, true ); if( obsolete_mod_found ) { // If we found an obsolete mod, overwrite the mod list without the obsolete one. save_mods_list( world ); diff --git a/src/mod_manager.h b/src/mod_manager.h index 279ff31ad35e..7b52a3070563 100644 --- a/src/mod_manager.h +++ b/src/mod_manager.h @@ -14,9 +14,7 @@ #include "ret_val.h" #include "type_id.h" -struct WORLD; - -using WORLDPTR = WORLD *; +struct WORLDINFO; class JsonObject; class dependency_tree; class mod_manager; @@ -165,12 +163,12 @@ class mod_manager * Save list of mods that are active in that world to * the world folder. */ - void save_mods_list( WORLDPTR world ) const; + void save_mods_list( WORLDINFO *world ) const; /** * Load list of mods that should be active in that * world. */ - void load_mods_list( WORLDPTR world ) const; + void load_mods_list( WORLDINFO *world ) const; const t_mod_list &get_default_mods() const; bool set_default_mods( const t_mod_list &mods ); std::vector get_all_sorted() const; @@ -184,7 +182,7 @@ class mod_manager * @returns path of a file in the world folder that contains * the list of mods that should be loaded for this world. */ - static std::string get_mods_list_file( WORLDPTR world ); + static std::string get_mods_list_file( WORLDINFO *world ); /** * Add mods from given list to the pool. diff --git a/src/newcharacter.cpp b/src/newcharacter.cpp index 4f8a3d800ac8..759c40073b2d 100644 --- a/src/newcharacter.cpp +++ b/src/newcharacter.cpp @@ -225,7 +225,8 @@ void avatar::randomize( const bool random_scenario, points_left &points, bool pl init_age = rng( 16, 55 ); // if adjusting min and max height from 145 and 200, make sure to see set_description() init_height = rng( 145, 200 ); - bool cities_enabled = world_generator->active_world->WORLD_OPTIONS["CITY_SIZE"].getValue() != "0"; + bool cities_enabled = world_generator->active_world->info->WORLD_OPTIONS["CITY_SIZE"].getValue() != + "0"; if( random_scenario ) { std::vector scenarios; for( const auto &scen : scenario::get_all() ) { @@ -455,7 +456,7 @@ bool avatar::create( character_type type, const std::string &tempname ) } auto nameExists = [&]( const std::string & name ) { - return world_generator->active_world->save_exists( save_t::from_save_id( name ) ) && + return world_generator->active_world->info->save_exists( save_t::from_save_id( name ) ) && !query_yn( _( "A save with the name '%s' already exists in this world.\n" "Saving will overwrite the already existing character.\n\n" "Continue anyways?" ), name ); @@ -2338,7 +2339,7 @@ tab_direction set_scenario( avatar &u, points_left &points, do { if( recalc_scens ) { sorted_scens.clear(); - auto &wopts = world_generator->active_world->WORLD_OPTIONS; + auto &wopts = world_generator->active_world->info->WORLD_OPTIONS; for( const auto &scen : scenario::get_all() ) { if( scen.scen_is_blacklisted() ) { continue; diff --git a/src/options.cpp b/src/options.cpp index 15b30e2252b3..9a81d25de8c5 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -2748,7 +2748,7 @@ static void refresh_tiles( bool used_tiles_changed, bool pixel_minimap_height_ch tilecontext->load_tileset( get_option( "TILES" ), - ingame ? world_generator->active_world->active_mod_order : dummy, + ingame ? world_generator->active_world->info->active_mod_order : dummy, /*precheck=*/false, /*force=*/force_tile_change, /*pump_events=*/true @@ -3309,8 +3309,8 @@ std::string options_manager::show( bool ingame, const bool world_options_only, save(); if( ingame && world_options_changed ) { - world_generator->active_world->WORLD_OPTIONS = ACTIVE_WORLD_OPTIONS; - world_generator->active_world->save(); + world_generator->active_world->info->WORLD_OPTIONS = ACTIVE_WORLD_OPTIONS; + world_generator->active_world->info->save(); } g->on_options_changed(); } else { @@ -3478,9 +3478,9 @@ bool options_manager::save() void options_manager::load() { const auto file = PATH_INFO::options(); - read_from_file_optional_json( file, [&]( JsonIn & jsin ) { + read_from_file_json( file, [&]( JsonIn & jsin ) { deserialize( jsin ); - } ); + }, true ); cache_to_globals(); } diff --git a/src/overmap.cpp b/src/overmap.cpp index 07f2c2d8135a..82dd30748803 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -60,6 +60,7 @@ #include "string_utils.h" #include "text_snippets.h" #include "translations.h" +#include "world.h" static const efftype_id effect_pet( "pet" ); @@ -6080,18 +6081,18 @@ void overmap::place_radios() void overmap::open( overmap_special_batch &enabled_specials ) { - const std::string terfilename = overmapbuffer::terrain_filename( loc ); + // const std::string terfilename = overmapbuffer::terrain_filename( loc ); const auto ter_reader = [&]( std::istream & fin ) { - overmap::unserialize( fin, terfilename ); + overmap::unserialize( fin, string_format( "overmap terrain %d.%d", loc.x(), loc.y() ) ); }; - if( read_from_file_optional( terfilename, ter_reader ) ) { - const std::string plrfilename = overmapbuffer::player_filename( loc ); + if( g->get_active_world()->read_overmap( loc, ter_reader ) ) { + // const std::string plrfilename = overmapbuffer::player_filename( loc ); const auto plr_reader = [&]( std::istream & fin ) { - overmap::unserialize_view( fin, plrfilename ); + overmap::unserialize_view( fin, string_format( "overmap visibility %d.%d", loc.x(), loc.y() ) ); }; - read_from_file_optional( plrfilename, plr_reader ); + g->get_active_world()->read_overmap_player_visibility( loc, plr_reader ); } else { // No map exists! Prepare neighbors, and generate one. std::vector pointers; // Fetch south and north @@ -6111,11 +6112,11 @@ void overmap::open( overmap_special_batch &enabled_specials ) // Note: this may throw io errors from std::ofstream void overmap::save() const { - write_to_file( overmapbuffer::player_filename( loc ), [&]( std::ostream & stream ) { + g->get_active_world()->write_overmap_player_visibility( loc, [&]( std::ostream & stream ) { serialize_view( stream ); } ); - write_to_file( overmapbuffer::terrain_filename( loc ), [&]( std::ostream & stream ) { + g->get_active_world()->write_overmap( loc, [&]( std::ostream & stream ) { serialize( stream ); } ); } diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index 69eeeea1a076..cb873b3ed5e0 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -44,6 +44,7 @@ #include "vehicle.h" #include "vehicle_part.h" #include "profile.h" +#include "world.h" class map_extra; @@ -66,15 +67,6 @@ omt_find_params::~omt_find_params() = default; omt_route_params::~omt_route_params() = default; -std::string overmapbuffer::terrain_filename( const point_abs_om &p ) -{ - return string_format( "%s/o.%d.%d", g->get_world_base_save_path(), p.x(), p.y() ); -} - -std::string overmapbuffer::player_filename( const point_abs_om &p ) -{ - return string_format( "%s.seen.%d.%d", g->get_player_base_save_path(), p.x(), p.y() ); -} overmap &overmapbuffer::get( const point_abs_om &p ) { @@ -267,7 +259,7 @@ overmap *overmapbuffer::get_existing( const point_abs_om &p ) // checked in a previous call of this function). return nullptr; } - if( file_exist( terrain_filename( p ) ) ) { + if( g->get_active_world() && g->get_active_world()->overmap_exists( p ) ) { // File exists, load it normally (the get function // indirectly call overmap::open to do so). return &get( p ); diff --git a/src/overmapbuffer.h b/src/overmapbuffer.h index a3a242775841..b821f1c20e77 100644 --- a/src/overmapbuffer.h +++ b/src/overmapbuffer.h @@ -151,9 +151,6 @@ class overmapbuffer public: overmapbuffer(); - static std::string terrain_filename( const point_abs_om & ); - static std::string player_filename( const point_abs_om & ); - /** * Uses overmap coordinates, that means x and y are directly * compared with the position of the overmap. diff --git a/src/panels.cpp b/src/panels.cpp index 4f9dec354a40..4002c14cfa72 100644 --- a/src/panels.cpp +++ b/src/panels.cpp @@ -2226,9 +2226,9 @@ bool panel_manager::save() bool panel_manager::load() { - return read_from_file_optional_json( PATH_INFO::panel_options(), [&]( JsonIn & jsin ) { + return read_from_file_json( PATH_INFO::panel_options(), [&]( JsonIn & jsin ) { deserialize( jsin ); - } ); + }, true ); } void panel_manager::serialize( JsonOut &json ) diff --git a/src/safemode_ui.cpp b/src/safemode_ui.cpp index 350c3fb29764..d6e7d75c9ee6 100644 --- a/src/safemode_ui.cpp +++ b/src/safemode_ui.cpp @@ -29,6 +29,7 @@ #include "string_utils.h" #include "translations.h" #include "ui_manager.h" +#include "world.h" safemode &get_safemode() { @@ -761,23 +762,25 @@ bool safemode::save_global() bool safemode::save( const bool is_character_in ) { is_character = is_character_in; - auto file = PATH_INFO::safemode(); - - if( is_character ) { - file = g->get_player_base_save_path() + ".sfm.json"; - if( !file_exist( g->get_player_base_save_path() + ".sav" ) ) { - return true; //Character not saved yet. - } - } - - return write_to_file( file, [&]( std::ostream & fout ) { + auto serializer = [&]( std::ostream & fout ) { JsonOut jout( fout, true ); serialize( jout ); if( !is_character ) { create_rules(); } - }, _( "safemode configuration" ) ); + }; + + if( is_character ) { + world *world = g->get_active_world(); + if( !world->player_file_exist( ".sav" ) ) { + return true; //Character not saved yet. + } + + return world->write_to_player_file( ".sfm.json", serializer, _( "safemode configuration" ) ); + } else { + return write_to_file( PATH_INFO::safemode(), serializer, _( "safemode configuration" ) ); + } } void safemode::load_character() @@ -793,25 +796,21 @@ void safemode::load_global() void safemode::load( const bool is_character_in ) { is_character = is_character_in; - - std::ifstream fin; - std::string file = PATH_INFO::safemode(); - if( is_character ) { - file = g->get_player_base_save_path() + ".sfm.json"; - } - - fin.open( file.c_str(), std::ifstream::in | std::ifstream::binary ); - - if( fin.good() ) { + auto loader = [&]( JsonIn & jsin ) { try { - JsonIn jsin( fin ); deserialize( jsin ); } catch( const JsonError &e ) { - debugmsg( "Error while loading safemode settings: %s", e.what() ); + debugmsg( "Error loading safemode settings: %s", e.c_str() ); } + }; + + std::ifstream fin; + if( is_character ) { + g->get_active_world()->read_from_player_file_json( ".sfm.json", loader, true ); + } else { + read_from_file_json( PATH_INFO::safemode(), loader, true ); } - fin.close(); create_rules(); } diff --git a/src/sdltiles.cpp b/src/sdltiles.cpp index 3afe06f1e080..d79920f40ae8 100644 --- a/src/sdltiles.cpp +++ b/src/sdltiles.cpp @@ -3590,7 +3590,7 @@ void load_tileset() } tilecontext->load_tileset( get_option( "TILES" ), - world_generator->active_world->active_mod_order, + world_generator->active_world->info->active_mod_order, /*precheck=*/false, /*force=*/false, /*pump_events=*/true diff --git a/src/world.cpp b/src/world.cpp new file mode 100644 index 000000000000..52e4ff16b39f --- /dev/null +++ b/src/world.cpp @@ -0,0 +1,426 @@ +#include "world.h" + +#include +#include +#include + +#include "game.h" +#include "avatar.h" +#include "debug.h" +#include "cata_utility.h" +#include "filesystem.h" +#include "output.h" +#include "worldfactory.h" +#include "mod_manager.h" +#include "path_info.h" + +#define dbg(x) DebugLogFL((x),DC::Main) + +save_t::save_t( const std::string &name ): name( name ) {} + +std::string save_t::decoded_name() const +{ + return name; +} + +std::string save_t::base_path() const +{ + return base64_encode( name ); +} + +save_t save_t::from_save_id( const std::string &save_id ) +{ + return save_t( save_id ); +} + +save_t save_t::from_base_path( const std::string &base_path ) +{ + return save_t( base64_decode( base_path ) ); +} + +std::string WORLDINFO::folder_path() const +{ + return PATH_INFO::savedir() + world_name; +} + +WORLDINFO::WORLDINFO() +{ + world_name = world_generator->get_next_valid_worldname(); + WORLD_OPTIONS = get_options().get_world_defaults(); + world_save_format = save_format::V1; + + world_saves.clear(); + active_mod_order = world_generator->get_mod_manager().get_default_mods(); +} + +void WORLDINFO::COPY_WORLD( const WORLDINFO *world_to_copy ) +{ + world_name = world_to_copy->world_name + "_copy"; + WORLD_OPTIONS = world_to_copy->WORLD_OPTIONS; + world_save_format = world_to_copy->world_save_format; + active_mod_order = world_to_copy->active_mod_order; +} + +bool WORLDINFO::needs_lua() const +{ + for( const mod_id &mod : active_mod_order ) { + if( mod.is_valid() && mod->lua_api_version ) { + return true; + } + } + return false; +} + +bool WORLDINFO::save_exists( const save_t &name ) const +{ + return std::find( world_saves.begin(), world_saves.end(), name ) != world_saves.end(); +} + +void WORLDINFO::add_save( const save_t &name ) +{ + if( !save_exists( name ) ) { + world_saves.push_back( name ); + } +} + +void WORLDINFO::load_options( JsonIn &jsin ) +{ + auto &opts = get_options(); + + jsin.start_array(); + while( !jsin.end_array() ) { + JsonObject jo = jsin.get_object(); + jo.allow_omitted_members(); + const std::string name = opts.migrateOptionName( jo.get_string( "name" ) ); + const std::string value = opts.migrateOptionValue( jo.get_string( "name" ), + jo.get_string( "value" ) ); + + if( opts.has_option( name ) && opts.get_option( name ).getPage() == "world_default" ) { + WORLD_OPTIONS[ name ].setValue( value ); + } + } +} + +void WORLDINFO::load_legacy_options( std::istream &fin ) +{ + auto &opts = get_options(); + + //load legacy txt + std::string sLine; + while( !fin.eof() ) { + getline( fin, sLine ); + if( !sLine.empty() && sLine[0] != '#' && std::count( sLine.begin(), sLine.end(), ' ' ) == 1 ) { + size_t ipos = sLine.find( ' ' ); + // make sure that the option being loaded is part of the world_default page in OPTIONS + // In 0.C some lines consisted of a space and nothing else + const std::string name = opts.migrateOptionName( sLine.substr( 0, ipos ) ); + const std::string value = opts.migrateOptionValue( sLine.substr( 0, ipos ), sLine.substr( ipos + 1, + sLine.length() ) ); + + if( ipos != 0 && opts.get_option( name ).getPage() == "world_default" ) { + WORLD_OPTIONS[name].setValue( value ); + } + } + } +} + +bool WORLDINFO::load_options() +{ + WORLD_OPTIONS = get_options().get_world_defaults(); + + using namespace std::placeholders; + const auto path = folder_path() + "/" + PATH_INFO::worldoptions(); + return read_from_file_json( path, [&]( JsonIn & jsin ) { + load_options( jsin ); + }, true ); +} + +bool WORLDINFO::save( const bool is_conversion ) const +{ + if( !assure_dir_exist( folder_path() ) ) { + DebugLog( DL::Error, DC::Main ) << "Unable to create or open world[" << world_name + << "] directory for saving"; + return false; + } + + if( !is_conversion ) { + const auto savefile = folder_path() + "/" + PATH_INFO::worldoptions(); + const bool saved = write_to_file( savefile, [&]( std::ostream & fout ) { + JsonOut jout( fout ); + + jout.start_array(); + + for( auto &elem : WORLD_OPTIONS ) { + // Skip hidden option because it is set by mod and should not be saved + if( !elem.second.getDefaultText().empty() ) { + jout.start_object(); + + jout.member( "info", elem.second.getTooltip() ); + jout.member( "default", elem.second.getDefaultText( false ) ); + jout.member( "name", elem.first ); + jout.member( "value", elem.second.getValue( true ) ); + + jout.end_object(); + } + } + + jout.end_array(); + }, _( "world data" ) ); + if( !saved ) { + return false; + } + } + + world_generator->get_mod_manager().save_mods_list( const_cast( this ) ); + return true; +} + +void load_world_option( const JsonObject &jo ) +{ + auto arr = jo.get_array( "options" ); + if( arr.empty() ) { + jo.throw_error( "no options specified", "options" ); + } + for( const std::string line : arr ) { + get_options().get_option( line ).setValue( "true" ); + } +} + +//load external option from json +void load_external_option( const JsonObject &jo ) +{ + auto name = jo.get_string( "name" ); + auto stype = jo.get_string( "stype" ); + options_manager &opts = get_options(); + if( !opts.has_option( name ) ) { + auto sinfo = jo.get_string( "info" ); + opts.add_external( name, "external_options", stype, sinfo, sinfo ); + } + options_manager::cOpt &opt = opts.get_option( name ); + if( stype == "float" ) { + opt.setValue( static_cast( jo.get_float( "value" ) ) ); + } else if( stype == "int" ) { + opt.setValue( jo.get_int( "value" ) ); + } else if( stype == "bool" ) { + if( jo.get_bool( "value" ) ) { + opt.setValue( "true" ); + } else { + opt.setValue( "false" ); + } + } else if( stype == "string" ) { + opt.setValue( jo.get_string( "value" ) ); + } else { + jo.throw_error( "Unknown or unsupported stype for external option", "stype" ); + } + // Just visit this member if it exists + if( jo.has_member( "info" ) ) { + jo.get_string( "info" ); + } +} + +world::world( WORLDINFO *info ) + : info( info ) + , save_tx_start_ts( 0 ) +{ + if( !assure_dir_exist( "" ) + || !assure_dir_exist( "/maps" ) ) { + dbg( DL::Error ) << "Unable to create or open world directory structure: " << info->folder_path(); + } +} + +world::~world() +{ +} + +void world::start_save_tx() +{ + if( save_tx_start_ts != 0 ) { + throw std::runtime_error( "Attempted to start a save transaction while one was already in progress" ); + } + save_tx_start_ts = std::chrono::duration_cast< std::chrono::milliseconds >( + std::chrono::system_clock::now().time_since_epoch() + ).count(); +} + +int64_t world::commit_save_tx() +{ + if( save_tx_start_ts == 0 ) { + throw std::runtime_error( "Attempted to commit a save transaction while none was in progress" ); + } + int64_t now = std::chrono::duration_cast< std::chrono::milliseconds >( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + int64_t duration = now - save_tx_start_ts; + save_tx_start_ts = 0; + return duration; +} + +/** + * DOMAIN SPECIFIC: MAP + */ + +static std::string get_quad_dirname( const tripoint &om_addr ) +{ + const tripoint segment_addr = omt_to_seg_copy( om_addr ); + return string_format( "maps/%d.%d.%d", segment_addr.x, segment_addr.y, segment_addr.z ); +} + +static std::string get_quad_filename( const tripoint &om_addr ) +{ + return string_format( "%d.%d.%d.map", om_addr.x, om_addr.y, om_addr.z ); +} + +bool world::read_map_quad( const tripoint &om_addr, file_read_json_fn reader ) const +{ + const std::string dirname = get_quad_dirname( om_addr ); + std::string quad_path = dirname + "/" + get_quad_filename( om_addr ); + + if( !file_exist( quad_path ) ) { + // Fix for old saves where the path was generated using std::stringstream, which + // did format the number using the current locale. That formatting may insert + // thousands separators, so the resulting path is "map/1,234.7.8.map" instead + // of "map/1234.7.8.map". + std::ostringstream buffer; + buffer << dirname << "/" << om_addr.x << "." << om_addr.y << "." << om_addr.z << ".map"; + if( file_exist( buffer.str() ) ) { + quad_path = buffer.str(); + } + } + + return read_from_file_json( quad_path, reader, true ); +} + +bool world::write_map_quad( const tripoint &om_addr, file_write_fn writer ) const +{ + const std::string dirname = get_quad_dirname( om_addr ); + std::string quad_path = dirname + "/" + get_quad_filename( om_addr ); + + assure_dir_exist( dirname ); + return write_to_file( quad_path, writer ); +} + +/** + * DOMAIN SPECIFIC: OVERMAP + */ + +std::string world::overmap_terrain_filename( const point_abs_om &p ) const +{ + return string_format( "o.%d.%d", p.x(), p.y() ); +} + +std::string world::overmap_player_filename( const point_abs_om &p ) const +{ + return string_format( ".seen.%d.%d", p.x(), p.y() ); +} + +bool world::overmap_exists( const point_abs_om &p ) const +{ + return file_exist( overmap_terrain_filename( p ) ); +} + +bool world::read_overmap( const point_abs_om &p, file_read_fn reader ) const +{ + return read_from_file( overmap_terrain_filename( p ), reader, true ); +} + +bool world::read_overmap_player_visibility( const point_abs_om &p, file_read_fn reader ) +{ + return read_from_player_file( overmap_player_filename( p ), reader, true ); +} + +bool world::write_overmap( const point_abs_om &p, file_write_fn writer ) const +{ + return write_to_file( overmap_terrain_filename( p ), writer ); +} + +bool world::write_overmap_player_visibility( const point_abs_om &p, file_write_fn writer ) +{ + return write_to_player_file( overmap_player_filename( p ), writer ); +} + +/** + * DOMAIN SPECIFIC: MAP MEMORY + */ +static std::string get_mm_filename( const tripoint &p ) +{ + return string_format( "%d.%d.%d.mmr", p.x, p.y, p.z ); +} + +bool world::read_player_mm_quad( const tripoint &p, file_read_json_fn reader ) +{ + return read_from_player_file_json( ".mm1/" + get_mm_filename( p ), reader, true ); +} + +bool world::write_player_mm_quad( const tripoint &p, file_write_fn writer ) +{ + const std::string descr = string_format( + _( "memory map region for (%d,%d,%d)" ), + p.x, p.y, p.z + ); + assure_dir_exist( get_player_path() + ".mm1" ); + return write_to_player_file( ".mm1/" + get_mm_filename( p ), writer, descr.c_str() ); +} + +/** + * PLAYER OPERATIONS + */ + +std::string world::get_player_path() const +{ + return base64_encode( g->u.get_save_id() ); +} + +bool world::player_file_exist( const std::string &path ) +{ + return file_exist( get_player_path() + path ); +} + +bool world::write_to_player_file( const std::string &path, file_write_fn writer, + const char *fail_message ) +{ + return write_to_file( get_player_path() + path, writer, fail_message ); +} + +bool world::read_from_player_file( const std::string &path, file_read_fn reader, + bool optional ) +{ + return read_from_file( get_player_path() + path, reader, optional ); +} + +bool world::read_from_player_file_json( const std::string &path, file_read_json_fn reader, + bool optional ) +{ + return read_from_file_json( get_player_path() + path, reader, optional ); +} + +/** + * GENERIC OPERATIONS + */ + +bool world::assure_dir_exist( const std::string &path ) const +{ + return ::assure_dir_exist( info->folder_path() + "/" + path ); +} + +bool world::file_exist( const std::string &path ) const +{ + return ::file_exist( info->folder_path() + "/" + path ); +} + +bool world::write_to_file( const std::string &path, file_write_fn writer, + const char *fail_message ) const +{ + return ::write_to_file( info->folder_path() + "/" + path, writer, fail_message ); +} + +bool world::read_from_file( const std::string &path, file_read_fn reader, + bool optional ) const +{ + return ::read_from_file( info->folder_path() + "/" + path, reader, optional ); +} + +bool world::read_from_file_json( const std::string &path, file_read_json_fn reader, + bool optional ) const +{ + return ::read_from_file_json( info->folder_path() + "/" + path, reader, optional ); +} \ No newline at end of file diff --git a/src/world.h b/src/world.h new file mode 100644 index 000000000000..4081a77dca8a --- /dev/null +++ b/src/world.h @@ -0,0 +1,170 @@ +#pragma once +#ifndef CATA_SRC_WORLD_H +#define CATA_SRC_WORLD_H + +#include +#include +#include "json.h" +#include "options.h" +#include "type_id.h" +#include "coordinates.h" +#include "fstream_utils.h" + +class avatar; + +class save_t +{ + private: + std::string name; + + save_t( const std::string &name ); + + public: + std::string decoded_name() const; + std::string base_path() const; + + static save_t from_save_id( const std::string &save_id ); + static save_t from_base_path( const std::string &base_path ); + + bool operator==( const save_t &rhs ) const { + return name == rhs.name; + } + bool operator!=( const save_t &rhs ) const { + return !operator==( rhs ); + } + save_t( const save_t & ) = default; + save_t &operator=( const save_t & ) = default; +}; + +enum save_format : int { + /** Original save layout; uncompressed JSON as loose files */ + V1 = 0, +}; + +/** + * Structure containing metadata about a world. No actual world data is processed here. + * + * The actual instances are owned by the worldfactory class. All other classes should + * only have a pointer to one of these owned instances. + */ +struct WORLDINFO { + public: + /** + * @returns A path to a folder in the file system that should contain + * all the world specific files. It depends on @ref world_name, + * changing that will also change the result of this function. + */ + std::string folder_path() const; + + std::string world_name; + options_manager::options_container WORLD_OPTIONS; + std::vector world_saves; + save_format world_save_format; + + /** + * A (possibly empty) list of (idents of) mods that + * should be loaded for this world. + */ + std::vector active_mod_order; + + WORLDINFO(); + void COPY_WORLD( const WORLDINFO *world_to_copy ); + + bool needs_lua() const; + + bool save_exists( const save_t &name ) const; + void add_save( const save_t &name ); + + bool save( bool is_conversion = false ) const; + + void load_options( JsonIn &jsin ); + bool load_options(); + void load_legacy_options( std::istream &fin ); +}; + +class world +{ + public: + world( WORLDINFO *info ); + ~world(); + + WORLDINFO *info; + + /** + * Right now, each file write is independent of the others. Once we start + * migrating to SQLite, each save would happen in a single transaction. This + * gives two benefits: (1) atomicity, and (2) performance. + * + * When using the V1 non-sqlite save system, this merely records some metadata + * so we can print how long the save took. + */ + /**@{*/ + void start_save_tx(); + int64_t commit_save_tx(); + /**@}*/ + + /* + * Targeted/domain-specific file operations. Different save formats may choose to + * lay out files differently, so centralize file placement logic here rather than + * scattering it throughout the codebase. + */ + bool read_map_quad( const tripoint &om_addr, file_read_json_fn reader ) const; + bool write_map_quad( const tripoint &om_addr, file_write_fn writer ) const; + + bool overmap_exists( const point_abs_om &p ) const; + bool read_overmap( const point_abs_om &p, file_read_fn reader ) const; + bool read_overmap_player_visibility( const point_abs_om &p, file_read_fn reader ); + bool write_overmap( const point_abs_om &p, file_write_fn writer ) const; + bool write_overmap_player_visibility( const point_abs_om &p, file_write_fn writer ); + + bool read_player_mm_quad( const tripoint &p, file_read_json_fn reader ); + bool write_player_mm_quad( const tripoint &p, file_write_fn writer ); + + /* + * Player-specific file operations. Paths will be prefixed with the player's save ID. + */ + bool player_file_exist( const std::string &path ); + bool write_to_player_file( const std::string &path, file_write_fn writer, + const char *fail_message = nullptr ); + bool read_from_player_file( const std::string &path, file_read_fn reader, + bool optional = true ); + bool read_from_player_file_json( const std::string &path, file_read_json_fn reader, + bool optional = true ); + + /* + * Generic file operations, acting as a catch-all for miscellaneous save files + * living in the root of the world directory. + */ + bool assure_dir_exist( const std::string &path ) const; + bool file_exist( const std::string &path ) const; + + /** + * If fail_message is provided, this method will eat any exceptions and display a popup with the + * exception detail and the message. If fail_message is not provided, the exception will be + * propagated. + * + * To eat any exceptions and not display a popup, pass the empty string as fail_message. + * + * @param path The path to write to. + * @param writer The function that writes to the file. + * @param fail_message The message to display if the write fails. + * @return True if the write was successful, false otherwise. + */ + bool write_to_file( const std::string &path, file_write_fn writer, + const char *fail_message = nullptr ) const; + bool read_from_file( const std::string &path, file_read_fn reader, + bool optional = true ) const; + bool read_from_file_json( const std::string &path, file_read_json_fn reader, + bool optional = true ) const; + + private: + /** If non-zero, indicates we're in the middle of a save event */ + int64_t save_tx_start_ts = 0; + + std::string overmap_terrain_filename( const point_abs_om &p ) const; + std::string overmap_player_filename( const point_abs_om &p ) const; + std::string get_player_path() const; + +}; + +#endif // CATA_SRC_WORLD_H diff --git a/src/worldfactory.cpp b/src/worldfactory.cpp index 2eff91ba1f95..ae40414898dd 100644 --- a/src/worldfactory.cpp +++ b/src/worldfactory.cpp @@ -25,7 +25,6 @@ #include "input.h" #include "json.h" #include "mod_manager.h" -#include "name.h" #include "output.h" #include "path_info.h" #include "point.h" @@ -35,6 +34,7 @@ #include "string_utils.h" #include "translations.h" #include "ui_manager.h" +#include "name.h" using namespace std::placeholders; @@ -47,78 +47,6 @@ std::unique_ptr world_generator; */ static const int max_worldname_len = 32; -save_t::save_t( const std::string &name ): name( name ) {} - -std::string save_t::decoded_name() const -{ - return name; -} - -std::string save_t::base_path() const -{ - return base64_encode( name ); -} - -save_t save_t::from_save_id( const std::string &save_id ) -{ - return save_t( save_id ); -} - -save_t save_t::from_base_path( const std::string &base_path ) -{ - return save_t( base64_decode( base_path ) ); -} - -static std::string get_next_valid_worldname() -{ - std::string worldname = Name::get( nameIsWorldName ); - - return worldname; -} - -WORLD::WORLD() -{ - world_name = get_next_valid_worldname(); - WORLD_OPTIONS = get_options().get_world_defaults(); - - world_saves.clear(); - active_mod_order = world_generator->get_mod_manager().get_default_mods(); -} - -void WORLD::COPY_WORLD( const WORLD *world_to_copy ) -{ - world_name = world_to_copy->world_name + "_copy"; - WORLD_OPTIONS = world_to_copy->WORLD_OPTIONS; - active_mod_order = world_to_copy->active_mod_order; -} - -bool WORLD::needs_lua() const -{ - for( const mod_id &mod : active_mod_order ) { - if( mod.is_valid() && mod->lua_api_version ) { - return true; - } - } - return false; -} - -std::string WORLD::folder_path() const -{ - return PATH_INFO::savedir() + world_name; -} - -bool WORLD::save_exists( const save_t &name ) const -{ - return std::find( world_saves.begin(), world_saves.end(), name ) != world_saves.end(); -} - -void WORLD::add_save( const save_t &name ) -{ - if( !save_exists( name ) ) { - world_saves.push_back( name ); - } -} - worldfactory::worldfactory() : active_world( nullptr ) , mman_ui( *mman ) @@ -131,7 +59,7 @@ worldfactory::worldfactory() worldfactory::~worldfactory() = default; -WORLDPTR worldfactory::add_world( std::unique_ptr retworld ) +WORLDINFO *worldfactory::add_world( std::unique_ptr retworld ) { if( !retworld->save() ) { return nullptr; @@ -139,17 +67,17 @@ WORLDPTR worldfactory::add_world( std::unique_ptr retworld ) return ( all_worlds[ retworld->world_name ] = std::move( retworld ) ).get(); } -WORLDPTR worldfactory::make_new_world( const std::vector &mods ) +WORLDINFO *worldfactory::make_new_world( const std::vector &mods ) { - std::unique_ptr retworld = std::make_unique(); + std::unique_ptr retworld = std::make_unique(); retworld->active_mod_order = mods; return add_world( std::move( retworld ) ); } -WORLDPTR worldfactory::make_new_world( bool show_prompt, const std::string &world_to_copy ) +WORLDINFO *worldfactory::make_new_world( bool show_prompt, const std::string &world_to_copy ) { // World to return after generating - std::unique_ptr retworld = std::make_unique(); + std::unique_ptr retworld = std::make_unique(); if( !world_to_copy.empty() ) { retworld->COPY_WORLD( world_generator->get_world( world_to_copy ) ); @@ -191,7 +119,7 @@ WORLDPTR worldfactory::make_new_world( bool show_prompt, const std::string &worl return add_world( std::move( retworld ) ); } -WORLDPTR worldfactory::make_new_world( special_game_type special_type ) +WORLDINFO *worldfactory::make_new_world( special_game_type special_type ) { std::string worldname; switch( special_type ) { @@ -211,7 +139,7 @@ WORLDPTR worldfactory::make_new_world( special_game_type special_type ) return all_worlds[worldname].get(); } - std::unique_ptr special_world = std::make_unique(); + std::unique_ptr special_world = std::make_unique(); special_world->world_name = worldname; special_world->WORLD_OPTIONS["WORLD_END"].setValue( "delete" ); @@ -223,56 +151,20 @@ WORLDPTR worldfactory::make_new_world( special_game_type special_type ) return ( all_worlds[worldname] = std::move( special_world ) ).get(); } -void worldfactory::set_active_world( WORLDPTR world ) +void worldfactory::set_active_world( WORLDINFO *new_world ) { - world_generator->active_world = world; - if( world ) { - get_options().set_world_options( &world->WORLD_OPTIONS ); + DebugLog( DL::Info, DC::Main ) << "Setting active world to " << ( new_world ? + new_world->folder_path() : "NULL" ); + + if( new_world ) { + get_options().set_world_options( &new_world->WORLD_OPTIONS ); + active_world = std::make_unique( new_world ); } else { get_options().set_world_options( nullptr ); + active_world = nullptr; } } -bool WORLD::save( const bool is_conversion ) const -{ - if( !assure_dir_exist( folder_path() ) ) { - DebugLog( DL::Error, DC::Main ) << "Unable to create or open world[" << world_name - << "] directory for saving"; - return false; - } - - if( !is_conversion ) { - const auto savefile = folder_path() + "/" + PATH_INFO::worldoptions(); - const bool saved = write_to_file( savefile, [&]( std::ostream & fout ) { - JsonOut jout( fout ); - - jout.start_array(); - - for( auto &elem : WORLD_OPTIONS ) { - // Skip hidden option because it is set by mod and should not be saved - if( !elem.second.getDefaultText().empty() ) { - jout.start_object(); - - jout.member( "info", elem.second.getTooltip() ); - jout.member( "default", elem.second.getDefaultText( false ) ); - jout.member( "name", elem.first ); - jout.member( "value", elem.second.getValue( true ) ); - - jout.end_object(); - } - } - - jout.end_array(); - }, _( "world data" ) ); - if( !saved ) { - return false; - } - } - - world_generator->get_mod_manager().save_mods_list( const_cast( this ) ); - return true; -} - void worldfactory::init() { load_last_world_info(); @@ -301,9 +193,11 @@ void worldfactory::init() worldname = world_dir.substr( name_index + 1 ); // create and store the world - all_worlds[worldname] = std::make_unique(); + all_worlds[worldname] = std::make_unique(); // give the world a name all_worlds[worldname]->world_name = worldname; + // Record the world save format. Only one exists at this time. + all_worlds[worldname]->world_save_format = save_format::V1; // add sav files for( auto &world_sav_file : world_sav_files ) { all_worlds[worldname]->world_saves.push_back( save_t::from_base_path( world_sav_file ) ); @@ -321,9 +215,9 @@ void worldfactory::init() // check to see if there exists a worldname "save" which denotes that a world exists in the save // directory and not in a sub-world directory if( has_world( "save" ) ) { - const WORLD &old_world = *all_worlds["save"]; + const WORLDINFO &old_world = *all_worlds["save"]; - std::unique_ptr newworld = std::make_unique(); + std::unique_ptr newworld = std::make_unique(); newworld->world_name = get_next_valid_worldname(); // save world as conversion world @@ -362,7 +256,7 @@ std::vector worldfactory::all_worldnames() const return result; } -WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) +WORLDINFO *worldfactory::pick_world( bool show_prompt, bool empty_only ) { std::vector world_names = all_worldnames(); @@ -485,7 +379,7 @@ WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) wmove( w_worlds, point( 4, static_cast( i ) ) ); std::string world_name = ( world_pages[selpage] )[i]; - WORLDPTR world = get_world( world_name ); + WORLDINFO *world = get_world( world_name ); size_t saves_num = world->world_saves.size(); std::string text = string_format( "%s (%d)", world_name, saves_num ); @@ -575,7 +469,7 @@ WORLDPTR worldfactory::pick_world( bool show_prompt, bool empty_only ) } } while( world_pages[selpage].empty() ); } else if( action == "CONFIRM" ) { - WORLDPTR world = get_world( world_pages[selpage][sel] ); + WORLDINFO *world = get_world( world_pages[selpage][sel] ); if( !( world->needs_lua() && !cata::has_lua() ) ) { return world; } @@ -589,10 +483,9 @@ void worldfactory::remove_world( const std::string &worldname ) { auto it = all_worlds.find( worldname ); if( it != all_worlds.end() ) { - WORLDPTR wptr = it->second.get(); - if( active_world == wptr ) { - get_options().set_world_options( nullptr ); - active_world = nullptr; + WORLDINFO *wptr = it->second.get(); + if( active_world && active_world->info == wptr ) { + set_active_world( nullptr ); } all_worlds.erase( it ); } @@ -633,7 +526,14 @@ std::string worldfactory::pick_random_name() return get_next_valid_worldname(); } -int worldfactory::show_worldgen_tab_options( const catacurses::window &, WORLDPTR world, +std::string worldfactory::get_next_valid_worldname() +{ + std::string worldname = Name::get( nameIsWorldName ); + + return worldname; +} + +int worldfactory::show_worldgen_tab_options( const catacurses::window &, WORLDINFO *world, const std::function &on_quit ) { get_options().set_world_options( &world->WORLD_OPTIONS ); @@ -851,7 +751,7 @@ void worldfactory::show_active_world_mods( const std::vector &world_mods } } -void worldfactory::edit_active_world_mods( WORLDPTR world ) +void worldfactory::edit_active_world_mods( WORLDINFO *world ) { // set up window catacurses::window wf_win; @@ -884,7 +784,7 @@ void worldfactory::edit_active_world_mods( WORLDPTR world ) } } -int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, WORLDPTR world, +int worldfactory::show_worldgen_tab_modselection( const catacurses::window &win, WORLDINFO *world, const std::function &on_quit ) { return show_modselection_window( win, world->active_mod_order, on_quit, on_quit, false ); @@ -1357,7 +1257,7 @@ int worldfactory::show_modselection_window( const catacurses::window &win, return tab_output; } -int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORLDPTR world, +int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORLDINFO *world, const std::function &on_quit ) { catacurses::window w_confirmation; @@ -1599,107 +1499,13 @@ bool worldfactory::valid_worldname( const std::string &name, bool automated ) return false; } -void WORLD::load_options( JsonIn &jsin ) -{ - auto &opts = get_options(); - - jsin.start_array(); - while( !jsin.end_array() ) { - JsonObject jo = jsin.get_object(); - jo.allow_omitted_members(); - const std::string name = opts.migrateOptionName( jo.get_string( "name" ) ); - const std::string value = opts.migrateOptionValue( jo.get_string( "name" ), - jo.get_string( "value" ) ); - - if( opts.has_option( name ) && opts.get_option( name ).getPage() == "world_default" ) { - WORLD_OPTIONS[ name ].setValue( value ); - } - } -} - -void WORLD::load_legacy_options( std::istream &fin ) -{ - auto &opts = get_options(); - - //load legacy txt - std::string sLine; - while( !fin.eof() ) { - getline( fin, sLine ); - if( !sLine.empty() && sLine[0] != '#' && std::count( sLine.begin(), sLine.end(), ' ' ) == 1 ) { - size_t ipos = sLine.find( ' ' ); - // make sure that the option being loaded is part of the world_default page in OPTIONS - // In 0.C some lines consisted of a space and nothing else - const std::string name = opts.migrateOptionName( sLine.substr( 0, ipos ) ); - const std::string value = opts.migrateOptionValue( sLine.substr( 0, ipos ), sLine.substr( ipos + 1, - sLine.length() ) ); - - if( ipos != 0 && opts.get_option( name ).getPage() == "world_default" ) { - WORLD_OPTIONS[name].setValue( value ); - } - } - } -} - -bool WORLD::load_options() -{ - WORLD_OPTIONS = get_options().get_world_defaults(); - - using namespace std::placeholders; - const auto path = folder_path() + "/" + PATH_INFO::worldoptions(); - return read_from_file_optional_json( path, [&]( JsonIn & jsin ) { - load_options( jsin ); - } ); -} - -void load_world_option( const JsonObject &jo ) -{ - auto arr = jo.get_array( "options" ); - if( arr.empty() ) { - jo.throw_error( "no options specified", "options" ); - } - for( const std::string line : arr ) { - get_options().get_option( line ).setValue( "true" ); - } -} - -//load external option from json -void load_external_option( const JsonObject &jo ) -{ - auto name = jo.get_string( "name" ); - auto stype = jo.get_string( "stype" ); - options_manager &opts = get_options(); - if( !opts.has_option( name ) ) { - auto sinfo = jo.get_string( "info" ); - opts.add_external( name, "external_options", stype, sinfo, sinfo ); - } - options_manager::cOpt &opt = opts.get_option( name ); - if( stype == "float" ) { - opt.setValue( static_cast( jo.get_float( "value" ) ) ); - } else if( stype == "int" ) { - opt.setValue( jo.get_int( "value" ) ); - } else if( stype == "bool" ) { - if( jo.get_bool( "value" ) ) { - opt.setValue( "true" ); - } else { - opt.setValue( "false" ); - } - } else if( stype == "string" ) { - opt.setValue( jo.get_string( "value" ) ); - } else { - jo.throw_error( "Unknown or unsupported stype for external option", "stype" ); - } - // Just visit this member if it exists - if( jo.has_member( "info" ) ) { - jo.get_string( "info" ); - } -} mod_manager &worldfactory::get_mod_manager() { return *mman; } -WORLDPTR worldfactory::get_world( const std::string &name ) +WORLDINFO *worldfactory::get_world( const std::string &name ) { const auto iter = all_worlds.find( name ); if( iter == all_worlds.end() ) { @@ -1729,6 +1535,11 @@ static bool isForbidden( const std::string &candidate ) void worldfactory::delete_world( const std::string &worldname, const bool delete_folder ) { + // Disconnect to the database if we're somehow trying to delete the currently active world + if( active_world && active_world->info->world_name == worldname ) { + set_active_world( nullptr ); + } + std::string worldpath = get_world( worldname )->folder_path(); std::set directory_paths; diff --git a/src/worldfactory.h b/src/worldfactory.h index 5820db39bd69..d8cef9486538 100644 --- a/src/worldfactory.h +++ b/src/worldfactory.h @@ -13,6 +13,7 @@ #include "options.h" #include "pimpl.h" #include "type_id.h" +#include "world.h" enum class special_game_type; @@ -24,69 +25,11 @@ namespace catacurses class window; } // namespace catacurses -class save_t -{ - private: - std::string name; - - save_t( const std::string &name ); - - public: - std::string decoded_name() const; - std::string base_path() const; - - static save_t from_save_id( const std::string &save_id ); - static save_t from_base_path( const std::string &base_path ); - - bool operator==( const save_t &rhs ) const { - return name == rhs.name; - } - bool operator!=( const save_t &rhs ) const { - return !operator==( rhs ); - } - save_t( const save_t & ) = default; - save_t &operator=( const save_t & ) = default; -}; - -struct WORLD { - public: - /** - * @returns A path to a folder in the file system that should contain - * all the world specific files. It depends on @ref world_name, - * changing that will also change the result of this function. - */ - std::string folder_path() const; - - std::string world_name; - options_manager::options_container WORLD_OPTIONS; - std::vector world_saves; - /** - * A (possibly empty) list of (idents of) mods that - * should be loaded for this world. - */ - std::vector active_mod_order; - - WORLD(); - void COPY_WORLD( const WORLD *world_to_copy ); - - bool needs_lua() const; - - bool save_exists( const save_t &name ) const; - void add_save( const save_t &name ); - - bool save( bool is_conversion = false ) const; - - void load_options( JsonIn &jsin ); - bool load_options(); - void load_legacy_options( std::istream &fin ); -}; class mod_manager; class mod_ui; class input_context; -using WORLDPTR = WORLD *; - class worldfactory { public: @@ -94,23 +37,23 @@ class worldfactory ~worldfactory(); // Generate a world - WORLDPTR make_new_world( bool show_prompt = true, const std::string &world_to_copy = "" ); - WORLDPTR make_new_world( special_game_type special_type ); + WORLDINFO *make_new_world( bool show_prompt = true, const std::string &world_to_copy = "" ); + WORLDINFO *make_new_world( special_game_type special_type ); // Used for unit tests - does NOT verify if the mods can be loaded - WORLDPTR make_new_world( const std::vector &mods ); + WORLDINFO *make_new_world( const std::vector &mods ); // Returns the *existing* world of given name. - WORLDPTR get_world( const std::string &name ); + WORLDINFO *get_world( const std::string &name ); // Returns index for world name, 0 if world cannot be found. size_t get_world_index( const std::string &name ); bool has_world( const std::string &name ) const; - void set_active_world( WORLDPTR world ); + void set_active_world( WORLDINFO *world ); void init(); - WORLDPTR pick_world( bool show_prompt = true, bool empty_only = false ); + WORLDINFO *pick_world( bool show_prompt = true, bool empty_only = false ); - WORLDPTR active_world; + std::unique_ptr active_world; std::vector all_worldnames() const; @@ -123,6 +66,7 @@ class worldfactory void remove_world( const std::string &worldname ); bool valid_worldname( const std::string &name, bool automated = false ); + std::string get_next_valid_worldname(); /** * @param delete_folder If true: delete all the files and directories of the given @@ -133,19 +77,19 @@ class worldfactory static void draw_worldgen_tabs( const catacurses::window &w, size_t current ); void show_active_world_mods( const std::vector &world_mods ); - void edit_active_world_mods( WORLDPTR world ); + void edit_active_world_mods( WORLDINFO *world ); private: - std::map> all_worlds; + std::map> all_worlds; void load_last_world_info(); std::string pick_random_name(); - int show_worldgen_tab_options( const catacurses::window &win, WORLDPTR world, + int show_worldgen_tab_options( const catacurses::window &win, WORLDINFO *world, const std::function &on_quit ); - int show_worldgen_tab_modselection( const catacurses::window &win, WORLDPTR world, + int show_worldgen_tab_modselection( const catacurses::window &win, WORLDINFO *world, const std::function &on_quit ); - int show_worldgen_tab_confirm( const catacurses::window &win, WORLDPTR world, + int show_worldgen_tab_confirm( const catacurses::window &win, WORLDINFO *world, const std::function &on_quit ); int show_modselection_window( const catacurses::window &win, std::vector &active_mod_order, @@ -159,12 +103,12 @@ class worldfactory const std::vector &mods, bool is_active_list, const std::string &text_if_empty, const catacurses::window &w_shift ); - WORLDPTR add_world( std::unique_ptr retworld ); + WORLDINFO *add_world( std::unique_ptr retworld ); pimpl mman; pimpl mman_ui; - using worldgen_display = std::function )>; std::vector tabs; diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index c0fcdd317ed3..dc3f936ddb60 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -7,6 +7,7 @@ #include "string_formatter.h" #include "path_info.h" #include "game.h" +#include "world.h" #include "cata_utility.h" static std::string str_to_hex( const std::string &s ) @@ -31,7 +32,7 @@ static void filesystem_test_group( int serial, const std::string &s1, const std: CAPTURE( str_to_hex( s3 ) ); // Make sure there's no interference from e.g. uncleaned old runs - std::string base = g->get_world_base_save_path() + "/fs_test_" + + std::string base = g->get_active_world()->info->folder_path() + "/fs_test_" + get_pid_string() + "_" + std::to_string( serial ) + "/"; CAPTURE( base ); REQUIRE( !dir_exist( base ) ); diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 6c9d5477c9aa..1fc4cbcfdff9 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -147,7 +147,7 @@ static void init_global_game_state( const std::vector &mods, world_generator->set_active_world( nullptr ); world_generator->init(); - WORLDPTR test_world = world_generator->make_new_world( mods ); + WORLDINFO *test_world = world_generator->make_new_world( mods ); assert( test_world != nullptr ); world_generator->set_active_world( test_world ); assert( world_generator->active_world != nullptr ); @@ -156,7 +156,7 @@ static void init_global_game_state( const std::vector &mods, calendar::set_season_length( get_option( "SEASON_LENGTH" ) ); loading_ui ui( false ); - init::load_world_modfiles( ui, g->get_world_base_save_path() + "/" + SAVE_ARTIFACTS ); + init::load_world_modfiles( ui, g->get_active_world(), SAVE_ARTIFACTS ); g->u = avatar(); g->u.create( character_type::NOW ); @@ -352,7 +352,7 @@ int main( int argc, const char *argv[] ) clear_all_state(); - auto world_name = world_generator->active_world->world_name; + auto world_name = world_generator->active_world->info->world_name; if( result == 0 || dont_save ) { world_generator->delete_world( world_name, true ); } else {