diff --git a/src/mission_start.cpp b/src/mission_start.cpp index 0d218066feb4b..32c9fae8091b6 100644 --- a/src/mission_start.cpp +++ b/src/mission_start.cpp @@ -1733,8 +1733,21 @@ void reveal_any_target( mission *miss, const std::vector &omter_ids void mission_start::reveal_refugee_center( mission *miss ) { + const overmap_special_id os_evac_center( "evac_center" ); const tripoint your_pos = g->u.global_omt_location(); - const tripoint center_pos = overmap_buffer.find_closest( your_pos, "evac_center_18", 0, false ); + + // Try and find the special. + tripoint center_pos = overmap_buffer.find_closest( your_pos, "evac_center_18", OMAPX * 5, false, + false, true, os_evac_center ); + + // We couldn't find the special, so let's try and place it. + if( center_pos == overmap::invalid_tripoint ) { + const bool placed = overmap_buffer.place_special( os_evac_center, your_pos, OMAPX * 5 ); + if( placed ) { + center_pos = overmap_buffer.find_closest( your_pos, "evac_center_18", OMAPX * 5, false, + false, true, os_evac_center ); + } + } if( center_pos == overmap::invalid_tripoint ) { add_msg( _( "You don't know where the address could be..." ) ); diff --git a/src/overmap.cpp b/src/overmap.cpp index 5df96b0dbd8ee..e44fafc0ff67f 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -9,6 +9,7 @@ #include "line.h" #include "map.h" #include "map_iterator.h" +#include "mapbuffer.h" #include "mapgen.h" #include "mapgen_functions.h" #include "messages.h" @@ -2351,8 +2352,8 @@ void overmap::place_building( const tripoint &p, om_direction::type dir, const c for( size_t retries = 10; retries > 0; --retries ) { const overmap_special_id building_tid = pick_random_building_to_place( town_dist ); - if( can_place_special( *building_tid, building_pos, building_dir ) ) { - place_special( *building_tid, building_pos, building_dir, town ); + if( can_place_special( *building_tid, building_pos, building_dir, false ) ) { + place_special( *building_tid, building_pos, building_dir, town, false, false ); break; } } @@ -2773,7 +2774,7 @@ void overmap::place_rifts( const int z ) } pf::path overmap::lay_out_connection( const overmap_connection &connection, const point &source, - const point &dest, int z ) const + const point &dest, int z, const bool must_be_unexplored ) const { const auto estimate = [&]( const pf::node & cur, const pf::node * prev ) { const auto &id( get_ter( cur.x, cur.y, z ) ); @@ -2784,8 +2785,21 @@ pf::path overmap::lay_out_connection( const overmap_connection &connection, cons return pf::rejected; // No option for this terrain. } - const bool existing = connection.has( id ); - if( existing && id->is_rotatable() && + const bool existing_connection = connection.has( id ); + + // Only do this check if it needs to be unexplored and there isn't already a connection. + if( must_be_unexplored && !existing_connection ) { + // If this must be unexplored, check if we've already got a submap generated. + const bool existing_submap = is_omt_generated( tripoint( cur.x, cur.y, z ) ); + + // If there is an existing submap, this area has already been explored and this + // isn't a valid placement. + if( existing_submap ) { + return pf::rejected; + } + } + + if( existing_connection && id->is_rotatable() && !om_direction::are_parallel( id->get_dir(), static_cast( cur.dir ) ) ) { return pf::rejected; // Can't intersect. } @@ -2803,7 +2817,7 @@ pf::path overmap::lay_out_connection( const overmap_connection &connection, cons const int dy = dest.y - cur.y; const int dist = subtype->is_orthogonal() ? std::abs( dx ) + std::abs( dy ) : std::sqrt( dx * dx + dy * dy ); - const int existency_mult = existing ? 1 : 5; // Prefer existing connections. + const int existency_mult = existing_connection ? 1 : 5; // Prefer existing connections. return existency_mult * dist + subtype->basic_cost; }; @@ -2915,9 +2929,10 @@ void overmap::build_connection( const overmap_connection &connection, const pf:: } void overmap::build_connection( const point &source, const point &dest, int z, - const overmap_connection &connection ) + const overmap_connection &connection, const bool must_be_unexplored ) { - build_connection( connection, lay_out_connection( connection, source, dest, z ), z ); + build_connection( connection, lay_out_connection( connection, source, dest, z, must_be_unexplored ), + z ); } void overmap::connect_closest_points( const std::vector &points, int z, @@ -2937,7 +2952,7 @@ void overmap::connect_closest_points( const std::vector &points, int z, } } if( closest > 0 ) { - build_connection( points[i], points[k], z, connection ); + build_connection( points[i], points[k], z, connection, false ); } } } @@ -3211,7 +3226,7 @@ bool om_direction::are_parallel( type dir1, type dir2 ) } om_direction::type overmap::random_special_rotation( const overmap_special &special, - const tripoint &p ) const + const tripoint &p, const bool must_be_unexplored ) const { std::vector rotations( om_direction::size ); const auto first = rotations.begin(); @@ -3251,14 +3266,14 @@ om_direction::type overmap::random_special_rotation( const overmap_special &spec // Pick first valid rotation at random. std::random_shuffle( first, last ); const auto rotation = std::find_if( first, last, [&]( om_direction::type elem ) { - return can_place_special( special, p, elem ); + return can_place_special( special, p, elem, must_be_unexplored ); } ); return rotation != last ? *rotation : om_direction::type::invalid; } bool overmap::can_place_special( const overmap_special &special, const tripoint &p, - om_direction::type dir ) const + om_direction::type dir, const bool must_be_unexplored ) const { assert( p != invalid_tripoint ); assert( dir != om_direction::type::invalid ); @@ -3275,6 +3290,17 @@ bool overmap::can_place_special( const overmap_special &special, const tripoint return false; } + if( must_be_unexplored ) { + // If this must be unexplored, check if we've already got a submap generated. + const bool existing_submap = is_omt_generated( rp ); + + // If there is an existing submap, this area has already been explored and this + // isn't a valid placement. + if( existing_submap ) { + return false; + } + } + const oter_id tid = get_ter( rp ); if( rp.z == 0 ) { @@ -3287,11 +3313,13 @@ bool overmap::can_place_special( const overmap_special &special, const tripoint // checks around the selected point to see if the special can be placed there void overmap::place_special( const overmap_special &special, const tripoint &p, - om_direction::type dir, const city &cit ) + om_direction::type dir, const city &cit, const bool must_be_unexplored, const bool force ) { assert( p != invalid_tripoint ); assert( dir != om_direction::type::invalid ); - assert( can_place_special( special, p, dir ) ); + if( !force ) { + assert( can_place_special( special, p, dir, must_be_unexplored ) ); + } const bool blob = special.flags.count( "BLOB" ) > 0; @@ -3318,7 +3346,7 @@ void overmap::place_special( const overmap_special &special, const tripoint &p, for( const auto &elem : special.connections ) { if( elem.connection ) { const tripoint rp( p + om_direction::rotate( elem.p, dir ) ); - build_connection( cit.pos, point( rp.x, rp.y ), elem.p.z, *elem.connection ); + build_connection( cit.pos, point( rp.x, rp.y ), elem.p.z, *elem.connection, must_be_unexplored ); } } } @@ -3337,33 +3365,34 @@ void overmap::place_special( const overmap_special &special, const tripoint &p, // This basement isn't part of the special that we asserted we could place at // the top of this function, so we need to make sure we can place the basement // special before doing so. - if( can_place_special( *basement_tid, basement_p, dir ) ) { - place_special( *basement_tid, basement_p, dir, cit ); + if( can_place_special( *basement_tid, basement_p, dir, must_be_unexplored ) || force ) { + place_special( *basement_tid, basement_p, dir, cit, force, must_be_unexplored ); } } } -std::vector overmap::get_sectors() const +om_special_sectors get_sectors( const int sector_width ) { std::vector res; - res.reserve( ( OMAPX / OMSPEC_FREQ ) * ( OMAPY / OMSPEC_FREQ ) ); - for( int x = 0; x < OMAPX; x += OMSPEC_FREQ ) { - for( int y = 0; y < OMAPY; y += OMSPEC_FREQ ) { + res.reserve( ( OMAPX / sector_width ) * ( OMAPY / sector_width ) ); + for( int x = 0; x < OMAPX; x += sector_width ) { + for( int y = 0; y < OMAPY; y += sector_width ) { res.emplace_back( x, y ); } } std::random_shuffle( res.begin(), res.end() ); - return res; + return { res, sector_width }; } bool overmap::place_special_attempt( overmap_special_batch &enabled_specials, - const point §or, const bool place_optional ) + const point §or, const int sector_width, const bool place_optional, + const bool must_be_unexplored ) { const int x = sector.x; const int y = sector.y; - const tripoint p( rng( x, x + OMSPEC_FREQ - 1 ), rng( y, y + OMSPEC_FREQ - 1 ), 0 ); + const tripoint p( rng( x, x + sector_width - 1 ), rng( y, y + sector_width - 1 ), 0 ); const city &nearest_city = get_nearest_city( p ); std::random_shuffle( enabled_specials.begin(), enabled_specials.end() ); @@ -3379,12 +3408,12 @@ bool overmap::place_special_attempt( overmap_special_batch &enabled_specials, continue; } // See if we can actually place the special there. - const auto rotation = random_special_rotation( special, p ); + const auto rotation = random_special_rotation( special, p, must_be_unexplored ); if( rotation == om_direction::type::invalid ) { continue; } - place_special( special, p, rotation, nearest_city ); + place_special( special, p, rotation, nearest_city, false, must_be_unexplored ); if( ++iter->instances_placed >= special.occurrences.max ) { enabled_specials.erase( iter ); @@ -3397,17 +3426,18 @@ bool overmap::place_special_attempt( overmap_special_batch &enabled_specials, } void overmap::place_specials_pass( overmap_special_batch &enabled_specials, - std::vector §ors, const bool place_optional ) + om_special_sectors §ors, const bool place_optional, const bool must_be_unexplored ) { // Walk over sectors in random order, to minimize "clumping". - std::random_shuffle( sectors.begin(), sectors.end() ); - for( auto it = sectors.begin(); it != sectors.end(); ) { + std::random_shuffle( sectors.sectors.begin(), sectors.sectors.end() ); + for( auto it = sectors.sectors.begin(); it != sectors.sectors.end(); ) { const size_t attempts = 10; bool placed = false; for( size_t i = 0; i < attempts; ++i ) { - if( place_special_attempt( enabled_specials, *it, place_optional ) ) { + if( place_special_attempt( enabled_specials, *it, sectors.sector_width, place_optional, + must_be_unexplored ) ) { placed = true; - it = sectors.erase( it ); + it = sectors.sectors.erase( it ); if( enabled_specials.empty() ) { return; // Job done. Bail out. } @@ -3447,11 +3477,11 @@ void overmap::place_specials( overmap_special_batch &enabled_specials ) if( enabled_specials.empty() ) { return; } - std::vector sectors = get_sectors(); + om_special_sectors sectors = get_sectors( OMSPEC_FREQ ); // First, place the mandatory specials to ensure that all minimum instance // counts are met. - place_specials_pass( enabled_specials, sectors, false ); + place_specials_pass( enabled_specials, sectors, false, false ); // Snapshot remaining specials, which will be the optional specials and // any unplaced mandatory specials. By passing a copy into the creation of @@ -3494,7 +3524,7 @@ void overmap::place_specials( overmap_special_batch &enabled_specials ) } } // Then fill in non-mandatory specials. - place_specials_pass( enabled_specials, sectors, true ); + place_specials_pass( enabled_specials, sectors, true, false ); // Clean up... // Because we passed a copy of the specials for placement in adjacent overmaps rather than @@ -3778,6 +3808,22 @@ std::shared_ptr overmap::find_npc( const int id ) const return nullptr; } +bool overmap::is_omt_generated( const tripoint &loc ) const +{ + if( !inbounds( loc ) ) { + return false; + } + + // Location is local to this overmap, but we need global submap coordinates + // for the mapbuffer lookup. + tripoint global_sm_loc = omt_to_sm_copy( loc ) + om_to_sm_copy( tripoint( pos().x, pos().y, + loc.z ) ); + + const bool is_generated = MAPBUFFER.lookup_submap( global_sm_loc ) != nullptr; + + return is_generated; +} + overmap_special_id overmap_specials::create_building_from( const string_id &base ) { // @todo: Get rid of the hard-coded ids. diff --git a/src/overmap.h b/src/overmap.h index 6fbcde672014a..a4195a420f132 100644 --- a/src/overmap.h +++ b/src/overmap.h @@ -94,6 +94,11 @@ struct map_layer { std::vector notes; }; +struct om_special_sectors { + std::vector sectors; + int sector_width; +}; + // Wrapper around an overmap special to track progress of placing specials. struct overmap_special_placement { int instances_placed; @@ -216,6 +221,13 @@ class overmap */ std::vector find_notes( const int z, const std::string &text ); + /** + * Returns whether or not the location has been generated (e.g. mapgen has run). + * @param loc Location to check. + * @returns True if param @loc has been generated. + */ + bool is_omt_generated( const tripoint &loc ) const; + /** Returns the (0, 0) corner of the overmap in the global coordinates. */ point global_base_point() const; @@ -339,13 +351,13 @@ class overmap // Connection laying pf::path lay_out_connection( const overmap_connection &connection, const point &source, - const point &dest, int z ) const; + const point &dest, int z, const bool must_be_unexplored ) const; pf::path lay_out_street( const overmap_connection &connection, const point &source, om_direction::type dir, size_t len ) const; void build_connection( const overmap_connection &connection, const pf::path &path, int z ); void build_connection( const point &source, const point &dest, int z, - const overmap_connection &connection ); + const overmap_connection &connection, const bool must_be_unexplored ); void connect_closest_points( const std::vector &points, int z, const overmap_connection &connection ); // Polishing @@ -357,19 +369,14 @@ class overmap void polish_river(); void good_river( int x, int y, int z ); - // Returns a vector of permuted coordinates of overmap sectors. - // Each sector consists of 12x12 small maps. Coordinates of the sectors are in range [0, 15], [0, 15]. - // Check OMAPX, OMAPY, and OMSPEC_FREQ to learn actual values. - std::vector get_sectors() const; - om_direction::type random_special_rotation( const overmap_special &special, - const tripoint &p ) const; + const tripoint &p, bool must_be_unexplored ) const; bool can_place_special( const overmap_special &special, const tripoint &p, - om_direction::type dir ) const; + om_direction::type dir, const bool must_be_unexplored ) const; void place_special( const overmap_special &special, const tripoint &p, om_direction::type dir, - const city &cit ); + const city &cit, const bool must_be_unexplored, const bool force ); /** * Iterate over the overmap and place the quota of specials. * If the stated minimums are not reached, it will spawn a new nearby overmap @@ -384,7 +391,7 @@ class overmap * @param place_optional restricts attempting to place specials that have met their minimum count in the first pass. */ void place_specials_pass( overmap_special_batch &enabled_specials, - std::vector §ors, bool place_optional ); + om_special_sectors §ors, bool place_optional, const bool must_be_unexplored ); /** * Attempts to place specials within a sector. @@ -393,7 +400,7 @@ class overmap * @param place_optional restricts attempting to place specials that have met their minimum count in the first pass. */ bool place_special_attempt( overmap_special_batch &enabled_specials, - const point §or, bool place_optional ); + const point §or, const int sector_width, bool place_optional, const bool must_be_unexplored ); void place_mongroups(); void place_radios(); @@ -410,4 +417,10 @@ bool is_ot_type( const std::string &otype, const oter_id &oter ); // Matches any oter_id that contains the substring passed in, useful when oter can be a suffix, not just a prefix. bool is_ot_subtype( const char *otype, const oter_id &oter ); +/** +* Gets a collection of sectors and their width for usage in placing overmap specials. +* @param sector_width used to divide the OMAPX by OMAPY map into sectors. +*/ +om_special_sectors get_sectors( const int sector_width ); + #endif diff --git a/src/overmap_ui.cpp b/src/overmap_ui.cpp index a6a92c51a97b7..2665d4d549296 100644 --- a/src/overmap_ui.cpp +++ b/src/overmap_ui.cpp @@ -1151,10 +1151,9 @@ tripoint display( const tripoint &orig, const draw_data_t &data = draw_data_t() overmap_buffer.ter( curs ) = uistate.place_terrain->id.id(); overmap_buffer.set_seen( curs.x, curs.y, curs.z, true ); } else { + overmap_buffer.place_special( *uistate.place_special, curs, uistate.omedit_rotation, false, true ); for( const auto &s_ter : uistate.place_special->terrains ) { const tripoint pos = curs + om_direction::rotate( s_ter.p, uistate.omedit_rotation ); - - overmap_buffer.ter( pos ) = s_ter.terrain->get_rotated( uistate.omedit_rotation ); overmap_buffer.set_seen( pos.x, pos.y, pos.z, true ); } } diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index 08e2e915fc00a..394bd91904410 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -268,6 +268,15 @@ overmap &overmapbuffer::get_om_global( const tripoint &p ) return get( om_pos.x, om_pos.y ); } +overmap_with_local_coordinates overmapbuffer::get_om_global_with_coordinates( const tripoint &p ) +{ + int x = p.x; + int y = p.y; + const point om_pos = omt_to_om_remain( x, y ); + overmap *om = &get( om_pos.x, om_pos.y ); + return { om, tripoint( x, y, p.z ) }; +} + overmap *overmapbuffer::get_existing_om_global( int &x, int &y ) { const point om_pos = omt_to_om_remain( x, y ); @@ -286,6 +295,34 @@ overmap *overmapbuffer::get_existing_om_global( const tripoint &p ) return get_existing( om_pos.x, om_pos.y ); } +cata::optional +overmapbuffer::get_existing_om_global_with_coordinates( + const tripoint &p ) +{ + int x = p.x; + int y = p.y; + const point om_pos = omt_to_om_remain( x, y ); + overmap *om = get_existing( om_pos.x, om_pos.y ); + if( om == nullptr ) { + return cata::nullopt; + } + + return overmap_with_local_coordinates { om, tripoint( x, y, p.z ) }; +} + +bool overmapbuffer::is_omt_generated( const tripoint &loc ) +{ + cata::optional om = get_existing_om_global_with_coordinates( loc ); + + // If the overmap doesn't exist, then for sure the local mapgen + // hasn't happened. + if( !om ) { + return false; + } + + return om->overmap_pointer->is_omt_generated( om->coordinates ); +} + bool overmapbuffer::has_note( int x, int y, int z ) { const overmap *om = get_existing_om_global( x, y ); @@ -622,6 +659,39 @@ bool overmapbuffer::reveal_route( const tripoint &source, const tripoint &dest, return !path.nodes.empty(); } +bool overmapbuffer::check_ot_type_existing( const std::string &type, const tripoint &loc ) +{ + const cata::optional om = get_existing_om_global_with_coordinates( + loc ); + if( !om ) { + return false; + } + return om->overmap_pointer->check_ot_type( type, om->coordinates.x, om->coordinates.y, + om->coordinates.z ); +} + +bool overmapbuffer::check_ot_subtype_existing( const std::string &type, const tripoint &loc ) +{ + const cata::optional om = get_existing_om_global_with_coordinates( + loc ); + if( !om ) { + return false; + } + return om->overmap_pointer->check_ot_subtype( type, om->coordinates.x, om->coordinates.y, + om->coordinates.z ); +} + +bool overmapbuffer::check_overmap_special_type_existing( const overmap_special_id &id, + const tripoint &loc ) +{ + const cata::optional om = get_existing_om_global_with_coordinates( + loc ); + if( !om ) { + return false; + } + return om->overmap_pointer->check_overmap_special_type( id, om->coordinates ); +} + bool overmapbuffer::check_ot_type( const std::string &type, int x, int y, int z ) { overmap &om = get_om_global( x, y ); @@ -634,13 +704,65 @@ bool overmapbuffer::check_ot_subtype( const std::string &type, int x, int y, int return om.check_ot_subtype( type, x, y, z ); } +bool overmapbuffer::check_overmap_special_type( const overmap_special_id &id, const tripoint &loc ) +{ + const overmap_with_local_coordinates om = get_om_global_with_coordinates( loc ); + return om.overmap_pointer->check_overmap_special_type( id, om.coordinates ); +} + +bool overmapbuffer::is_findable_location( const tripoint &location, const std::string &type, + bool must_be_seen, bool allow_subtype_matches, + bool existing_overmaps_only, cata::optional om_special ) +{ + if( existing_overmaps_only ) { + const bool type_matches = allow_subtype_matches + ? check_ot_subtype_existing( type, location ) + : check_ot_type_existing( type, location ); + + if( !type_matches ) { + return false; + } + } else { + const bool type_matches = allow_subtype_matches + ? check_ot_subtype( type, location.x, location.y, location.z ) + : check_ot_type( type, location.x, location.y, location.z ); + + if( !type_matches ) { + return false; + } + } + + const bool meets_seen_criteria = !must_be_seen || seen( location.x, location.y, location.z ); + if( !meets_seen_criteria ) { + return false; + } + + if( existing_overmaps_only ) { + const bool meets_om_special_criteria = !om_special || + check_overmap_special_type_existing( *om_special, location ); + if( !meets_om_special_criteria ) { + return false; + } + } else { + const bool meets_om_special_criteria = !om_special || + check_overmap_special_type( *om_special, location ); + if( !meets_om_special_criteria ) { + return false; + } + } + + + return true; +} + tripoint overmapbuffer::find_closest( const tripoint &origin, const std::string &type, - const int radius, bool must_be_seen, bool allow_subtype_matches ) + int const radius, bool must_be_seen, bool allow_subtype_matches, + bool existing_overmaps_only, + cata::optional om_special ) { // Check the origin before searching adjacent tiles! - if( allow_subtype_matches - ? check_ot_subtype( type, origin.x, origin.y, origin.z ) - : check_ot_type( type, origin.x, origin.y, origin.z ) ) { + if( is_findable_location( origin, type, must_be_seen, allow_subtype_matches, existing_overmaps_only, + om_special ) ) { return origin; } @@ -668,47 +790,31 @@ tripoint overmapbuffer::find_closest( const tripoint &origin, const std::string // south is +y, north is -y for( int i = 0; i < dist * 2; i++ ) { //start at northwest, scan north edge - int x = origin.x - dist + i; - int y = origin.y - dist; - if( allow_subtype_matches - ? check_ot_subtype( type, x, y, z ) - : check_ot_type( type, x, y, z ) ) { - if( !must_be_seen || seen( x, y, z ) ) { - return tripoint( x, y, z ); - } + const tripoint n_loc( origin.x - dist + i, origin.y - dist, z ); + if( is_findable_location( n_loc, type, must_be_seen, allow_subtype_matches, existing_overmaps_only, + om_special ) ) { + return n_loc; } //start at southeast, scan south - x = origin.x + dist - i; - y = origin.y + dist; - if( allow_subtype_matches - ? check_ot_subtype( type, x, y, z ) - : check_ot_type( type, x, y, z ) ) { - if( !must_be_seen || seen( x, y, z ) ) { - return tripoint( x, y, z ); - } + const tripoint s_loc( origin.x + dist - i, origin.y + dist, z ); + if( is_findable_location( s_loc, type, must_be_seen, allow_subtype_matches, existing_overmaps_only, + om_special ) ) { + return s_loc; } //start at southwest, scan west - x = origin.x - dist; - y = origin.y + dist - i; - if( allow_subtype_matches - ? check_ot_subtype( type, x, y, z ) - : check_ot_type( type, x, y, z ) ) { - if( !must_be_seen || seen( x, y, z ) ) { - return tripoint( x, y, z ); - } + const tripoint w_loc( origin.x - dist, origin.y + dist - i, z ); + if( is_findable_location( w_loc, type, must_be_seen, allow_subtype_matches, existing_overmaps_only, + om_special ) ) { + return w_loc; } //start at northeast, scan east - x = origin.x + dist; - y = origin.y - dist + i; - if( allow_subtype_matches - ? check_ot_subtype( type, x, y, z ) - : check_ot_type( type, x, y, z ) ) { - if( !must_be_seen || seen( x, y, z ) ) { - return tripoint( x, y, z ); - } + const tripoint e_loc( origin.x + dist, origin.y - dist + i, z ); + if( is_findable_location( e_loc, type, must_be_seen, allow_subtype_matches, existing_overmaps_only, + om_special ) ) { + return e_loc; } } } @@ -716,18 +822,19 @@ tripoint overmapbuffer::find_closest( const tripoint &origin, const std::string } std::vector overmapbuffer::find_all( const tripoint &origin, const std::string &type, - int dist, bool must_be_seen ) + int dist, bool must_be_seen, bool allow_subtype_matches, + bool existing_overmaps_only, + cata::optional om_special ) { std::vector result; // dist == 0 means search a whole overmap diameter. dist = dist ? dist : OMAPX; for( int x = origin.x - dist; x <= origin.x + dist; x++ ) { for( int y = origin.y - dist; y <= origin.y + dist; y++ ) { - if( must_be_seen && !seen( x, y, origin.z ) ) { - continue; - } - if( check_ot_type( type, x, y, origin.z ) ) { - result.push_back( tripoint( x, y, origin.z ) ); + const tripoint search_loc( x, y, origin.z ); + if( is_findable_location( search_loc, type, must_be_seen, allow_subtype_matches, + existing_overmaps_only, om_special ) ) { + result.push_back( search_loc ); } } } @@ -735,9 +842,13 @@ std::vector overmapbuffer::find_all( const tripoint &origin, const std } tripoint overmapbuffer::find_random( const tripoint &origin, const std::string &type, - int dist, bool must_be_seen ) + int dist, bool must_be_seen, bool allow_subtype_matches, + bool existing_overmaps_only, + cata::optional om_special ) { - return random_entry( find_all( origin, type, dist, must_be_seen ), overmap::invalid_tripoint ); + return random_entry( find_all( origin, type, dist, must_be_seen, allow_subtype_matches, + existing_overmaps_only, + om_special ), overmap::invalid_tripoint ); } std::shared_ptr overmapbuffer::find_npc( int id ) @@ -1089,3 +1200,105 @@ bool overmapbuffer::is_safe( int x, int y, int z ) } return true; } + +bool overmapbuffer::place_special( const overmap_special &special, const tripoint &p, + om_direction::type dir, const bool must_be_unexplored, const bool force ) +{ + // get_om_global will transform these into overmap local coordinates, which + // we'll then use with the overmap methods. + int x = p.x; + int y = p.y; + overmap &om = get_om_global( x, y ); + const tripoint om_loc( x, y, p.z ); + + // Get the closest city that is within the overmap because + // all of the overmap generation functions only function within + // the single overmap. If future generation is hoisted up to the + // buffer to spawn overmaps, then this can also be changed accordingly. + const city c = om.get_nearest_city( om_loc ); + + bool placed = false; + // Only place this special if we can actually place it per its criteria, or we're forcing + // the placement, which is mostly a debug behavior, since a forced placement may not function + // correctly (e.g. won't check correct underlying terrain). + if( om.can_place_special( special, om_loc, dir, must_be_unexplored ) || force ) { + om.place_special( special, om_loc, dir, c, must_be_unexplored, force ); + placed = true; + } + return placed; +} + +bool overmapbuffer::place_special( const overmap_special_id special_id, const tripoint ¢er, + int radius ) +{ + // First find the requested special. If it doesn't exist, we're done here. + bool found = false; + overmap_special special; + for( const auto &elem : overmap_specials::get_all() ) { + if( elem.id == special_id ) { + special = elem; + found = true; + break; + } + } + if( !found ) { + return false; + } + + // Figure out the longest side of the special for purposes of determining our sector size + // when attempting placements. + const auto calculate_longest_side = [&special]() { + auto min_max_x = std::minmax_element( special.terrains.begin(), + special.terrains.end(), []( const overmap_special_terrain & lhs, + const overmap_special_terrain & rhs ) { + return lhs.p.x < rhs.p.x; + } ); + + auto min_max_y = std::minmax_element( special.terrains.begin(), + special.terrains.end(), []( const overmap_special_terrain & lhs, + const overmap_special_terrain & rhs ) { + return lhs.p.y < rhs.p.y; + } ); + + const int special_longest_side = std::max( min_max_x.second->p.x - min_max_x.first->p.x, + min_max_y.second->p.y - min_max_y.first->p.y ) + 1; + + // If our longest side is greater than the OMSPEC_FREQ, just use that instead. + return std::min( special_longest_side, OMSPEC_FREQ ); + }; + + const int longest_side = calculate_longest_side(); + + // Predefine our sectors to search in. + om_special_sectors sectors = get_sectors( longest_side ); + + // Get all of the overmaps within the defined radius of the center. + for( const auto &om : get_overmaps_near( omt_to_sm_copy( center ), omt_to_sm_copy( radius ) ) ) { + + // Build an overmap_special_batch for the special on this overmap. + std::vector specials; + specials.push_back( &special ); + overmap_special_batch batch( om->pos(), specials ); + + // Attempt to place the specials using our batch and sectors. We + // require they be placed in unexplored terrain right now. + om->place_specials_pass( batch, sectors, true, true ); + + // The place special pass will erase specials that have reached their + // maximum number of instances so first check if its been erased. + if( batch.empty() ) { + return true; + } + + // Hasn't been erased, so lets check placement counts. + for( const auto &special_placement : batch ) { + if( special_placement.instances_placed > 0 ) { + // It was placed, lets get outta here. + return true; + } + } + } + + // If we got this far, we've failed to make the placement. + return false; +} diff --git a/src/overmapbuffer.h b/src/overmapbuffer.h index 89f2b80eae03a..f96d3de304979 100644 --- a/src/overmapbuffer.h +++ b/src/overmapbuffer.h @@ -4,7 +4,9 @@ #include "enums.h" #include "int_id.h" +#include "omdata.h" #include "overmap_types.h" +#include "string_id.h" #include #include @@ -18,10 +20,7 @@ struct om_vehicle; struct oter_t; using oter_id = int_id; - class overmap; -class overmap_special; -class overmap_special_batch; struct radio_tower; struct regional_settings; class vehicle; @@ -54,6 +53,11 @@ struct city_reference { int get_distance_from_bounds() const; }; +struct overmap_with_local_coordinates { + overmap *overmap_pointer; + tripoint coordinates; +}; + class overmapbuffer { public: @@ -107,7 +111,6 @@ class overmapbuffer int get_horde_size( int x, int y, int z ); std::vector get_vehicle( int x, int y, int z ); const regional_settings &get_settings( int x, int y, int z ); - /** * Accessors for horde introspection into overmaps. * Probably also useful for NPC overmap-scale navigation. @@ -211,9 +214,18 @@ class overmapbuffer * If 0, OMAPX is used. * @param must_be_seen If true, only terrain seen by the player * should be searched. + * @param allow_subtype_matches If true, will allow matching on subtypes for the + * terrain type (e.g. forest will match forest_thick and forest_water). + * @param existing_overmaps_only If true, will restrict searches to existing overmaps only. This + * is particularly useful if we want to attempt to add a missing overmap special to an existing + * overmap rather than creating many overmaps in an attempt to find it. + * @param om_special If set, the terrain must be part of the specified + * overmap special. */ std::vector find_all( const tripoint &origin, const std::string &type, - int dist, bool must_be_seen ); + int dist, bool must_be_seen, bool allow_subtype_matches = false, + bool existing_overmaps_only = false, + cata::optional om_special = cata::nullopt ); /** * Returns a random point of specific terrain type among those found in certain search radius. @@ -225,9 +237,18 @@ class overmapbuffer * @param origin uses overmap terrain coordinates. * @param must_be_seen If true, only terrain seen by the player * should be searched. + * @param allow_subtype_matches If true, will allow matching on subtypes for the + * terrain type (e.g. forest will match forest_thick and forest_water). + * @param existing_overmaps_only If true, will restrict searches to existing overmaps only. This + * is particularly useful if we want to attempt to add a missing overmap special to an existing + * overmap rather than creating many overmaps in an attempt to find it. + * @param om_special If set, the terrain must be part of the specified + * overmap special. */ tripoint find_random( const tripoint &origin, const std::string &type, - int dist, bool must_be_seen ); + int dist, bool must_be_seen, bool allow_subtype_matches = false, + bool existing_overmaps_only = false, + cata::optional om_special = cata::nullopt ); /** * Mark a square area around center on Z-level z @@ -254,9 +275,18 @@ class overmapbuffer * @param origin uses overmap terrain coordinates. * @param must_be_seen If true, only terrain seen by the player * should be searched. + * @param allow_subtype_matches If true, will allow matching on subtypes for the + * terrain type (e.g. forest will match forest_thick and forest_water). + * @param existing_overmaps_only If true, will restrict searches to existing overmaps only. This + * is particularly useful if we want to attempt to add a missing overmap special to an existing + * overmap rather than creating many overmaps in an attempt to find it. + * @param om_special If set, the terrain must be part of the specified + * overmap special. */ tripoint find_closest( const tripoint &origin, const std::string &type, int radius, - bool must_be_seen, bool allow_subtype_matches = false ); + bool must_be_seen, bool allow_subtype_matches = false, + bool existing_overmaps_only = false, + cata::optional om_special = cata::nullopt ); /* These 4 functions return the overmap that contains the given * overmap terrain coordinate. @@ -273,6 +303,18 @@ class overmapbuffer overmap &get_om_global( int &x, int &y ); overmap &get_om_global( const point &p ); overmap &get_om_global( const tripoint &p ); + + /** + * These two functions return the overmap that contains the given + * global overmap terrain coordinate. They additionally will reproject + * and return the provided global overmap terrain coordinate to the + * local coordinate system, so that it can be used with that overmap. + * They follow the same semantics as the get_om_global and + * get_existing_om_global regarding creating new overmaps. + */ + overmap_with_local_coordinates get_om_global_with_coordinates( const tripoint &p ); + cata::optional get_existing_om_global_with_coordinates( + const tripoint &p ); /** * (x,y) are global overmap coordinates (same as @ref get). * @returns true if the buffer has a overmap with @@ -286,6 +328,12 @@ class overmapbuffer * (x,y) are global overmap coordinates (same as @ref get). */ overmap *get_existing( int x, int y ); + /** + * Returns whether or not the location has been generated (e.g. mapgen has run). + * @param loc is in world-global omt coordinates. + * @returns True if the location has been generated. + */ + bool is_omt_generated( const tripoint &loc ); typedef std::pair t_point_with_note; typedef std::vector t_notes_vector; @@ -357,7 +405,52 @@ class overmapbuffer std::string get_description_at( const tripoint &where ); + /** + * Place the specified overmap special directly on the map using the provided location and rotation. + * Intended to be used when you have a special in hand, the desired location and rotation are known + * and the special should be directly placed rather than using the overmap's placement algorithm. + * @param special The overmap special to place. + * @param location The location to place the overmap special. Absolute overmap terrain coordinates. + * @param dir The direction to rotate the overmap special before placement. + * @param must_be_unexplored If true, will require that all of the terrains where the special would be + * placed are unexplored. + * @param force If true, placement will bypass the checks for valid placement. + * @returns True if the special was placed, else false. + */ + bool place_special( const overmap_special &special, const tripoint &location, + om_direction::type dir, + const bool must_be_unexplored, const bool force ); + /** + * Place the specified overmap special using the overmap's placement algorithm. Intended to be used + * when you have a special that you want placed but it should be placed similarly to as if it were + * created during overmap generation. + * @param special_id The id of overmap special to place. + * @param center Used in conjunction with radius to search the specified and adjacent overmaps for + * a valid placement location. Absolute overmap terrain coordinates. + * @param radius Used in conjunction with center. Absolute overmap terrain units. + * @returns True if the special was placed, else false. + */ + bool place_special( const overmap_special_id special_id, const tripoint ¢er, int radius ); + private: + /** + * Common function used by the find_closest/all/random to determine if the location is + * findable based on the specified criteria. + * @param location The location to evaluate. Uses overmap terrain coordinates. + * @param type Type of terrain to look for + * @param must_be_seen If true, the terrain must have been seen by the player to be acceptable. + * @param allow_subtype_matches If true, will allow matching on subtypes for the terrain type + * (e.g. forest will match forest_thick and forest_water). + * @param existing_overmaps_only If true, will restrict searches to existing overmaps only. This + * is particularly useful if we want to attempt to add a missing overmap special to an existing + * overmap rather than creating many overmaps in an attempt to find it. + * @param om_special If set, the terrain must be part of the specified overmap special. + */ + bool is_findable_location( const tripoint &location, const std::string &type, bool must_be_seen, + bool allow_subtype_matches = false, + bool existing_overmaps_only = false, + cata::optional om_special = cata::nullopt ); + std::unordered_map< point, std::unique_ptr< overmap > > overmaps; /** * Set of overmap coordinates of overmaps that are known @@ -382,6 +475,15 @@ class overmapbuffer */ bool check_ot_type( const std::string &otype, int x, int y, int z ); bool check_ot_subtype( const std::string &otype, int x, int y, int z ); + bool check_overmap_special_type( const overmap_special_id &id, const tripoint &loc ); + + /** + * These versions of the check_* methods will only check existing overmaps, and + * return false if the overmap doesn't exist. They do not create new overmaps. + */ + bool check_ot_type_existing( const std::string &otype, const tripoint &loc ); + bool check_ot_subtype_existing( const std::string &otype, const tripoint &loc ); + bool check_overmap_special_type_existing( const overmap_special_id &id, const tripoint &loc ); private: /** * Go thorough the monster groups of the overmap and move out-of-bounds