diff --git a/data/json/npcs/refugee_center/surface_staff/Smokes/free_merchant_shopkeep_talk.json b/data/json/npcs/refugee_center/surface_staff/Smokes/free_merchant_shopkeep_talk.json index f5a9289f0e98f..3720aad454b8c 100644 --- a/data/json/npcs/refugee_center/surface_staff/Smokes/free_merchant_shopkeep_talk.json +++ b/data/json/npcs/refugee_center/surface_staff/Smokes/free_merchant_shopkeep_talk.json @@ -52,6 +52,7 @@ "id": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading", "type": "talk_topic", "dynamic_line": [ "Hope it helps.", "Give me second to write that down… okay, done.", "You know where to find me." ], + "speaker_effect": [ { "effect": "distribute_food_auto" } ], "responses": [ { "text": "About other things…", "topic": "TALK_FREE_MERCHANTS_MERCHANT_Talk" }, { "text": "Let's trade.", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading", "effect": "start_trade" }, diff --git a/doc/NPCs.md b/doc/NPCs.md index b924f65f0a7d2..e0a8512dd90b0 100644 --- a/doc/NPCs.md +++ b/doc/NPCs.md @@ -881,6 +881,7 @@ Effect | Description `assign_guard` | Makes the NPC into a guard. If allied and at a camp, they will be assigned to that camp. `stop_guard` | Releases the NPC from their guard duty (also see `assign_guard`). Friendly NPCs will return to following. `start_camp` | The NPC will start a faction camp with the player. +`distribute_food_auto` | The NPC will immediately distribute all food on their tile and adjacent tiles into the local camp's larder. Requires that such a camp exists and that the NPC has access to that camp. `wake_up` | Wakes up sleeping, but not sedated, NPCs. `reveal_stats` | Reveals the NPC's stats, based on the player's skill at assessing them. `end_conversation` | Ends the conversation and makes the NPC ignore you from now on. diff --git a/src/basecamp.cpp b/src/basecamp.cpp index 728bf43c602eb..1a1777dc575f2 100644 --- a/src/basecamp.cpp +++ b/src/basecamp.cpp @@ -695,11 +695,13 @@ void basecamp::form_storage_zones( map &here, const tripoint_abs_ms &abspos ) if( here.check_vehicle_zones( here.get_abs_sub().z() ) ) { mgr.cache_vzones(); } + // NPC camps may never have had bb_pos registered + validate_bb_pos( project_to( omt_pos ) ); tripoint src_loc = here.getlocal( bb_pos ) + point_north; std::vector possible_liquid_dumps; if( mgr.has_near( zone_type_CAMP_STORAGE, abspos, 60 ) ) { const std::vector zones = mgr.get_near_zones( zone_type_CAMP_STORAGE, abspos, - 60 ); + 60, get_owner() ); // Find the nearest unsorted zone to dump objects at if( !zones.empty() ) { if( zones != storage_zones ) { diff --git a/src/basecamp.h b/src/basecamp.h index d58f8ffea2233..94f61547adfbd 100644 --- a/src/basecamp.h +++ b/src/basecamp.h @@ -260,7 +260,7 @@ class basecamp item make_fake_food( const nutrients &to_use ) const; /// Takes all the food from the camp_food zone and increases the faction /// food_supply - bool distribute_food(); + bool distribute_food( bool player_command = true ); std::string name_display_of( const mission_id &miss_id ); void handle_hide_mission( const point &dir ); void handle_reveal_mission( const point &dir ); diff --git a/src/faction_camp.cpp b/src/faction_camp.cpp index 8bda640424303..c533ef9e366c5 100644 --- a/src/faction_camp.cpp +++ b/src/faction_camp.cpp @@ -5342,8 +5342,8 @@ bool basecamp::validate_sort_points() zone_manager &mgr = zone_manager::get_manager(); map *here = &get_map(); const tripoint_abs_ms abspos = get_player_character().get_location(); - if( !mgr.has_near( zone_type_CAMP_STORAGE, abspos, 60 ) || - !mgr.has_near( zone_type_CAMP_FOOD, abspos, 60 ) ) { + if( !mgr.has_near( zone_type_CAMP_STORAGE, abspos, 60, get_owner() ) || + !mgr.has_near( zone_type_CAMP_FOOD, abspos, 60, get_owner() ) ) { if( query_yn( _( "You do not have sufficient sort zones. Do you want to add them?" ) ) ) { return set_sort_points(); } else { @@ -5732,15 +5732,22 @@ static const npc &getAverageJoe() } // mission support -bool basecamp::distribute_food() +bool basecamp::distribute_food( bool player_command ) { if( !validate_sort_points() ) { - popup( _( "You do not have a camp food zone. Aborting…" ) ); + if( player_command ) { + popup( _( "You do not have a camp food zone. Aborting…" ) ); + } else { + debugmsg( "NPC-initiated food distribution at %s failed due to lacking zones", name ); + } return false; } - bool distribute_vitamins = query_yn( - _( "Do you also wish to distribute comestibles without any calorie value (i.e. multivitamins, mutagens)?" ) ); + bool distribute_vitamins = false; // NPCs only ever distribute food + if( player_command ) { + distribute_vitamins = query_yn( + _( "Do you also wish to distribute comestibles without any calorie value (i.e. multivitamins, mutagens)?" ) ); + } map &here = get_map(); zone_manager &mgr = zone_manager::get_manager(); @@ -5749,7 +5756,7 @@ bool basecamp::distribute_food() } const tripoint_abs_ms &abspos = get_dumping_spot(); const std::unordered_set &z_food = - mgr.get_near( zone_type_CAMP_FOOD, abspos, 60 ); + mgr.get_near( zone_type_CAMP_FOOD, abspos, 60, nullptr, get_owner() ); double quick_rot = 0.6 + ( has_provides( "pantry" ) ? 0.1 : 0 ); double slow_rot = 0.8 + ( has_provides( "pantry" ) ? 0.05 : 0 ); @@ -5845,7 +5852,9 @@ bool basecamp::distribute_food() } if( nutrients_to_add.kcal() <= 0 && nutrients_to_add.vitamins().empty() ) { - popup( _( "No suitable items are located at the drop points…" ) ); + if( player_command ) { + popup( _( "No suitable items are located at the drop points…" ) ); + } return false; } @@ -5857,7 +5866,9 @@ bool basecamp::distribute_food() popup_msg = _( "You distribute vitamins and medicine to your companions." ); } - popup( popup_msg ); + if( player_command ) { + popup( popup_msg ); + } camp_food_supply( nutrients_to_add ); return true; } diff --git a/src/npctalk.cpp b/src/npctalk.cpp index ada311cec01dd..cbce239abc414 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -6913,6 +6913,7 @@ void talk_effect_t::parse_string_effect( const std::string &effect_id, const Jso WRAP( clear_overrides ), WRAP( pick_style ), WRAP( do_disassembly ), + WRAP( distribute_food_auto ), WRAP( nothing ) #undef WRAP } diff --git a/src/npctalk.h b/src/npctalk.h index 0fa71827321e5..9d8c61ac34d96 100644 --- a/src/npctalk.h +++ b/src/npctalk.h @@ -105,6 +105,9 @@ void start_training_npc( npc & ); void start_training_seminar( npc &p ); void start_training_gen( Character &teacher, std::vector &students, teach_domain &d ); +// used for NPC camps +void distribute_food_auto( npc &p ); + void wake_up( npc & ); void copy_npc_rules( npc &p ); void set_npc_pickup( npc &p ); diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index 689cd78093dcc..63be4fefee61e 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -109,6 +109,9 @@ static const mtype_id mon_chicken( "mon_chicken" ); static const mtype_id mon_cow( "mon_cow" ); static const mtype_id mon_horse( "mon_horse" ); +static const zone_type_id zone_type_CAMP_FOOD( "CAMP_FOOD" ); +static const zone_type_id zone_type_CAMP_STORAGE( "CAMP_STORAGE" ); + struct itype; static void spawn_animal( npc &p, const mtype_id &mon ); @@ -1266,6 +1269,42 @@ npc *pick_follower() return followers[ menu.ret ]; } +void talk_function::distribute_food_auto( npc &p ) +{ + std::optional bcp = overmap_buffer.find_camp( p.global_omt_location().xy() ); + if( !bcp ) { + debugmsg( "distribute_food_auto called without a basecamp, aborting." ); + return; + } + basecamp *npc_camp = *bcp; + if( !npc_camp->allowed_access_by( p ) ) { + debugmsg( "distribute_food_auto called on npc that isn't allowed to access local basecamp storage, aborting." ); + return; + } + + zone_manager &mgr = zone_manager::get_manager(); + const tripoint_abs_ms &npc_abs_loc = p.get_location(); + // 3x3 square with NPC in the center, includes NPC's tile and all adjacent ones, for overflow + const tripoint top_left = npc_abs_loc.raw() + point{-1, -1}; // Awful hack, zones want the raw value + const tripoint bottom_right = npc_abs_loc.raw() + point{1, 1}; // Awful hack, zones want the raw value + std::string zone_name = "ERROR IF YOU SEE THIS (dummy zone talk_function::distribute_food_auto)"; + const faction_id &fac_id = p.get_fac_id(); + mgr.add( zone_name, zone_type_CAMP_FOOD, fac_id, false, true, top_left, bottom_right ); + mgr.add( zone_name, zone_type_CAMP_STORAGE, fac_id, false, true, top_left, bottom_right ); + npc_camp->distribute_food( false ); + // Now we clean up all camp zones, though there SHOULD only be the two we just made + auto lambda_remove_zones = [&mgr, &fac_id]( zone_type_id type_to_remove ) { + std::vector p_zones = mgr.get_zones( fac_id ); + for( zone_data &a_zone : p_zones ) { + if( a_zone.get_type() == type_to_remove ) { + mgr.remove( a_zone ); + } + } + }; + lambda_remove_zones( zone_type_CAMP_FOOD ); + lambda_remove_zones( zone_type_CAMP_STORAGE ); +} + void talk_function::copy_npc_rules( npc &p ) { const npc *other = pick_follower();