diff --git a/doc/JSON_FLAGS.md b/doc/JSON_FLAGS.md index db062fa6af5be..dee031a8713b2 100644 --- a/doc/JSON_FLAGS.md +++ b/doc/JSON_FLAGS.md @@ -1169,6 +1169,7 @@ These branches are also the valid entries for the categories of `dreams` in `dre - ```SAFE_AT_WORLDGEN``` Location will not spawn overmap monster groups during worldgen (does not affect monsters spawned by mapgen). - ```TRIFFID``` Location is related to triffids. Used to classify location. - ```UNIQUE``` Location is unique and will only occur once per overmap. `occurrences` is overridden to define a percent chance (e.g. `"occurrences" : [75, 100]` is 75%) +- ```GLOBALLY_UNIQUE``` Location will only occur once per world. `occurrences` is overridden to define a percent chance (e.g. `"occurrences" : [75, 100]` is 75%) - ```URBAN``` - ```WILDERNESS``` - ```MAN_MADE``` - For location, created by human. For Innawood mod purposes diff --git a/doc/OVERMAP.md b/doc/OVERMAP.md index 4c7600de28a96..c581f77b2f90b 100644 --- a/doc/OVERMAP.md +++ b/doc/OVERMAP.md @@ -343,11 +343,12 @@ each normal special has a very high chance of being placed at least once per ove quirks of the code (most notably, the number of specials is only slightly more than the number of slots per overmap, specials that failed placement don't get disqualified and can be rolled for again, and placement iterates until all sectors are occupied). For specials that are not common enough to warrant appearing more -than once per overmap please use the "UNIQUE" flag. +than once per overmap please use the "UNIQUE" flag. For specials that should only have one instance +per world use "GLOBALLY_UNIQUE". -### Occurrences ( UNIQUE ) +### Occurrences ( UNIQUE, GLOBALLY_UNIQUE ) -When the special has the "UNIQUE" flag, instead of defining the minimum and maximum number placed +When the special has the "UNIQUE" or "GLOBALLY_UNIQUE" flag, instead of defining the minimum and maximum number placed. the occurrences field defines the chance of the special to be included in any one given overmap. Before any placement rolls, all specials with this flag have to succeed in an x_in_y (first value, second value) roll to be included in the `overmap_special_batch` for the currently generated overmap; diff --git a/src/overmap.cpp b/src/overmap.cpp index af1053c0d1319..3c126648687fe 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -614,9 +614,9 @@ void overmap_specials::check_consistency() const size_t actual_count = std::accumulate( specials.get_all().begin(), specials.get_all().end(), static_cast< size_t >( 0 ), []( size_t sum, const overmap_special & elem ) { - size_t min_occur = - static_cast( std::max( elem.get_constraints().occurrences.min, 0 ) ); - return sum + ( elem.has_flag( "UNIQUE" ) ? static_cast( 0 ) : min_occur ); + size_t min_occur = static_cast( std::max( elem.get_constraints().occurrences.min, 0 ) ); + const bool unique = elem.has_flag( "UNIQUE" ) || elem.has_flag( "GLOBALLY_UNIQUE" ); + return sum + ( unique ? 0 : min_occur ); } ); if( actual_count > max_count ) { @@ -5948,6 +5948,10 @@ bool overmap::can_place_special( const overmap_special &special, const tripoint_ if( !special.id ) { return false; } + if( special.has_flag( "GLOBALLY_UNIQUE" ) && + overmap_buffer.contains_unique_special( special.id ) ) { + return false; + } if( special.has_eoc() ) { dialogue d( get_talker_for( get_avatar() ), nullptr ); @@ -5992,6 +5996,9 @@ std::vector overmap::place_special( if( !force ) { cata_assert( can_place_special( special, p, dir, must_be_unexplored ) ); } + if( special.has_flag( "GLOBALLY_UNIQUE" ) ) { + overmap_buffer.add_unique_special( special.id ); + } const bool is_safe_zone = special.has_flag( "SAFE_AT_WORLDGEN" ); @@ -6140,13 +6147,15 @@ void overmap::place_specials( overmap_special_batch &enabled_specials ) continue; } - if( iter->special_details->has_flag( "UNIQUE" ) ) { - const overmap_special_placement_constraints &constraints = - iter->special_details->get_constraints(); + const bool unique = iter->special_details->has_flag( "UNIQUE" ); + const bool globally_unique = iter->special_details->has_flag( "GLOBALLY_UNIQUE" ); + if( unique || globally_unique ) { + const overmap_special_id &id = iter->special_details->id; + const overmap_special_placement_constraints &constraints = iter->special_details->get_constraints(); const int min = constraints.occurrences.min; const int max = constraints.occurrences.max; - if( x_in_y( min, max ) ) { + if( x_in_y( min, max ) && ( !globally_unique || !overmap_buffer.contains_unique_special( id ) ) ) { // Min and max are overloaded to be the chance of occurrence, // so reset instances placed to one short of max so we don't place several. iter->instances_placed = max - 1; diff --git a/src/overmapbuffer.cpp b/src/overmapbuffer.cpp index 9fe02e68ac542..9f72e86b89983 100644 --- a/src/overmapbuffer.cpp +++ b/src/overmapbuffer.cpp @@ -255,6 +255,7 @@ void overmapbuffer::clear() { overmaps.clear(); known_non_existing.clear(); + placed_unique_specials.clear(); last_requested_overmap = nullptr; } @@ -1086,6 +1087,19 @@ bool overmapbuffer::check_overmap_special_type( const overmap_special_id &id, return om_loc.om->check_overmap_special_type( id, om_loc.local ); } +void overmapbuffer::add_unique_special( const overmap_special_id &id ) +{ + if( contains_unique_special( id ) ) { + debugmsg( "Unique overmap special placed more than once: %s", id.str() ); + } + placed_unique_specials.emplace( id ); +} + +bool overmapbuffer::contains_unique_special( const overmap_special_id &id ) const +{ + return placed_unique_specials.find( id ) != placed_unique_specials.end(); +} + static omt_find_params assign_params( const std::string &type, int const radius, bool must_be_seen, ot_match_type match_type, bool existing_overmaps_only, diff --git a/src/overmapbuffer.h b/src/overmapbuffer.h index c4db0703d0820..2dd51e9ee8310 100644 --- a/src/overmapbuffer.h +++ b/src/overmapbuffer.h @@ -9,11 +9,13 @@ #include #include #include +#include #include #include #include "coordinates.h" #include "enums.h" +#include "json.h" #include "memory_fast.h" #include "omdata.h" #include "optional.h" @@ -535,6 +537,8 @@ class overmapbuffer mutable std::set known_non_existing; // Cached result of previous call to overmapbuffer::get_existing overmap mutable *last_requested_overmap; + // Set of globally unique overmap specials that have already been placed + std::unordered_set placed_unique_specials; /** * Get a list of notes in the (loaded) overmaps. @@ -569,6 +573,23 @@ class overmapbuffer const tripoint_abs_omt &loc ); bool check_overmap_special_type_existing( const overmap_special_id &id, const tripoint_abs_omt &loc ); + + /** + * Adds the given globally unique overmap special to the list of placed specials. + */ + void add_unique_special( const overmap_special_id &id ); + /** + * Returns true if the given globally unique overmap special has already been placed. + */ + bool contains_unique_special( const overmap_special_id &id ) const; + /** + * Writes the placed unique specials as a JSON value. + */ + void serialize_placed_unique_specials( JsonOut &json ) const; + /** + * Reads placed unique specials from JSON and overwrites the global value. + */ + void deserialize_placed_unique_specials( JsonIn &jsin ); private: /** * Go thorough the monster groups of the overmap and move out-of-bounds diff --git a/src/savegame.cpp b/src/savegame.cpp index aa975bd9cf6de..c3ae6f39bb1bf 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -31,6 +31,7 @@ #include "omdata.h" #include "options.h" #include "overmap.h" +#include "overmapbuffer.h" #include "overmap_types.h" #include "path_info.h" #include "regional_settings.h" @@ -1450,6 +1451,8 @@ void game::unserialize_master( std::istream &fin ) weather_manager::unserialize_all( jsin ); } else if( name == "timed_events" ) { timed_event_manager::unserialize_all( jsin ); + } else if( name == "placed_unique_specials" ) { + overmap_buffer.deserialize_placed_unique_specials( jsin ); } else { // silently ignore anything else jsin.skip_value(); @@ -1535,6 +1538,8 @@ void game::serialize_master( std::ostream &fout ) json.member( "active_missions" ); mission::serialize_all( json ); + json.member( "placed_unique_specials" ); + overmap_buffer.serialize_placed_unique_specials( json ); json.member( "timed_events" ); timed_event_manager::serialize_all( json ); @@ -1659,3 +1664,17 @@ void creature_tracker::serialize( JsonOut &jsout ) const } jsout.end_array(); } + +void overmapbuffer::serialize_placed_unique_specials( JsonOut &json ) const +{ + json.write_as_array( placed_unique_specials ); +} + +void overmapbuffer::deserialize_placed_unique_specials( JsonIn &jsin ) +{ + placed_unique_specials.clear(); + jsin.start_array(); + while( !jsin.end_array() ) { + placed_unique_specials.emplace( jsin.get_string() ); + } +}