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 ] }, 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", 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 c876bfc664de0..3536dc6613920 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 ) ) { @@ -793,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 ) { @@ -832,35 +877,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 +942,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 +957,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..47b44ffce7a9c 100644 --- a/src/clzones.h +++ b/src/clzones.h @@ -20,6 +20,12 @@ class JsonIn; class JsonOut; class JsonObject; class item; +class faction; +class map; + +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 +135,7 @@ class zone_data private: std::string name; zone_type_id type; + faction_id faction; bool invert; bool enabled; bool is_vehicle; @@ -148,12 +155,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 +182,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 +256,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 +279,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,22 +297,29 @@ 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 ); + void rotate_zones( map &target_map, const int turns ); void start_sort( const std::vector &src_sorted ); void end_sort(); @@ -296,8 +329,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 8f6f3e44fb2c1..5ac94b23d5955 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; @@ -6026,7 +6024,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(); @@ -6075,25 +6072,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 ); } } @@ -6104,7 +6105,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(); @@ -6134,7 +6136,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; @@ -6145,7 +6148,8 @@ void game::zones_manager() draw_ter(); blink = false; - 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" ) { @@ -6218,8 +6222,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; } @@ -6233,7 +6237,8 @@ void game::zones_manager() blink = false; - 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 ) { @@ -6261,7 +6266,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(); @@ -6321,7 +6327,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, @@ -6369,10 +6376,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, ' ' ); } } } diff --git a/src/mapgen.cpp b/src/mapgen.cpp index cd7314caf6d84..6faafab33ba3f 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" @@ -1657,6 +1659,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 ) @@ -2144,6 +2174,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; } @@ -2322,6 +2353,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 @@ -7297,6 +7329,10 @@ void map::rotate( int turns ) } } } + + // rotate zones + zone_manager &mgr = zone_manager::get_manager(); + mgr.rotate_zones( *this, turns ); } // Hideous function, I admit... diff --git a/src/npc.h b/src/npc.h index 4cc7b8d45166e..c6d15c94eec85 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 040354d5e2a1a..632b0ac377310 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -152,11 +152,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() ) { @@ -488,7 +489,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(); @@ -597,8 +598,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(), @@ -607,10 +609,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, @@ -728,14 +743,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(); @@ -1878,8 +1898,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; } @@ -3221,14 +3241,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; @@ -3241,7 +3261,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; } @@ -3250,14 +3270,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() ); } } @@ -3318,16 +3334,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(); @@ -3349,7 +3364,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; } @@ -3395,7 +3411,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 394b1990b878f..078211602911b 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 {