diff --git a/data/json/effects_on_condition/example_eocs.json b/data/json/effects_on_condition/example_eocs.json index 79735c4c9ef35..3880a4e8cf101 100644 --- a/data/json/effects_on_condition/example_eocs.json +++ b/data/json/effects_on_condition/example_eocs.json @@ -522,5 +522,31 @@ { "math": [ "ITEM_POWER_PERC", "=", "n_val('power_percentage')" ] }, { "u_message": " / (%)" } ] + }, + { + "type": "effect_on_condition", + "id": "EOC_dimension_swap_test", + "//": "shifts the character to a new dimension", + "global": true, + "effect": [ + { + "set_string_var": "", + "string_input": { + "title": { "i18n": true, "str": "teleport to dimension named:" }, + "description": { "i18n": true, "str": "'default' returns you to main dimension" }, + "width": 30 + }, + "target_var": { "u_val": "destination_dimension" } + }, + { "u_location_variable": { "u_val": "tele_test" }, "x_adjust": 0, "y_adjust": 0 }, + { + "u_teleport": { "u_val": "tele_test" }, + "dimension_prefix": { "u_val": "destination_dimension" }, + "fail_message": "your body doesn't move", + "success_message": "This place feels different." + }, + { "u_message": { "u_val": "destination_dimension" } }, + { "if": { "u_in_dimension": "" }, "then": { "u_message": "home sweet home" } } + ] } ] diff --git a/doc/EFFECT_ON_CONDITION.md b/doc/EFFECT_ON_CONDITION.md index 13ff7e2d38832..fa48cc751e79b 100644 --- a/doc/EFFECT_ON_CONDITION.md +++ b/doc/EFFECT_ON_CONDITION.md @@ -3802,6 +3802,7 @@ You or NPC is teleported to `target_var` coordinates | "success_message" | optional | string or [variable object](#variable-object) | message, that would be printed, if teleportation was successful | | "fail_message" | optional | string or [variable object](#variable-object) | message, that would be printed, if teleportation was failed, like if coordinates contained creature or impassable obstacle (like wall) | | "force" | optional | boolean | default false; if true, teleportation can't fail - any creature, that stand on target coordinates, would be brutally telefragged, and if impassable obstacle occur, the closest point would be picked instead | +| "dimension_prefix" | optional | string | default ""; if a value is specified, will teleport the player to a dimension named after the prefix. Currently very WIP | ##### Valid talkers: diff --git a/src/condition.cpp b/src/condition.cpp index 599f6716d343f..8757348fef6a6 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -835,6 +835,14 @@ conditional_t::func f_is_wearing( const JsonObject &jo, std::string_view member, }; } +conditional_t::func f_in_dimension( const JsonObject &jo, std::string_view member ) +{ + str_or_var dimension_prefix = get_str_or_var( jo.get_member( member ), member, true ); + return [dimension_prefix]( dialogue const & d ) { + return ( g->get_dimension_prefix() == dimension_prefix.evaluate( d ) ); + }; +} + conditional_t::func f_has_item( const JsonObject &jo, std::string_view member, bool is_npc ) { str_or_var item_id = get_str_or_var( jo.get_member( member ), member, true ); @@ -2515,6 +2523,7 @@ parsers = { {"u_has_perception", "npc_has_perception", jarg::member | jarg::array, &conditional_fun::f_has_perception }, {"u_has_part_temp", "npc_has_part_temp", jarg::member | jarg::array, &conditional_fun::f_has_part_temp }, {"u_is_wearing", "npc_is_wearing", jarg::member, &conditional_fun::f_is_wearing }, + {"u_in_dimension", jarg::member, &conditional_fun::f_in_dimension }, {"u_has_item", "npc_has_item", jarg::member, &conditional_fun::f_has_item }, {"u_has_item_with_flag", "npc_has_item_with_flag", jarg::member, &conditional_fun::f_has_item_with_flag }, {"u_has_items", "npc_has_items", jarg::member, &conditional_fun::f_has_items }, diff --git a/src/do_turn.cpp b/src/do_turn.cpp index 3cb24f84e8bc8..15ce3a6990c5b 100644 --- a/src/do_turn.cpp +++ b/src/do_turn.cpp @@ -460,7 +460,10 @@ bool do_turn() g->gamemode->per_turn(); calendar::turn += 1_turns; } - + //used for dimension swapping + if( g->swapping_dimensions ) { + g->swapping_dimensions = false; + } play_music( music::get_music_id_string() ); // starting a new turn, clear out temperature cache diff --git a/src/game.cpp b/src/game.cpp index e932ff268b8f7..cc582720b2922 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5379,7 +5379,7 @@ bool game::revive_corpse( const tripoint &p, item &it, int radius ) } // If this is not here, the game may attempt to spawn a monster before the map exists, // leading to it querying for furniture, and crashing. - if( g->new_game ) { + if( g->new_game || g->swapping_dimensions ) { return false; } if( it.has_flag( flag_FIELD_DRESS ) || it.has_flag( flag_FIELD_DRESS_FAILED ) || @@ -12325,6 +12325,55 @@ void game::vertical_move( int movez, bool force, bool peeking ) cata_event_dispatch::avatar_moves( old_abs_pos.raw(), u, m ); } +bool game::travel_to_dimension( const std::string &new_prefix ) +{ + map &here = get_map(); + avatar &player = get_avatar(); + unload_npcs(); + for( monster &critter : all_monsters() ) { + despawn_monster( critter ); + } + if( player.in_vehicle ) { + here.unboard_vehicle( player.pos_bub() ); + } + // Make sure we don't mess up savedata if for some reason maps can't be saved + if( !save_maps() ) { + return false; + } + for( int z = -OVERMAP_DEPTH; z <= OVERMAP_HEIGHT; z++ ) { + here.clear_vehicle_list( z ); + } + here.rebuild_vehicle_level_caches(); + // Inputting an empty string to the text input EOC fails + // so i'm using 'default' as empty/main dimension + if( new_prefix != "default" ) { + dimension_prefix = new_prefix; + } else { + dimension_prefix.clear(); + } + MAPBUFFER.clear(); + // FIXME hack to prevent crashes from temperature checks + // This returns to false in 'on_turn()' so it should be fine? + swapping_dimensions = true; + // In theory if we skipped the next two lines we'd have an exact copy of the overmap + // from the past dimension, only with differences noticeable in the local map. + overmap_buffer.clear(); + // load/create new overmap + overmap_buffer.get( point_abs_om{} ); + // Loads submaps and invalidate related caches + here.load( tripoint_abs_sm( here.get_abs_sub() ), false ); + here.access_cache( here.get_abs_sub().z() ).map_memory_cache_dec.reset(); + here.access_cache( here.get_abs_sub().z() ).map_memory_cache_ter.reset(); + here.invalidate_visibility_cache(); + load_npcs(); + // Handle static monsters + here.spawn_monsters( true, true ); + update_overmap_seen(); + // Update weather now as it could be different on the new location + get_weather().nextweather = calendar::turn; + return true; +} + void game::start_hauling( const tripoint &pos ) { std::vector candidate_items = m.get_haulable_items( pos ); @@ -13291,6 +13340,20 @@ cata_path PATH_INFO::world_base_save_path_path() return world_generator->active_world->folder_path_path(); } +cata_path PATH_INFO::current_dimension_save_path_path() +{ + std::string dimension_prefix = g->get_dimension_prefix(); + if( !dimension_prefix.empty() ) { + return PATH_INFO::world_base_save_path_path() / "dimensions" / dimension_prefix; + } + return PATH_INFO::world_base_save_path_path(); +} + +cata_path PATH_INFO::current_dimension_player_save_path_path() +{ + return PATH_INFO::current_dimension_save_path_path() / base64_encode( get_avatar().get_save_id() ); +} + void game::shift_destination_preview( const point &delta ) { for( tripoint_bub_ms &p : destination_preview ) { diff --git a/src/game.h b/src/game.h index 77e7af867e190..ca5a311ad1b99 100644 --- a/src/game.h +++ b/src/game.h @@ -289,6 +289,18 @@ class game * If peeking == true, forbids some exotic movement options */ void vertical_move( int z, bool force, bool peeking = false ); + /** + * Moves the player to an alternate dimension. + * The prefix identifies the dimension and its properties. + */ + bool travel_to_dimension( const std::string &prefix ); + /** + * Retrieve the identifier of the current dimension. + * TODO: this should be a dereferencable id that gives properties of the dimension. + */ + std::string get_dimension_prefix() { + return dimension_prefix; + } void start_hauling( const tripoint &pos ); /** Returns the other end of the stairs (if any). May query, affect u etc. * @param pos Disable queries and msgs if not the same position as player. @@ -1314,6 +1326,12 @@ class game const tripoint &examp, climbing_aid_id aid, bool deploy_affordance = false ); + //currently used as a hacky workaround for dimension swapping + bool swapping_dimensions = false; // NOLINT (cata-serialize) + private: + // Stores the currently occupoed dimension. + // TODO: should be an id instead of a string. + std::string dimension_prefix; }; // Returns temperature modifier from direct heat radiation of nearby sources diff --git a/src/item.cpp b/src/item.cpp index 7cf6f0f17402f..23a03d47d9e89 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -12845,7 +12845,7 @@ bool item::process_temperature_rot( float insulation, const tripoint &pos, map & units::temperature_delta temp_mod; // Toilets and vending machines will try to get the heat radiation and convection during mapgen and segfault. - if( !g->new_game ) { + if( !g->new_game && !g->swapping_dimensions ) { temp_mod = get_heat_radiation( pos ); temp_mod += get_convection_temperature( pos ); temp_mod += here.get_temperature_mod( pos ); diff --git a/src/map_memory.cpp b/src/map_memory.cpp index da2ec7ae6ae7b..e95b7c648be1a 100644 --- a/src/map_memory.cpp +++ b/src/map_memory.cpp @@ -19,7 +19,7 @@ static constexpr int MM_SIZE = MAPSIZE * 2; static cata_path find_mm_dir() { - return PATH_INFO::player_base_save_path_path() + ".mm1"; + return PATH_INFO::current_dimension_player_save_path_path() + ".mm1"; } static cata_path find_region_path( const cata_path &dirname, const tripoint &p ) diff --git a/src/mapbuffer.cpp b/src/mapbuffer.cpp index 0e73c539ceb90..7ebaaa7538d96 100644 --- a/src/mapbuffer.cpp +++ b/src/mapbuffer.cpp @@ -28,8 +28,6 @@ class game; // NOLINTNEXTLINE(cata-static-declarations) -extern std::unique_ptr g; -// NOLINTNEXTLINE(cata-static-declarations) extern const int savegame_version; static cata_path find_quad_path( const cata_path &dirname, const tripoint_abs_omt &om_addr ) @@ -40,9 +38,10 @@ static cata_path find_quad_path( const cata_path &dirname, const tripoint_abs_om static cata_path find_dirname( const tripoint_abs_omt &om_addr ) { const tripoint_abs_seg segment_addr = project_to( om_addr ); - return PATH_INFO::world_base_save_path_path() / "maps" / string_format( "%d.%d.%d", - segment_addr.x(), - segment_addr.y(), segment_addr.z() ); + std::string segment = string_format( "%d.%d.%d", + segment_addr.x(), + segment_addr.y(), segment_addr.z() ); + return PATH_INFO::current_dimension_save_path_path() / "maps" / segment; } mapbuffer MAPBUFFER; @@ -136,8 +135,7 @@ bool mapbuffer::submap_exists( const tripoint_abs_sm &p ) void mapbuffer::save( bool delete_after_save ) { - assure_dir_exist( PATH_INFO::world_base_save_path() + "/maps" ); - + assure_dir_exist( PATH_INFO::current_dimension_save_path_path() / "maps" ); int num_saved_submaps = 0; int num_total_submaps = submaps.size(); diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 7b931023b4e75..c19785d9c38a9 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -6715,13 +6715,28 @@ talk_effect_fun_t::func f_teleport( const JsonObject &jo, std::string_view membe } else { success_message.str_val = translation(); } + str_or_var dimension_prefix; + if( jo.has_member( "dimension_prefix" ) ) { + dimension_prefix = get_str_or_var( jo.get_member( "dimension_prefix" ), + "dimension_prefix", false, "" ); + } else { + dimension_prefix.str_val = ""; + } bool force = jo.get_bool( "force", false ); - return [is_npc, target_var, fail_message, success_message, force]( dialogue const & d ) { + return [is_npc, target_var, fail_message, success_message, force, + dimension_prefix]( dialogue const & d ) { tripoint_abs_ms target_pos = get_tripoint_from_var( target_var, d, is_npc ); Creature *teleporter = d.actor( is_npc )->get_creature(); if( teleporter ) { + std::string prefix = dimension_prefix.evaluate( d ); + bool successful_dimension_swap = false; + // Make sure we don't cause a dimension swap on every + // short/long range teleport outside the default dimension + if( !prefix.empty() && prefix != g->get_dimension_prefix() ) { + successful_dimension_swap = g->travel_to_dimension( prefix ); + } if( teleport::teleport_to_point( *teleporter, get_map().bub_from_abs( target_pos ), true, false, - false, force ) ) { + false, force ) || successful_dimension_swap ) { teleporter->add_msg_if_player( success_message.evaluate( d ) ); } else { teleporter->add_msg_if_player( fail_message.evaluate( d ) ); diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index 244ee6b1c13a9..4ea670a67400b 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -71,7 +71,7 @@ int camp_reference::get_distance_from_bounds() const cata_path overmapbuffer::terrain_filename( const point_abs_om &p ) { - return PATH_INFO::world_base_save_path_path() / string_format( "o.%d.%d", p.x(), p.y() ); + return PATH_INFO::current_dimension_save_path_path() / string_format( "o.%d.%d", p.x(), p.y() ); } cata_path overmapbuffer::player_filename( const point_abs_om &p ) @@ -305,6 +305,7 @@ overmap *overmapbuffer::get_existing( const point_abs_om &p ) // checked in a previous call of this function). return nullptr; } + assure_dir_exist( PATH_INFO::current_dimension_save_path_path() ); if( file_exist( terrain_filename( p ) ) ) { // File exists, load it normally (the get function // indirectly call overmap::open to do so). diff --git a/src/path_info.h b/src/path_info.h index e74bfbd0376f8..784844452a20a 100644 --- a/src/path_info.h +++ b/src/path_info.h @@ -96,6 +96,8 @@ cata_path user_keybindings(); cata_path user_moddir_path(); cata_path user_sound(); cata_path world_base_save_path_path(); +cata_path current_dimension_save_path_path(); +cata_path current_dimension_player_save_path_path(); void set_datadir( const std::string &datadir ); void set_config_dir( const std::string &config_dir ); diff --git a/src/savegame.cpp b/src/savegame.cpp index 58cdcfda99d49..2933e1c265ee4 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -69,7 +69,7 @@ extern std::map> quick_shortcuts_map; * Changes that break backwards compatibility should bump this number, so the game can * load a legacy format loader. */ -const int savegame_version = 34; +const int savegame_version = 35; /* * This is a global set by detected version header in .sav, maps.txt, or overmap. @@ -98,6 +98,7 @@ void game::serialize( std::ostream &fout ) json.member( "calendar_start", calendar::start_of_cataclysm ); json.member( "game_start", calendar::start_of_game ); json.member( "initial_season", static_cast( calendar::initial_season ) ); + json.member( "dimension_prefix", g->get_dimension_prefix() ); json.member( "auto_travel_mode", auto_travel_mode ); json.member( "run_mode", static_cast( safe_mode ) ); json.member( "mostseen", mostseen ); @@ -220,6 +221,10 @@ void game::unserialize( std::istream &fin, const cata_path &path ) calendar::initial_season = static_cast( data.get_int( "initial_season", static_cast( SPRING ) ) ); + std::string loaded_dimension_prefix; + if( data.read( "dimension_prefix", loaded_dimension_prefix ) ) { + dimension_prefix = loaded_dimension_prefix; + } data.read( "auto_travel_mode", auto_travel_mode ); data.read( "run_mode", tmprun ); data.read( "mostseen", mostseen ); diff --git a/src/weather.cpp b/src/weather.cpp index 968bb94b776da..7b1345dfaaa6d 100644 --- a/src/weather.cpp +++ b/src/weather.cpp @@ -965,7 +965,7 @@ units::temperature weather_manager::get_temperature( const tripoint &location ) //underground temperature = average New England temperature = 43F/6C units::temperature temp = location.z < 0 ? AVERAGE_ANNUAL_TEMPERATURE : temperature; - if( !g->new_game ) { + if( !g->new_game && !g->swapping_dimensions ) { units::temperature_delta temp_mod; temp_mod = get_heat_radiation( location ); temp_mod += get_convection_temperature( location ); diff --git a/tests/submap_load_test.cpp b/tests/submap_load_test.cpp index 5bdb5be306a89..e426a84dc9278 100644 --- a/tests/submap_load_test.cpp +++ b/tests/submap_load_test.cpp @@ -851,7 +851,7 @@ static JsonValue submap_fd_pre_migration = json_loader::from_string( submap_fd_p static void load_from_jsin( submap &sm, const JsonValue &jsin ) { // Ensure that the JSON is up to date for our savegame version - REQUIRE( savegame_version == 34 ); + REQUIRE( savegame_version == 35 ); int version = 0; JsonObject sm_json = jsin.get_object(); if( sm_json.has_member( "version" ) ) {