From 7656b1d42b403cd26146167ceeda8919381a59f3 Mon Sep 17 00:00:00 2001 From: Mark Langsdorf Date: Tue, 30 Apr 2019 19:03:37 -0500 Subject: [PATCH 1/5] NPCs: move the lobby beggars into their own faction The lobby beggars need to have different behavior than other people in the Evac Center, and should be in their own faction. --- data/json/npcs/factions.json | 19 +++++++++++++++++++ data/json/npcs/npc.json | 10 +++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/data/json/npcs/factions.json b/data/json/npcs/factions.json index 10ae2317afbda..75bf371eb1e97 100644 --- a/data/json/npcs/factions.json +++ b/data/json/npcs/factions.json @@ -56,6 +56,25 @@ "cult": 0, "description": "A conglomeration of entrepreneurs and businessmen that stand together to hammer-out an existence through trade and industry." }, + { + "type": "faction", + "id": "lobby_beggars", + "name": "The Beggars in the Lobby", + "likes_u": 0, + "respects_u": 0, + "known_by_u": false, + "size": 5, + "power": 0, + "combat_ability": 0, + "food_supply": 1152, + "wealth": 100, + "good": 1, + "strength": 0, + "sneak": 0, + "crime": -1, + "cult": 0, + "description": "A collection of mentally and physically disadvantaged survivors who beg for food in the Evac Center lobby." + }, { "type": "faction", "id": "tacoma_commune", diff --git a/data/json/npcs/npc.json b/data/json/npcs/npc.json index 513e0845d54da..77823694baa63 100644 --- a/data/json/npcs/npc.json +++ b/data/json/npcs/npc.json @@ -421,7 +421,7 @@ "attitude": 0, "mission": 7, "chat": "TALK_REFUGEE_BEGGAR_1", - "faction": "wasteland_scavengers" + "faction": "lobby_beggars" }, { "type": "npc", @@ -434,7 +434,7 @@ "attitude": 0, "mission": 7, "chat": "TALK_REFUGEE_BEGGAR_2", - "faction": "wasteland_scavengers" + "faction": "lobby_beggars" }, { "type": "npc", @@ -447,7 +447,7 @@ "attitude": 0, "mission": 7, "chat": "TALK_REFUGEE_BEGGAR_3", - "faction": "wasteland_scavengers" + "faction": "lobby_beggars" }, { "type": "npc", @@ -460,7 +460,7 @@ "attitude": 0, "mission": 7, "chat": "TALK_REFUGEE_BEGGAR_4", - "faction": "wasteland_scavengers" + "faction": "lobby_beggars" }, { "type": "npc", @@ -473,7 +473,7 @@ "attitude": 0, "mission": 7, "chat": "TALK_REFUGEE_BEGGAR_5", - "faction": "wasteland_scavengers" + "faction": "lobby_beggars" }, { "type": "npc", From d007dca160259522ae4b36d806d85501b1ecd1ce Mon Sep 17 00:00:00 2001 From: Mark Langsdorf Date: Wed, 1 May 2019 08:45:46 -0500 Subject: [PATCH 2/5] zones: tag zones by the faction that created them Zones can be tagged by faction ID (defaulting to your_followers) and different factions cannot retrieve each other's zones. --- src/clzones.cpp | 97 +++++++++++++++++++++++++++++++------------------ src/clzones.h | 61 +++++++++++++++++++++++-------- src/game.cpp | 70 +++++++++++++++++++---------------- 3 files changed, 146 insertions(+), 82 deletions(-) diff --git a/src/clzones.cpp b/src/clzones.cpp index c876bfc664de0..d8294ad5222f8 100644 --- a/src/clzones.cpp +++ b/src/clzones.cpp @@ -375,9 +375,9 @@ bool zone_manager::has_type( const zone_type_id &type ) const return types.count( type ) > 0; } -bool zone_manager::has_defined( const zone_type_id &type ) const +bool zone_manager::has_defined( const zone_type_id &type, const faction_id &fac ) const { - const auto &type_iter = area_cache.find( type ); + const auto &type_iter = area_cache.find( zone_data::make_type_hash( type, fac ) ); return type_iter != area_cache.end(); } @@ -390,8 +390,8 @@ void zone_manager::cache_data() continue; } - const zone_type_id &type = elem.get_type(); - auto &cache = area_cache[type]; + const std::string &type_hash = elem.get_type_hash(); + auto &cache = area_cache[type_hash]; tripoint start = elem.get_start_point(); tripoint end = elem.get_end_point(); @@ -416,8 +416,8 @@ void zone_manager::cache_vzones() continue; } - const zone_type_id &type = elem->get_type(); - auto &cache = vzone_cache[type]; + const std::string &type_hash = elem->get_type_hash(); + auto &cache = area_cache[type_hash]; tripoint start = elem->get_start_point(); tripoint end = elem->get_end_point(); @@ -433,9 +433,10 @@ void zone_manager::cache_vzones() } } -std::unordered_set zone_manager::get_point_set( const zone_type_id &type ) const +std::unordered_set zone_manager::get_point_set( const zone_type_id &type, + const faction_id &fac ) const { - const auto &type_iter = area_cache.find( type ); + const auto &type_iter = area_cache.find( zone_data::make_type_hash( type, fac ) ); if( type_iter == area_cache.end() ) { return std::unordered_set(); } @@ -443,10 +444,11 @@ std::unordered_set zone_manager::get_point_set( const zone_type_id &ty return type_iter->second; } -std::unordered_set zone_manager::get_vzone_set( const zone_type_id &type ) const +std::unordered_set zone_manager::get_vzone_set( const zone_type_id &type, + const faction_id &fac ) const { //Only regenerate the vehicle zone cache if any vehicles have moved - const auto &type_iter = vzone_cache.find( type ); + const auto &type_iter = vzone_cache.find( zone_data::make_type_hash( type, fac ) ); if( type_iter == vzone_cache.end() ) { return std::unordered_set(); } @@ -454,16 +456,18 @@ std::unordered_set zone_manager::get_vzone_set( const zone_type_id &ty return type_iter->second; } -bool zone_manager::has( const zone_type_id &type, const tripoint &where ) const +bool zone_manager::has( const zone_type_id &type, const tripoint &where, + const faction_id &fac ) const { - const auto &point_set = get_point_set( type ); - const auto &vzone_set = get_vzone_set( type ); + const auto &point_set = get_point_set( type, fac ); + const auto &vzone_set = get_vzone_set( type, fac ); return point_set.find( where ) != point_set.end() || vzone_set.find( where ) != vzone_set.end(); } -bool zone_manager::has_near( const zone_type_id &type, const tripoint &where, int range ) const +bool zone_manager::has_near( const zone_type_id &type, const tripoint &where, int range, + const faction_id &fac ) const { - const auto &point_set = get_point_set( type ); + const auto &point_set = get_point_set( type, fac ); for( auto &point : point_set ) { if( point.z == where.z ) { if( square_dist( point, where ) <= range ) { @@ -472,7 +476,7 @@ bool zone_manager::has_near( const zone_type_id &type, const tripoint &where, in } } - const auto &vzone_set = get_vzone_set( type ); + const auto &vzone_set = get_vzone_set( type, fac ); for( auto &point : vzone_set ) { if( point.z == where.z ) { if( square_dist( point, where ) <= range ) { @@ -501,9 +505,9 @@ bool zone_manager::has_loot_dest_near( const tripoint &where ) const } std::unordered_set zone_manager::get_near( const zone_type_id &type, - const tripoint &where, int range ) const + const tripoint &where, int range, const faction_id &fac ) const { - const auto &point_set = get_point_set( type ); + const auto &point_set = get_point_set( type, fac ); auto near_point_set = std::unordered_set(); for( auto &point : point_set ) { @@ -514,7 +518,7 @@ std::unordered_set zone_manager::get_near( const zone_type_id &type, } } - const auto &vzone_set = get_vzone_set( type ); + const auto &vzone_set = get_vzone_set( type, fac ); for( auto &point : vzone_set ) { if( point.z == where.z ) { if( square_dist( point, where ) <= range ) { @@ -527,7 +531,7 @@ std::unordered_set zone_manager::get_near( const zone_type_id &type, } cata::optional zone_manager::get_nearest( const zone_type_id &type, const tripoint &where, - int range ) const + int range, const faction_id &fac ) const { if( range < 0 ) { return cata::nullopt; @@ -535,7 +539,7 @@ cata::optional zone_manager::get_nearest( const zone_type_id &type, co tripoint nearest_pos = tripoint( INT_MIN, INT_MIN, INT_MIN ); int nearest_dist = range + 1; - const std::unordered_set &point_set = get_point_set( type ); + const std::unordered_set &point_set = get_point_set( type, fac ); for( const tripoint &p : point_set ) { int cur_dist = square_dist( p, where ); if( cur_dist < nearest_dist ) { @@ -547,7 +551,7 @@ cata::optional zone_manager::get_nearest( const zone_type_id &type, co } } - const std::unordered_set &vzone_set = get_vzone_set( type ); + const std::unordered_set &vzone_set = get_vzone_set( type, fac ); for( const tripoint &p : vzone_set ) { int cur_dist = square_dist( p, where ); if( cur_dist < nearest_dist ) { @@ -663,12 +667,12 @@ zone_type_id zone_manager::get_near_zone_type_for_item( const item &it, } std::vector zone_manager::get_zones( const zone_type_id &type, - const tripoint &where ) const + const tripoint &where, const faction_id &fac ) const { auto zones = std::vector(); for( const auto &zone : this->zones ) { - if( zone.get_type() == type ) { + if( zone.get_type() == type && zone.get_faction() == fac ) { if( zone.has_inside( where ) ) { zones.emplace_back( zone ); } @@ -678,10 +682,14 @@ std::vector zone_manager::get_zones( const zone_type_id &type, return zones; } -const zone_data *zone_manager::get_bottom_zone( const tripoint &where ) const +const zone_data *zone_manager::get_bottom_zone( const tripoint &where, + const faction_id &fac ) const { for( auto it = zones.rbegin(); it != zones.rend(); ++it ) { const auto &zone = *it; + if( zone.get_faction() != fac ) { + continue; + } if( zone.has_inside( where ) ) { return &zone; @@ -690,6 +698,9 @@ const zone_data *zone_manager::get_bottom_zone( const tripoint &where ) const auto vzones = g->m.get_vehicle_zones( g->get_levz() ); for( auto it = vzones.rbegin(); it != vzones.rend(); ++it ) { const auto zone = *it; + if( zone->get_faction() != fac ) { + continue; + } if( zone->has_inside( where ) ) { return zone; @@ -715,12 +726,11 @@ void zone_manager::create_vehicle_loot_zone( vehicle &vehicle, const point &moun cache_vzones(); } -void zone_manager::add( const std::string &name, const zone_type_id &type, - const bool invert, const bool enabled, const tripoint &start, const tripoint &end, - std::shared_ptr options ) +void zone_manager::add( const std::string &name, const zone_type_id &type, const faction_id &fac, + const bool invert, const bool enabled, const tripoint &start, + const tripoint &end, std::shared_ptr options ) { - zone_data new_zone = zone_data( name, type, invert, enabled, start, - end, options ); + zone_data new_zone = zone_data( name, type, fac, invert, enabled, start, end, options ); //the start is a vehicle tile with cargo space if( const cata::optional vp = g->m.veh_at( g->m.getlocal( start ) ).part_with_feature( "CARGO", false ) ) { @@ -832,35 +842,44 @@ void zone_manager::decrement_num_processed( const tripoint &src ) } } -std::vector zone_manager::get_zones() +std::vector zone_manager::get_zones( const faction_id &fac ) { auto zones = std::vector(); for( auto &zone : this->zones ) { - zones.emplace_back( zone ); + if( zone.get_faction() == fac ) { + zones.emplace_back( zone ); + } } auto vzones = g->m.get_vehicle_zones( g->get_levz() ); for( auto zone : vzones ) { - zones.emplace_back( *zone ); + if( zone->get_faction() == fac ) { + zones.emplace_back( *zone ); + } } return zones; } -std::vector zone_manager::get_zones() const +std::vector zone_manager::get_zones( + const faction_id &fac ) const { auto zones = std::vector(); for( auto &zone : this->zones ) { - zones.emplace_back( zone ); + if( zone.get_faction() == fac ) { + zones.emplace_back( zone ); + } } auto vzones = g->m.get_vehicle_zones( g->get_levz() ); for( auto zone : vzones ) { - zones.emplace_back( *zone ); + if( zone->get_faction() == fac ) { + zones.emplace_back( *zone ); + } } return zones; @@ -888,6 +907,7 @@ void zone_data::serialize( JsonOut &json ) const json.start_object(); json.member( "name", name ); json.member( "type", type ); + json.member( "faction", faction ); json.member( "invert", invert ); json.member( "enabled", enabled ); json.member( "is_vehicle", is_vehicle ); @@ -902,6 +922,11 @@ void zone_data::deserialize( JsonIn &jsin ) JsonObject data = jsin.get_object(); data.read( "name", name ); data.read( "type", type ); + if( data.has_member( "faction" ) ) { + data.read( "faction", faction ); + } else { + faction = your_fac; + } data.read( "invert", invert ); data.read( "enabled", enabled ); //Legacy support diff --git a/src/clzones.h b/src/clzones.h index 08d4d983c74d1..3be41bd93730a 100644 --- a/src/clzones.h +++ b/src/clzones.h @@ -20,6 +20,11 @@ class JsonIn; class JsonOut; class JsonObject; class item; +class faction; + +using faction_id = string_id; +const faction_id your_fac( "your_followers" ); +const std::string type_fac_hash_str = "__FAC__"; class zone_type { @@ -129,6 +134,7 @@ class zone_data private: std::string name; zone_type_id type; + faction_id faction; bool invert; bool enabled; bool is_vehicle; @@ -148,12 +154,13 @@ class zone_data options = nullptr; } - zone_data( const std::string &_name, const zone_type_id &_type, + zone_data( const std::string &_name, const zone_type_id &_type, const faction_id &_faction, bool _invert, const bool _enabled, const tripoint &_start, const tripoint &_end, std::shared_ptr _options = nullptr ) { name = _name; type = _type; + faction = _faction; invert = _invert; enabled = _enabled; is_vehicle = false; @@ -174,9 +181,25 @@ class zone_data void set_enabled( const bool enabled_arg ); void set_is_vehicle( const bool is_vehicle_arg ); + static std::string make_type_hash( const zone_type_id &_type, const faction_id &_fac ) { + return _type.c_str() + type_fac_hash_str + _fac.c_str(); + } + static zone_type_id unhash_type( const std::string &hash_type ) { + size_t end = hash_type.find( type_fac_hash_str ); + if( end != std::string::npos && end < hash_type.size() ) { + return zone_type_id( hash_type.substr( 0, end ) ); + } + return zone_type_id( "" ); + } std::string get_name() const { return name; } + const faction_id &get_faction() const { + return faction; + } + std::string get_type_hash() const { + return make_type_hash( type, faction ); + } const zone_type_id &get_type() const { return type; } @@ -232,10 +255,12 @@ class zone_manager std::vector removed_vzones; std::map types; - std::unordered_map> area_cache; - std::unordered_map> vzone_cache; - std::unordered_set get_point_set( const zone_type_id &type ) const; - std::unordered_set get_vzone_set( const zone_type_id &type ) const; + std::unordered_map> area_cache; + std::unordered_map> vzone_cache; + std::unordered_set get_point_set( const zone_type_id &type, + const faction_id &fac = your_fac ) const; + std::unordered_set get_vzone_set( const zone_type_id &type, + const faction_id &fac = your_fac ) const; //Cache number of items already checked on each source tile when sorting std::unordered_map num_processed; @@ -253,7 +278,7 @@ class zone_manager return manager; } - void add( const std::string &name, const zone_type_id &type, + void add( const std::string &name, const zone_type_id &type, const faction_id &faction, const bool invert, const bool enabled, const tripoint &start, const tripoint &end, std::shared_ptr options = nullptr ); @@ -271,19 +296,25 @@ class zone_manager } std::string get_name_from_type( const zone_type_id &type ) const; bool has_type( const zone_type_id &type ) const; - bool has_defined( const zone_type_id &type ) const; + bool has_defined( const zone_type_id &type, const faction_id &fac = your_fac ) const; void cache_data(); void cache_vzones(); - bool has( const zone_type_id &type, const tripoint &where ) const; - bool has_near( const zone_type_id &type, const tripoint &where, int range = MAX_DISTANCE ) const; + bool has( const zone_type_id &type, const tripoint &where, + const faction_id &fac = your_fac ) const; + bool has_near( const zone_type_id &type, const tripoint &where, int range = MAX_DISTANCE, + const faction_id &fac = your_fac ) const; bool has_loot_dest_near( const tripoint &where ) const; std::unordered_set get_near( const zone_type_id &type, const tripoint &where, - int range = MAX_DISTANCE ) const; + int range = MAX_DISTANCE, + const faction_id &fac = your_fac ) const; cata::optional get_nearest( const zone_type_id &type, const tripoint &where, - int range = MAX_DISTANCE ) const; + int range = MAX_DISTANCE, + const faction_id &fac = your_fac ) const; zone_type_id get_near_zone_type_for_item( const item &it, const tripoint &where ) const; - std::vector get_zones( const zone_type_id &type, const tripoint &where ) const; - const zone_data *get_bottom_zone( const tripoint &where ) const; + std::vector get_zones( const zone_type_id &type, const tripoint &where, + const faction_id &fac = your_fac ) const; + const zone_data *get_bottom_zone( const tripoint &where, + const faction_id &fac = your_fac ) const; cata::optional query_name( const std::string &default_name = "" ) const; cata::optional query_type() const; void swap( zone_data &a, zone_data &b ); @@ -296,8 +327,8 @@ class zone_manager void decrement_num_processed( const tripoint &src ); // 'direct' access to zone_manager::zones, giving direct access was nono - std::vector get_zones(); - std::vector get_zones() const; + std::vector get_zones( const faction_id &fac = your_fac ); + std::vector get_zones( const faction_id &fac = your_fac ) const; bool save_zones(); void load_zones(); diff --git a/src/game.cpp b/src/game.cpp index 20ef0365ae358..c66101e36c265 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5980,19 +5980,17 @@ void game::zones_manager() int zone_options_height = 7; const int width = 45; - const int offsetX = get_option( "SIDEBAR_POSITION" ) == "left" ? TERMX + VIEW_OFFSET_X - - - width : VIEW_OFFSET_X; - catacurses::window w_zones = catacurses::newwin( TERMY - 2 - zone_ui_height - VIEW_OFFSET_Y * 2, - width - 2, VIEW_OFFSET_Y + 1, offsetX + 1 ); - catacurses::window w_zones_border = catacurses::newwin( TERMY - zone_ui_height - VIEW_OFFSET_Y * 2, - width, + const int offsetX = get_option( "SIDEBAR_POSITION" ) == "left" ? + TERMX + VIEW_OFFSET_X - width : VIEW_OFFSET_X; + int w_zone_height = TERMY - zone_ui_height - VIEW_OFFSET_Y * 2; + catacurses::window w_zones = catacurses::newwin( w_zone_height - 2, width - 2, + VIEW_OFFSET_Y + 1, offsetX + 1 ); + catacurses::window w_zones_border = catacurses::newwin( w_zone_height, width, VIEW_OFFSET_Y, offsetX ); catacurses::window w_zones_info = catacurses::newwin( zone_ui_height - zone_options_height - 1, - width - 2, - TERMY - zone_ui_height - VIEW_OFFSET_Y, offsetX + 1 ); + width - 2, w_zone_height + VIEW_OFFSET_Y, offsetX + 1 ); catacurses::window w_zones_info_border = catacurses::newwin( zone_ui_height, width, - TERMY - zone_ui_height - VIEW_OFFSET_Y, offsetX ); + w_zone_height + VIEW_OFFSET_Y, offsetX ); catacurses::window w_zones_options = catacurses::newwin( zone_options_height - 1, width - 2, TERMY - zone_options_height - VIEW_OFFSET_Y, offsetX + 1 ); @@ -6015,7 +6013,7 @@ void game::zones_manager() ctxt.register_action( "HELP_KEYBINDINGS" ); auto &mgr = zone_manager::get_manager(); - const int max_rows = TERMY - zone_ui_height - 2 - VIEW_OFFSET_Y * 2; + const int max_rows = w_zone_height - 2; int start_index = 0; int active_index = 0; bool blink = false; @@ -6027,7 +6025,6 @@ void game::zones_manager() // get zones on the same z-level, with distance between player and // zone center point <= 50 or all zones, if show_all_zones is true auto get_zones = [&]() { - std::vector zones; if( show_all_zones ) { zones = mgr.get_zones(); @@ -6076,25 +6073,29 @@ void game::zones_manager() tripoint center = u.pos() + u.view_offset; - const look_around_result first = look_around( w_zones_info, center, center, false, true, false ); + const look_around_result first = look_around( w_zones_info, center, center, false, true, + false ); if( first.position ) { mvwprintz( w_zones_info, 3, 2, c_white, _( "Select second point." ) ); wrefresh( w_zones_info ); - const look_around_result second = look_around( w_zones_info, center, *first.position, true, true, - false ); + const look_around_result second = look_around( w_zones_info, center, *first.position, + true, true, false ); if( second.position ) { werase( w_zones_info ); wrefresh( w_zones_info ); - tripoint first_abs = m.getabs( tripoint( std::min( first.position->x, second.position->x ), + tripoint first_abs = m.getabs( tripoint( std::min( first.position->x, + second.position->x ), std::min( first.position->y, second.position->y ), - std::min( first.position->z, second.position->z ) ) ); - tripoint second_abs = m.getabs( tripoint( std::max( first.position->x, second.position->x ), + std::min( first.position->z, + second.position->z ) ) ); + tripoint second_abs = m.getabs( tripoint( std::max( first.position->x, + second.position->x ), std::max( first.position->y, second.position->y ), - std::max( first.position->z, second.position->z ) ) ); - + std::max( first.position->z, + second.position->z ) ) ); return std::pair( first_abs, second_abs ); } } @@ -6105,7 +6106,8 @@ void game::zones_manager() zones_manager_open = true; do { if( action == "ADD_ZONE" ) { - zones_manager_draw_borders( w_zones_border, w_zones_info_border, zone_ui_height, width ); + zones_manager_draw_borders( w_zones_border, w_zones_info_border, + zone_ui_height, width ); do { // not a loop, just for quick bailing out if canceled const auto maybe_id = mgr.query_type(); @@ -6135,7 +6137,8 @@ void game::zones_manager() break; } - mgr.add( name, id, false, true, position->first, position->second, options ); + mgr.add( name, id, your_followers, false, true, position->first, position->second, + options ); zones = get_zones(); active_index = zone_cnt - 1; @@ -6147,7 +6150,8 @@ void game::zones_manager() blink = false; redraw_info = true; - zones_manager_draw_borders( w_zones_border, w_zones_info_border, zone_ui_height, width ); + zones_manager_draw_borders( w_zones_border, w_zones_info_border, + zone_ui_height, width ); zones_manager_shortcuts( w_zones_info ); } else if( action == "SHOW_ALL_ZONES" ) { @@ -6224,8 +6228,8 @@ void game::zones_manager() break; case 4: { const auto pos = query_position(); - if( pos && ( pos->first != zone.get_start_point() || pos->second != zone.get_end_point() ) ) { - + if( pos && ( pos->first != zone.get_start_point() || + pos->second != zone.get_end_point() ) ) { zone.set_position( *pos ); stuff_changed = true; } @@ -6240,7 +6244,8 @@ void game::zones_manager() blink = false; redraw_info = true; - zones_manager_draw_borders( w_zones_border, w_zones_info_border, zone_ui_height, width ); + zones_manager_draw_borders( w_zones_border, w_zones_info_border, + zone_ui_height, width ); zones_manager_shortcuts( w_zones_info ); } else if( action == "MOVE_ZONE_UP" && zone_cnt > 1 ) { @@ -6270,7 +6275,8 @@ void game::zones_manager() ui::omap::display_zones( player_overmap_position, zone_overmap, active_index ); - zones_manager_draw_borders( w_zones_border, w_zones_info_border, zone_ui_height, width ); + zones_manager_draw_borders( w_zones_border, w_zones_info_border, + zone_ui_height, width ); zones_manager_shortcuts( w_zones_info ); draw_ter(); @@ -6335,7 +6341,8 @@ void game::zones_manager() //Draw direction + distance mvwprintz( w_zones, iNum - start_index, 32, colorLine, "%*d %s", 5, static_cast( trig_dist( player_absolute_pos, center ) ), - direction_name_short( direction_from( player_absolute_pos, center ) ) ); + direction_name_short( direction_from( player_absolute_pos, + center ) ) ); //Draw Vehicle Indicator mvwprintz( w_zones, iNum - start_index, 41, colorLine, @@ -6383,10 +6390,11 @@ void game::zones_manager() u.pos() + u.view_offset ); } else { if( u.has_effect( effect_boomered ) ) { - mvwputch( w_terrain, iY - offset_y, iX - offset_x, c_magenta, '#' ); - + mvwputch( w_terrain, iY - offset_y, iX - offset_x, + c_magenta, '#' ); } else { - mvwputch( w_terrain, iY - offset_y, iX - offset_x, c_black, ' ' ); + mvwputch( w_terrain, iY - offset_y, iX - offset_x, + c_black, ' ' ); } } } From 5c0de6509568ee9214320a7e795a527f74d8cc34 Mon Sep 17 00:00:00 2001 From: Mark Langsdorf Date: Wed, 1 May 2019 08:49:37 -0500 Subject: [PATCH 3/5] NPCs: respect faction zone tags for investigate and retreat Now that zones are tagged by faction, let all NPCs, not just the player's allies, use no investigate, investigate limits, and retreat zones. NPCs will also stop investigating a sound if they spend 10 turns trying to reach it without moving. --- src/npc.h | 6 ++-- src/npcmove.cpp | 74 ++++++++++++++++++++++++++++++------------------- src/npctalk.cpp | 24 +++++++++------- 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/src/npc.h b/src/npc.h index c3097521491a7..413fec6ff0cf6 100644 --- a/src/npc.h +++ b/src/npc.h @@ -290,7 +290,7 @@ struct npc_follower_rules { }; struct dangerous_sound { - tripoint pos; + tripoint abs_pos; int type; int volume; }; @@ -321,7 +321,9 @@ struct npc_short_term_cache { // map of positions / type / volume of suspicious sounds std::vector sound_alerts; // current sound position being investigated - tripoint spos; + tripoint s_abs_pos; + // number of times we haven't moved when investigating a sound + int stuck = 0; // Position to return to guarding cata::optional guard_pos; double my_weapon_value; diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 29b71de5c2971..3783dcea004fd 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -151,11 +151,12 @@ bool clear_shot_reach( const tripoint &from, const tripoint &to ) tripoint npc::good_escape_direction( bool include_pos ) { - if( !is_enemy() && path.empty() ) { + if( path.empty() ) { zone_type_id retreat_zone = zone_type_id( "NPC_RETREAT" ); const tripoint &abs_pos = global_square_location(); const zone_manager &mgr = zone_manager::get_manager(); - cata::optional retreat_target = mgr.get_nearest( retreat_zone, abs_pos, 60 ); + cata::optional retreat_target = mgr.get_nearest( retreat_zone, abs_pos, 60, + fac_id ); if( retreat_target && *retreat_target != abs_pos ) { update_path( g->m.getlocal( *retreat_target ) ); if( !path.empty() ) { @@ -487,7 +488,7 @@ void npc::regen_ai_cache() { auto i = std::begin( ai_cache.sound_alerts ); while( i != std::end( ai_cache.sound_alerts ) ) { - if( sees( i->pos ) ) { + if( sees( g->m.getlocal( i->abs_pos ) ) ) { i = ai_cache.sound_alerts.erase( i ); if( ai_cache.sound_alerts.size() == 1 ) { path.clear(); @@ -596,8 +597,9 @@ void npc::move() } else if( target != nullptr && ai_cache.danger > 0 ) { action = method_of_attack(); } else if( !ai_cache.sound_alerts.empty() && !is_walking_with() ) { + tripoint cur_s_abs_pos = ai_cache.s_abs_pos; if( !ai_cache.guard_pos ) { - ai_cache.guard_pos = pos(); + ai_cache.guard_pos = g->m.getabs( pos() ); } if( ai_cache.sound_alerts.size() > 1 ) { std::sort( ai_cache.sound_alerts.begin(), ai_cache.sound_alerts.end(), @@ -606,10 +608,23 @@ void npc::move() ai_cache.sound_alerts.resize( 10 ); } } - ai_cache.spos = ai_cache.sound_alerts.front().pos; - add_msg( m_debug, "NPC %s: investigating sound at x(%d) y(%d)", name, ai_cache.spos.x, - ai_cache.spos.y ); action = npc_investigate_sound; + if( ai_cache.sound_alerts.front().abs_pos != cur_s_abs_pos ) { + ai_cache.stuck = 0; + ai_cache.s_abs_pos = ai_cache.sound_alerts.front().abs_pos; + } else if( ai_cache.stuck > 10 ) { + ai_cache.stuck = 0; + if( ai_cache.sound_alerts.size() == 1 ) { + ai_cache.sound_alerts.clear(); + action = npc_return_to_guard_pos; + } else { + ai_cache.s_abs_pos = ai_cache.sound_alerts.at( 1 ).abs_pos; + } + } + if( action == npc_investigate_sound ) { + add_msg( m_debug, "NPC %s: investigating sound at x(%d) y(%d)", name, + ai_cache.s_abs_pos.x, ai_cache.s_abs_pos.y ); + } } else if( ai_cache.sound_alerts.empty() && ai_cache.guard_pos ) { tripoint return_guard_pos = *ai_cache.guard_pos; add_msg( m_debug, "NPC %s: returning to guard spot at x(%d) y(%d)", name, @@ -727,14 +742,19 @@ void npc::execute_action( npc_action action ) break; case npc_investigate_sound: { - update_path( ai_cache.spos ); + tripoint cur_pos = pos(); + update_path( g->m.getlocal( ai_cache.s_abs_pos ) ); move_to_next(); + if( pos() == cur_pos ) { + ai_cache.stuck += 1; + } } break; case npc_return_to_guard_pos: { - update_path( *ai_cache.guard_pos ); - if( pos() == *ai_cache.guard_pos || path.empty() ) { + const tripoint local_guard_pos = g->m.getlocal( *ai_cache.guard_pos ); + update_path( local_guard_pos ); + if( pos() == local_guard_pos || path.empty() ) { move_pause(); ai_cache.guard_pos = cata::nullopt; path.clear(); @@ -1860,8 +1880,8 @@ void npc::move_to_next() } if( path.empty() ) { - add_msg( m_debug, - "npc::move_to_next() called with an empty path or path containing only current position" ); + add_msg( m_debug, "npc::move_to_next() called with an empty path or path " + "containing only current position" ); move_pause(); return; } @@ -3157,14 +3177,14 @@ void npc::reach_omt_destination() { if( is_travelling() ) { talk_function::assign_guard( *this ); - guard_pos = global_square_location(); + guard_pos = g->m.getabs( pos() ); omt_path.clear(); goal = no_goal_point; if( rl_dist( g->u.pos(), pos() ) > SEEX * 2 || !g->u.sees( pos() ) ) { if( g->u.has_item_with_flag( "TWO_WAY_RADIO", true ) && has_item_with_flag( "TWO_WAY_RADIO", true ) ) { - add_msg( m_info, _( "From your two-way radio you hear %s reporting in, 'I've arrived, boss!'" ), - disp_name() ); + add_msg( m_info, _( "From your two-way radio you hear %s reporting in, " + " 'I've arrived, boss!'" ), disp_name() ); } } return; @@ -3177,7 +3197,7 @@ void npc::reach_omt_destination() } // If we are guarding, remember our position in case we get forcibly moved goal = global_omt_location(); - if( guard_pos == global_square_location() ) { + if( guard_pos == g->m.getabs( pos() ) ) { // This is the specific point return; } @@ -3186,14 +3206,10 @@ void npc::reach_omt_destination() // No point recalculating the path to get home move_to_next(); } else if( guard_pos != no_goal_point ) { - const tripoint sm_dir = goal - submap_coords; - const tripoint dest( sm_dir.x * SEEX + guard_pos.x - posx(), - sm_dir.y * SEEY + guard_pos.y - posy(), - guard_pos.z ); - update_path( dest ); + update_path( g->m.getlocal( guard_pos ) ); move_to_next(); } else { - guard_pos = global_square_location(); + guard_pos = g->m.getabs( pos() ); } } @@ -3254,16 +3270,15 @@ void npc::set_omt_destination() } DebugLog( D_INFO, DC_ALL ) << "npc::set_omt_destination - new goal for NPC [" << get_name() << - "] with [" - << get_need_str_id( needs.front() ) << "] is [" << dest_type << - "] in [" - << goal.x << "," << goal.y << "," << goal.z << "]."; + "] with [" << get_need_str_id( needs.front() ) << + "] is [" << dest_type << + "] in [" << goal.x << "," << goal.y << "," << goal.z << "]."; } void npc::go_to_omt_destination() { if( ai_cache.guard_pos ) { - if( pos() == *ai_cache.guard_pos ) { + if( g->m.getabs( pos() ) == *ai_cache.guard_pos ) { path.clear(); ai_cache.guard_pos = cata::nullopt; move_pause(); @@ -3285,7 +3300,8 @@ void npc::go_to_omt_destination() add_msg( m_debug, "%s going (%d,%d,%d)->(%d,%d,%d)", name, omt_pos.x, omt_pos.y, omt_pos.z, goal.x, goal.y, goal.z ); if( goal == omt_pos ) { - // We're at our desired map square! + // We're at our desired map square! Pause to keep the NPC infinite loop counter happy + move_pause(); reach_omt_destination(); return; } @@ -3331,7 +3347,7 @@ void npc::go_to_omt_destination() void npc::guard_current_pos() { goal = global_omt_location(); - guard_pos = global_square_location(); + guard_pos = g->m.getabs( pos() ); } std::string npc_action_name( npc_action action ) diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 824ef44632a5a..34a7978b4fbad 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -300,8 +300,12 @@ void game::chat() void npc::handle_sound( int priority, const std::string &description, int heard_volume, const tripoint &spos ) { + const tripoint s_abs_pos = g->m.getabs( spos ); + const tripoint my_abs_pos = g->m.getabs( pos() ); + add_msg( m_debug, "%s heard '%s', priority %d at volume %d from %d:%d, my pos %d:%d", - disp_name(), description, priority, heard_volume, spos.x, spos.y, pos().x, pos().y ); + disp_name(), description, priority, heard_volume, s_abs_pos.x, s_abs_pos.y, + my_abs_pos.x, my_abs_pos.y ); const sounds::sound_t spriority = static_cast( priority ); bool player_ally = g->u.pos() == spos && is_player_ally(); @@ -354,30 +358,30 @@ void npc::handle_sound( int priority, const std::string &description, int heard_ warn_about( "movement_noise", rng( 1, 10 ) * 1_minutes, description ); } else if( spriority > sounds::sound_t::movement ) { if( !( player_ally || npc_ally ) && ( spriority == sounds::sound_t::speech || - spriority == sounds::sound_t::alert || spriority == sounds::sound_t::order ) ) { + spriority == sounds::sound_t::alert || + spriority == sounds::sound_t::order ) ) { warn_about( "speech_noise", rng( 1, 10 ) * 1_minutes ); } else if( spriority > sounds::sound_t::activity ) { warn_about( "combat_noise", rng( 1, 10 ) * 1_minutes ); } bool should_check = rl_dist( pos(), spos ) < investigate_dist; - if( should_check && is_ally( g->u ) ) { + if( should_check ) { const zone_manager &mgr = zone_manager::get_manager(); - const tripoint &s_abs_pos = g->m.getabs( spos ); - if( mgr.has( zone_no_investigate, s_abs_pos ) ) { + if( mgr.has( zone_no_investigate, s_abs_pos, fac_id ) ) { should_check = false; - } else if( mgr.has_defined( zone_investigate_only ) && - !mgr.has( zone_investigate_only, s_abs_pos ) ) { + } else if( mgr.has( zone_investigate_only, my_abs_pos, fac_id ) && + !mgr.has( zone_investigate_only, s_abs_pos, fac_id ) ) { should_check = false; } } if( should_check ) { - add_msg( m_debug, "NPC %s added noise at pos %d:%d", name, spos.x, spos.y ); + add_msg( m_debug, "%s added noise at pos %d:%d", name, s_abs_pos.x, s_abs_pos.y ); dangerous_sound temp_sound; - temp_sound.pos = spos; + temp_sound.abs_pos = s_abs_pos; temp_sound.volume = heard_volume; temp_sound.type = priority; if( !ai_cache.sound_alerts.empty() ) { - if( ai_cache.sound_alerts.back().pos != spos ) { + if( ai_cache.sound_alerts.back().abs_pos != s_abs_pos ) { ai_cache.sound_alerts.push_back( temp_sound ); } } else { From 34e5236afef0e85d2d1e349709f32cd44f799425 Mon Sep 17 00:00:00 2001 From: Mark Langsdorf Date: Wed, 1 May 2019 08:56:08 -0500 Subject: [PATCH 4/5] mapgen: add commands to place NPC zones NPC zones can be added by faction id and zone type. --- doc/MAPGEN.md | 7 +++++++ src/clzones.cpp | 35 +++++++++++++++++++++++++++++++++++ src/clzones.h | 2 ++ src/mapgen.cpp | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/doc/MAPGEN.md b/doc/MAPGEN.md index 2c1d11f24fe86..9811988711ff0 100644 --- a/doc/MAPGEN.md +++ b/doc/MAPGEN.md @@ -66,6 +66,7 @@ * 2.5.17 "sealed_item" * 2.5.18 "graffiti" * 2.6.19 "translate_ter" + * 2.6.20 "zones" * 2.6 "rotation" * 3 update_mapgen * 3.1 overmap tile specification @@ -698,6 +699,12 @@ normal mapgen, but it is useful for setting a baseline with update_mapgen. - "from": (required, string) the terrain id of the terrain to be transformed - "to": (required, string) the terrain id that the from terrain will transformed into +### 2.5.20 "zones" +Places a zone for an NPC faction. NPCs in the faction will use the zone to influence the AI. +- "type": (required, string) must be one of NPC_RETREAT, NPC_NO_INVESTIGATE, or NPC_INVESTIGATE_ONLY. NPCs will prefer to retreat towards NPC_RETREAT zones. They will not move to the see the source of unseen sounds coming from NPC_NO_INVESTIGATE zones. They will not move to the see the source of unseen sounds coming from outside NPC_INVESTIGATE_ONLY zones. +- "faction": (required, string) the faction id of the NPC faction that will use the zone. +- "name": (optional, string) the name of the zone. + # 2.7 "rotation" Rotates the generated map after all the other mapgen stuff has been done. The value can be a single integer or a range (out of which a value will be randomly chosen). Example: ```JSON diff --git a/src/clzones.cpp b/src/clzones.cpp index d8294ad5222f8..3536dc6613920 100644 --- a/src/clzones.cpp +++ b/src/clzones.cpp @@ -803,6 +803,41 @@ void zone_manager::swap( zone_data &a, zone_data &b ) std::swap( a, b ); } +void zone_manager::rotate_zones( map &target_map, const int turns ) +{ + if( turns == 0 ) { + return; + } + const tripoint a_start = target_map.getabs( tripoint( 0, 0, 0 ) ); + const tripoint a_end = target_map.getabs( tripoint( 23, 23, 0 ) ); + const point dim( 24, 24 ); + for( zone_data &zone : zones ) { + const tripoint z_start = zone.get_start_point(); + const tripoint z_end = zone.get_end_point(); + if( ( a_start.x <= z_start.x && a_start.y <= z_start.y ) && + ( a_end.x > z_start.x && a_end.y >= z_start.y ) && + ( a_start.x <= z_end.x && a_start.y <= z_end.y ) && + ( a_end.x >= z_end.x && a_end.y >= z_end.y ) ) { + tripoint z_l_start3 = target_map.getlocal( z_start ); + tripoint z_l_end3 = target_map.getlocal( z_end ); + // don't rotate centered squares + if( z_l_start3.x == z_l_start3.y && z_l_end3.x == z_l_end3.y && + ( z_l_start3.x + z_l_end3.x ) == 23 ) { + continue; + } + point z_l_start = point( z_l_start3.x, z_l_start3.y ).rotate( turns, dim ); + point z_l_end = point( z_l_end3.x, z_l_end3.y ).rotate( turns, dim ); + point new_z_start = target_map.getabs( z_l_start ); + point new_z_end = target_map.getabs( z_l_end ); + tripoint first = tripoint( std::min( new_z_start.x, new_z_end.x ), + std::min( new_z_start.y, new_z_end.y ), a_start.z ); + tripoint second = tripoint( std::max( new_z_start.x, new_z_end.x ), + std::max( new_z_start.y, new_z_end.y ), a_end.z ); + zone.set_position( std::make_pair( first, second ), false ); + } + } +} + void zone_manager::start_sort( const std::vector &src_sorted ) { for( auto &src : src_sorted ) { diff --git a/src/clzones.h b/src/clzones.h index 3be41bd93730a..47b44ffce7a9c 100644 --- a/src/clzones.h +++ b/src/clzones.h @@ -21,6 +21,7 @@ class JsonOut; class JsonObject; class item; class faction; +class map; using faction_id = string_id; const faction_id your_fac( "your_followers" ); @@ -318,6 +319,7 @@ class zone_manager cata::optional query_name( const std::string &default_name = "" ) const; cata::optional query_type() const; void swap( zone_data &a, zone_data &b ); + void rotate_zones( map &target_map, const int turns ); void start_sort( const std::vector &src_sorted ); void end_sort(); diff --git a/src/mapgen.cpp b/src/mapgen.cpp index aa994b1247d40..329f04f0f4092 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -14,12 +14,14 @@ #include #include "ammo.h" +#include "clzones.h" #include "computer.h" #include "coordinate_conversions.h" #include "coordinates.h" #include "debug.h" #include "drawing_primitives.h" #include "enums.h" +#include "faction.h" #include "game.h" #include "item_group.h" #include "itype.h" @@ -1646,6 +1648,34 @@ class jmapgen_translate : public jmapgen_piece dat.m.translate( from, to ); } }; +/** + * Place a zone + */ +class jmapgen_zone : public jmapgen_piece +{ + public: + zone_type_id zone_type; + faction_id faction; + std::string name = ""; + jmapgen_zone( JsonObject &jsi ) : jmapgen_piece() { + if( jsi.has_string( "faction" ) && jsi.has_string( "type" ) ) { + std::string fac_id = jsi.get_string( "faction" ); + faction = faction_id( fac_id ); + std::string zone_id = jsi.get_string( "type" ); + zone_type = zone_type_id( zone_id ); + if( jsi.has_string( "name" ) ) { + name = jsi.get_string( "name" ); + } + } + } + void apply( const mapgendata &dat, const jmapgen_int &x, const jmapgen_int &y, + const float /*mdensity*/, mission * /*miss*/ ) const override { + zone_manager &mgr = zone_manager::get_manager(); + const tripoint start = dat.m.getabs( tripoint( x.val, y.val, 0 ) ); + const tripoint end = dat.m.getabs( tripoint( x.valmax, y.valmax, 0 ) ); + mgr.add( name, zone_type, faction, false, true, start, end ); + } +}; static void load_weighted_entries( JsonObject &jsi, const std::string &json_key, weighted_int_list &list ) @@ -2148,6 +2178,7 @@ mapgen_palette mapgen_palette::load_internal( JsonObject &jo, const std::string new_pal.load_place_mapings( jo, "liquids", format_placings ); new_pal.load_place_mapings( jo, "graffiti", format_placings ); new_pal.load_place_mapings( jo, "translate", format_placings ); + new_pal.load_place_mapings( jo, "zones", format_placings ); return new_pal; } @@ -2326,6 +2357,7 @@ bool mapgen_function_json_base::setup_common( JsonObject jo ) objects.load_objects( jo, "place_nested" ); objects.load_objects( jo, "place_graffiti" ); objects.load_objects( jo, "translate_ter" ); + objects.load_objects( jo, "place_zones" ); if( !mapgen_defer::defer ) { is_ready = true; // skip setup attempts from any additional pointers @@ -7301,6 +7333,10 @@ void map::rotate( int turns ) } } } + + // rotate zones + zone_manager &mgr = zone_manager::get_manager(); + mgr.rotate_zones( *this, turns ); } // Hideous function, I admit... From add93c3e58561cf99cbfa68345fe1ffd4bb381a5 Mon Sep 17 00:00:00 2001 From: Mark Langsdorf Date: Wed, 1 May 2019 08:58:02 -0500 Subject: [PATCH 5/5] evac center: place investigate limit and no investigate zones Add investigate limit and no investigate zones to the Evac Center: - Most NPCs won't investigate noises coming from beyond the center's sidewalks - No NPC will investigate noises coming from the zombie infested back bay. - The beggars in the lobby won't investigate noises coming from outside the lobby. --- data/json/mapgen/refugee_center.json | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/data/json/mapgen/refugee_center.json b/data/json/mapgen/refugee_center.json index 2e8e884a155e6..c58c9d973b99d 100644 --- a/data/json/mapgen/refugee_center.json +++ b/data/json/mapgen/refugee_center.json @@ -33,6 +33,12 @@ "......_______,,_______ssssssssssssssssssssssssssssss,,,,,,,,,,,,,,,,ssssssssssssssssssssssssssssss_______,,_______......" ], "palettes": [ "evac_center" ], + "place_zones": [ + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 24, 47 ], "y": [ 23, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 48, 71 ], "y": [ 23, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 24, 47 ], "y": [ 23, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 48, 71 ], "y": [ 23, 23 ] } + ], "place_vehicles": [ { "vehicle": "schoolbus", "x": 32, "y": 18, "chance": 75, "rotation": 0 }, { "vehicle": "car", "x": 48, "y": 23, "chance": 75, "rotation": 270 } @@ -73,6 +79,20 @@ "......_______,,_______sss....#########t+ S# ########### ###########==#S +t##########....sss_______,,_______......" ], "palettes": [ "evac_center" ], + "place_zones": [ + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 24, 47 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 48, 68 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 24, 47 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 48, 68 ], "y": [ 0, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "free_merchants", "x": [ 69, 71 ], "y": [ 2, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "free_merchants", "x": [ 72, 95 ], "y": [ 0, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "wasteland_scavengers", "x": [ 69, 71 ], "y": [ 2, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "wasteland_scavengers", "x": [ 72, 95 ], "y": [ 0, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "old_guard", "x": [ 69, 71 ], "y": [ 2, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "old_guard", "x": [ 72, 95 ], "y": [ 0, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "lobby_beggars", "x": [ 69, 71 ], "y": [ 2, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "lobby_beggars", "x": [ 72, 95 ], "y": [ 0, 23 ] } + ], "place_vehicles": [ { "vehicle": "schoolbus", "x": 21, "y": 13, "chance": 75, "rotation": 270 }, { "vehicle": "flatbed_truck", "x": 98, "y": 18, "chance": 75, "rotation": 90 } @@ -151,6 +171,17 @@ "......_______,,________sss.........##### S# # 6V 2 2 V6 v# #S #####.........sss_________,,_______......" ], "palettes": [ "evac_center" ], + "place_zones": [ + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 24, 47 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 48, 71 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 72, 95 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 24, 47 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 48, 71 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 72, 95 ], "y": [ 0, 23 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "free_merchants", "x": [ 24, 32 ], "y": [ 3, 20 ] }, + { "type": "NPC_NO_INVESTIGATE", "faction": "wasteland_scavengers", "x": [ 24, 32 ], "y": [ 3, 20 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "lobby_beggars", "x": [ 51, 68 ], "y": [ 21, 23 ] } + ], "items": { "D": { "item": "trash", "chance": 60, "repeat": [ 1, 3 ] }, "L": { "item": "cleaning", "chance": 80, "repeat": [ 2, 6 ] }, @@ -212,6 +243,18 @@ "......_______,,_______ssss...............................ssssss...............................ssss_______,,_______......" ], "palettes": [ "evac_center" ], + "place_zones": [ + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 24, 47 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 48, 71 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "free_merchants", "x": [ 72, 95 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 24, 47 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 48, 71 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "wasteland_scavengers", "x": [ 72, 95 ], "y": [ 0, 23 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "old_guard", "x": [ 29, 47 ], "y": [ 2, 19 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "old_guard", "x": [ 48, 59 ], "y": [ 2, 19 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "old_guard", "x": [ 48, 48 ], "y": [ 0, 0 ] }, + { "type": "NPC_INVESTIGATE_ONLY", "faction": "lobby_beggars", "x": [ 51, 68 ], "y": [ 0, 4 ] } + ], "items": { "@": { "item": "bed", "chance": 80 }, "D": { "item": "trash", "chance": 60, "repeat": [ 1, 3 ] },