From 52d144aa331ab3ed8fd5f2d456b7e1a180cbb284 Mon Sep 17 00:00:00 2001 From: davidpwbrown <39344466+davidpwbrown@users.noreply.github.com> Date: Tue, 27 Aug 2019 08:09:44 +0200 Subject: [PATCH] NPC activity rework - farming and generic multi-activity loop (#32730) --- data/json/npcs/TALK_COMMON_ALLY.json | 6 +- data/json/player_activities.json | 31 +- src/activity_handlers.cpp | 151 +-- src/activity_handlers.h | 29 +- src/activity_item_handling.cpp | 1352 +++++++++++++++++++------- src/clzones.cpp | 55 +- src/clzones.h | 16 +- src/construction.cpp | 7 +- src/construction.h | 1 - src/handle_action.cpp | 11 +- src/iexamine.cpp | 27 +- src/iexamine.h | 2 +- src/inventory.cpp | 10 + src/inventory.h | 2 + src/iuse.cpp | 7 +- src/map.cpp | 11 + src/map.h | 1 + src/npcmove.cpp | 6 +- src/npctalk.cpp | 2 +- src/npctalk.h | 2 +- src/npctalk_funcs.cpp | 8 +- src/player.cpp | 7 +- src/player.h | 8 +- 23 files changed, 1259 insertions(+), 493 deletions(-) diff --git a/data/json/npcs/TALK_COMMON_ALLY.json b/data/json/npcs/TALK_COMMON_ALLY.json index 87bc58d545cab..c30c304f43435 100644 --- a/data/json/npcs/TALK_COMMON_ALLY.json +++ b/data/json/npcs/TALK_COMMON_ALLY.json @@ -741,16 +741,16 @@ "effect": "sort_loot" }, { - "text": "Please work on any unfinished construction task that you know how to finish.", + "text": "Please do any construction work that you can.", "topic": "TALK_DONE", "condition": { "not": "npc_has_activity" }, "effect": "do_construction" }, { - "text": "Please work on any contruction blueprint zones nearby.", + "text": "Please do some farming work.", "topic": "TALK_DONE", "condition": { "not": "npc_has_activity" }, - "effect": "do_blueprint_construction" + "effect": "do_farming" } ] }, diff --git a/data/json/player_activities.json b/data/json/player_activities.json index 2a8f3425b5eb1..b69910bc5931d 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -26,10 +26,10 @@ "no_resume": true }, { - "id": "ACT_BLUEPRINT_CONSTRUCTION", + "id": "ACT_TIDY_UP", "type": "activity_type", - "activity_level": "ACTIVE_EXERCISE", - "verb": "constructing", + "activity_level": "MODERATE_EXERCISE", + "verb": "tidying up", "suspendable": false, "based_on": "neither", "no_resume": true @@ -306,6 +306,24 @@ "based_on": "neither", "no_resume": true }, + { + "id": "ACT_FETCH_REQUIRED", + "type": "activity_type", + "activity_level": "MODERATE_EXERCISE", + "verb": "fetching components", + "suspendable": false, + "based_on": "neither", + "no_resume": true + }, + { + "id": "ACT_MULTIPLE_FARM", + "type": "activity_type", + "activity_level": "ACTIVE_EXERCISE", + "verb": "farming", + "suspendable": false, + "based_on": "neither", + "no_resume": true + }, { "id": "ACT_PLANT_PLOT", "type": "activity_type", @@ -533,6 +551,13 @@ "verb": "drilling", "based_on": "speed" }, + { + "id": "ACT_CHURN", + "type": "activity_type", + "activity_level": "EXTRA_EXERCISE", + "verb": "churning earth", + "based_on": "time" + }, { "id": "ACT_DIG", "type": "activity_type", diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 2a64f67cab0e8..f42e39265849c 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -8,6 +8,7 @@ #include #include #include + #include #include #include @@ -130,7 +131,8 @@ activity_handlers::do_turn_functions = { { activity_id( "ACT_PICKUP" ), pickup_do_turn }, { activity_id( "ACT_WEAR" ), wear_do_turn }, { activity_id( "ACT_MULTIPLE_CONSTRUCTION" ), multiple_construction_do_turn }, - { activity_id( "ACT_BLUEPRINT_CONSTRUCTION" ), blueprint_construction_do_turn }, + { activity_id( "ACT_MULTIPLE_FARM" ), multiple_farm_do_turn }, + { activity_id( "ACT_FETCH_REQUIRED" ), fetch_do_turn }, { activity_id( "ACT_BUILD" ), build_do_turn }, { activity_id( "ACT_EAT_MENU" ), eat_menu_do_turn }, { activity_id( "ACT_CONSUME_FOOD_MENU" ), consume_food_menu_do_turn }, @@ -148,6 +150,7 @@ activity_handlers::do_turn_functions = { { activity_id( "ACT_BUTCHER_FULL" ), butcher_do_turn }, { activity_id( "ACT_TRAVELLING" ), travel_do_turn }, { activity_id( "ACT_AUTODRIVE" ), drive_do_turn }, + { activity_id( "ACT_CHURN" ), churn_do_turn }, { activity_id( "ACT_FIELD_DRESS" ), butcher_do_turn }, { activity_id( "ACT_SKIN" ), butcher_do_turn }, { activity_id( "ACT_QUARTER" ), butcher_do_turn }, @@ -157,6 +160,7 @@ activity_handlers::do_turn_functions = { { activity_id( "ACT_CHOP_TREE" ), chop_tree_do_turn }, { activity_id( "ACT_CHOP_LOGS" ), chop_tree_do_turn }, { activity_id( "ACT_CHOP_PLANKS" ), chop_tree_do_turn }, + { activity_id( "ACT_TIDY_UP" ), tidy_up_do_turn }, { activity_id( "ACT_JACKHAMMER" ), jackhammer_do_turn }, { activity_id( "ACT_DIG" ), dig_do_turn }, { activity_id( "ACT_DIG_CHANNEL" ), dig_channel_do_turn }, @@ -194,6 +198,7 @@ activity_handlers::finish_functions = { { activity_id( "ACT_RELOAD" ), reload_finish }, { activity_id( "ACT_START_FIRE" ), start_fire_finish }, { activity_id( "ACT_TRAIN" ), train_finish }, + { activity_id( "ACT_CHURN" ), churn_finish }, { activity_id( "ACT_VEHICLE" ), vehicle_finish }, { activity_id( "ACT_START_ENGINES" ), start_engines_finish }, { activity_id( "ACT_OXYTORCH" ), oxytorch_finish }, @@ -2597,7 +2602,7 @@ void activity_handlers::move_items_do_turn( player_activity *act, player *p ) void activity_handlers::move_loot_do_turn( player_activity *act, player *p ) { - activity_on_turn_move_loot( *act, *p ); + generic_multi_activity_handler( *act, *p ); } void activity_handlers::adv_inventory_do_turn( player_activity *, player *p ) @@ -3089,6 +3094,24 @@ static bool character_has_skill_for( const player *p, const construction &con ) } ); } +void activity_handlers::churn_do_turn( player_activity *act, player *p ) +{ + ( void )act; + ( void )p; + p->set_moves( 0 ); +} + +void activity_handlers::churn_finish( player_activity *act, player *p ) +{ + p->add_msg_if_player( _( "You finish churning up the earth here." ) ); + g->m.ter_set( g->m.getlocal( act->placement ), t_dirtmound ); + // Go back to what we were doing before + // could be player zone activity, or could be NPC multi-farming + act->set_to_null(); + p->activity = p->backlog.front(); + p->backlog.pop_front(); +} + void activity_handlers::build_do_turn( player_activity *act, player *p ) { const std::vector &list_constructions = get_constructions(); @@ -3144,100 +3167,24 @@ void activity_handlers::build_do_turn( player_activity *act, player *p ) } } -void activity_handlers::multiple_construction_do_turn( player_activity *act, player *p ) +void activity_handlers::tidy_up_do_turn( player_activity *act, player *p ) { - ( void )act; - const activity_id act_multiple_construction = activity_id( "ACT_MULTIPLE_CONSTRUCTION" ); - tripoint src_loc_start = p->pos(); - // search in a radius around unsorted zone to find corpse spots - std::vector build_spots; - for( tripoint p : g->m.points_in_radius( src_loc_start, 20 ) ) { - partial_con *pc = g->m.partial_con_at( p ); - if( pc ) { - build_spots.push_back( p ); - } - } - // Nuke the current activity, leaving the backlog alone. - p->activity = player_activity(); - - // sort source tiles by distance - for( auto &src_loc : build_spots ) { - if( !g->m.inbounds( src_loc ) ) { - if( !g->m.inbounds( p->pos() ) ) { - // p is implicitly an NPC that has been moved off the map, so reset the activity - // and unload them - p->assign_activity( act_multiple_construction ); - p->set_moves( 0 ); - g->reload_npcs(); - return; - } - std::vector route = route_adjacent( *p, src_loc ); - if( route.empty() ) { - // can't get there, can't do anything, skip it - continue; - } - p->set_destination( route, player_activity( act_multiple_construction ) ); - return; - } - - // skip tiles on fire so as not to try and butcher corpses on fire - // and inaccessible furniture, like filled charcoal kiln - if( g->m.get_field( src_loc, fd_fire ) != nullptr ) { - continue; - } - bool adjacent = false; - for( auto elem : g->m.points_in_radius( src_loc, 1 ) ) { - if( p->pos() == elem ) { - adjacent = true; - break; - } - } - if( !adjacent ) { - std::vector route = route_adjacent( *p, src_loc ); - - // check if we found path to source / adjacent tile - if( route.empty() ) { - add_msg( m_info, _( "%s can't reach the source tile to construct" ), - p->disp_name() ); - return; - } - - // set the destination and restart activity after player arrives there - // we don't need to check for safe mode, - // activity will be restarted only if - // player arrives on destination tile - p->set_destination( route, player_activity( act_multiple_construction ) ); - return; - } - // maybe the construction dissappeared, double check before starting work - partial_con *nc = g->m.partial_con_at( src_loc ); - if( !nc ) { - p->assign_activity( act_multiple_construction ); - return; - } - p->backlog.push_front( act_multiple_construction ); - p->assign_activity( activity_id( "ACT_BUILD" ) ); - p->activity.placement = g->m.getabs( src_loc ); - return; + generic_multi_activity_handler( *act, *p ); +} - } - if( p->moves <= 0 ) { - // Restart activity and break from cycle. - p->assign_activity( act_multiple_construction ); - return; - } +void activity_handlers::multiple_construction_do_turn( player_activity *act, player *p ) +{ + generic_multi_activity_handler( *act, *p ); +} - // If we got here without restarting the activity, it means we're done. - if( p->is_npc() ) { - npc *guy = dynamic_cast( p ); - guy->current_activity_id = activity_id::NULL_ID(); - guy->revert_after_activity(); - } +void activity_handlers::multiple_farm_do_turn( player_activity *act, player *p ) +{ + generic_multi_activity_handler( *act, *p ); } -void activity_handlers::blueprint_construction_do_turn( player_activity *act, player *p ) +void activity_handlers::fetch_do_turn( player_activity *act, player *p ) { - activity_on_turn_blueprint_move( *act, *p ); + generic_multi_activity_handler( *act, *p ); } void activity_handlers::craft_do_turn( player_activity *act, player *p ) @@ -3792,40 +3739,40 @@ static void perform_zone_activity_turn( player *p, } else { // we are at destination already /* Perform action */ tile_action( *p, tile_loc ); - if( p->moves <= 0 ) { return; } } } - add_msg( m_info, finished_msg ); p->activity.set_to_null(); } void activity_handlers::harvest_plot_do_turn( player_activity *, player *p ) { - const auto reject_tile = [p]( const tripoint & tile ) { - return !p->sees( tile ) || !g->m.has_flag_furn( "GROWTH_HARVEST", tile ); + const auto reject_tile = []( const tripoint & tile ) { + return !g->m.has_flag_furn( "GROWTH_HARVEST", tile ); + }; + const auto harvest = [&]( player & p, const tripoint & tile ) { + iexamine::harvest_plant( p, tile, false ); }; perform_zone_activity_turn( p, zone_type_id( "FARM_PLOT" ), reject_tile, - iexamine::harvest_plant, + harvest, _( "You harvested all the plots you could." ) ); } void activity_handlers::till_plot_do_turn( player_activity *, player *p ) { - const auto reject_tile = [p]( const tripoint & tile ) { - return !p->sees( tile ) || !g->m.has_flag( "PLOWABLE", tile ) || g->m.has_furn( tile ); + const auto reject_tile = []( const tripoint & tile ) { + return !g->m.has_flag( "PLOWABLE", tile ) || g->m.has_furn( tile ); }; const auto dig = []( player & p, const tripoint & tile_loc ) { - p.add_msg_if_player( _( "You churn up the earth here." ) ); - p.mod_moves( -300 ); - g->m.ter_set( tile_loc, t_dirtmound ); + p.assign_activity( activity_id( "ACT_CHURN" ), 18000, -1 ); + p.activity.placement = tile_loc; }; perform_zone_activity_turn( p, @@ -3861,7 +3808,7 @@ void activity_handlers::fertilize_plot_do_turn( player_activity *act, player *p const auto reject_tile = [&]( const tripoint & tile ) { check_fertilizer(); ret_val can_fert = iexamine::can_fertilize( *p, tile, fertilizer ); - return !p->sees( tile ) || !can_fert.success(); + return !can_fert.success(); }; const auto fertilize = [&]( player & p, const tripoint & tile ) { @@ -3912,7 +3859,7 @@ void activity_handlers::plant_plot_do_turn( player_activity *, player *p ) // cleanup unwanted tiles (local coords) const auto reject_tiles = [&]( const tripoint & tile ) { - if( !p->sees( tile ) || !g->m.has_flag_ter_or_furn( "PLANTABLE", tile ) || + if( !g->m.has_flag_ter_or_furn( "PLANTABLE", tile ) || g->m.has_items( tile ) ) { return true; } diff --git a/src/activity_handlers.h b/src/activity_handlers.h index 2e96b19083fb3..42c79aa993c67 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -29,13 +29,32 @@ enum butcher_type : int { DISSECT // dissect a corpse for CBMs }; +enum do_activity_reason : int { + CAN_DO_CONSTRUCTION, // Can do construction. + CAN_DO_FETCH, // Can do fetch - this is usually the default result for fetch task + CAN_DO_PREREQ, // for constructions - cant build the main construction, but can build the pre-req + CAN_DO_PREREQ_2, // Can do the second pre-req deep below the desired one. + NO_COMPONENTS, // can't do the activity there due to lack of components /tools + NO_COMPONENTS_PREREQ, // need components to build the pre-requisite for the actual desired construction + NO_COMPONENTS_PREREQ_2, // need components to the second pre-req deep. + DONT_HAVE_SKILL, // don't have the required skill + NO_ZONE, // There is no required zone anymore + ALREADY_DONE, // the activity is done already ( maybe by someone else ) + UNKNOWN_ACTIVITY, // This is probably an error - got to the end of function with no previous reason + NEEDS_HARVESTING, // For farming - tile is harvestable now. + NEEDS_PLANTING, // For farming - tile can be planted + NEEDS_TILLING, // For farming - tile can be tilled + BLOCKING_TILE // Something has made it's way onto the tile, so the activity cannot proceed +}; + int butcher_time_to_cut( const player &u, const item &corpse_item, butcher_type action ); // activity_item_handling.cpp void activity_on_turn_drop(); void activity_on_turn_move_items( player_activity &act, player &p ); void activity_on_turn_move_loot( player_activity &act, player &p ); -void activity_on_turn_blueprint_move( player_activity &, player &p ); +void generic_multi_activity_handler( player_activity &act, player &p ); +void activity_on_turn_fetch( player_activity &, player *p ); void activity_on_turn_pickup(); void activity_on_turn_wear( player_activity &act, player &p ); void try_fuel_fire( player_activity &act, player &p, bool starting_fire = false ); @@ -50,6 +69,8 @@ enum class item_drop_reason { void put_into_vehicle_or_drop( Character &c, item_drop_reason, const std::list &items ); void put_into_vehicle_or_drop( Character &c, item_drop_reason, const std::list &items, const tripoint &where, bool force_ground = false ); +void drop_on_map( Character &c, item_drop_reason reason, const std::list &items, + const tripoint &where ); namespace activity_handlers { @@ -63,6 +84,7 @@ void drop_do_turn( player_activity *act, player *p ); void stash_do_turn( player_activity *act, player *p ); void pulp_do_turn( player_activity *act, player *p ); void game_do_turn( player_activity *act, player *p ); +void churn_do_turn( player_activity *act, player *p ); void start_fire_do_turn( player_activity *act, player *p ); void vibe_do_turn( player_activity *act, player *p ); void hand_crank_do_turn( player_activity *act, player *p ); @@ -75,8 +97,9 @@ void consume_food_menu_do_turn( player_activity *act, player *p ); void consume_drink_menu_do_turn( player_activity *act, player *p ); void consume_meds_menu_do_turn( player_activity *act, player *p ); void move_items_do_turn( player_activity *act, player *p ); +void multiple_farm_do_turn( player_activity *act, player *p ); void multiple_construction_do_turn( player_activity *act, player *p ); -void blueprint_construction_do_turn( player_activity *act, player *p ); +void fetch_do_turn( player_activity *act, player *p ); void move_loot_do_turn( player_activity *act, player *p ); void travel_do_turn( player_activity *act, player *p ); void drive_do_turn( player_activity *act, player *p ); @@ -90,6 +113,7 @@ void butcher_do_turn( player_activity *act, player *p ); void hacksaw_do_turn( player_activity *act, player *p ); void chop_tree_do_turn( player_activity *act, player *p ); void jackhammer_do_turn( player_activity *act, player *p ); +void tidy_up_do_turn( player_activity *act, player *p ); void dig_do_turn( player_activity *act, player *p ); void build_do_turn( player_activity *act, player *p ); void dig_channel_do_turn( player_activity *act, player *p ); @@ -126,6 +150,7 @@ void start_fire_finish( player_activity *act, player *p ); void train_finish( player_activity *act, player *p ); void vehicle_finish( player_activity *act, player *p ); void start_engines_finish( player_activity *act, player *p ); +void churn_finish( player_activity *act, player *p ); void oxytorch_finish( player_activity *act, player *p ); void cracking_finish( player_activity *act, player *p ); void open_gate_finish( player_activity *act, player * ); diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 34a161fcd7a5a..9a89253f2d254 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -17,8 +17,8 @@ #include "field.h" #include "fire.h" #include "game.h" -#include "item.h" #include "iuse.h" +#include "iexamine.h" #include "map.h" #include "map_iterator.h" #include "mapdata.h" @@ -43,7 +43,6 @@ #include "inventory.h" #include "line.h" #include "units.h" -#include "type_id.h" #include "flat_set.h" #include "int_id.h" #include "item_location.h" @@ -57,6 +56,8 @@ void cancel_aim_processing(); const efftype_id effect_controlled( "controlled" ); const efftype_id effect_pet( "pet" ); +const zone_type_id z_loot_unsorted( "LOOT_UNSORTED" ); + const trap_str_id tr_firewood_source( "tr_firewood_source" ); const trap_str_id tr_unfinished_construction( "tr_unfinished_construction" ); @@ -77,7 +78,7 @@ using drop_indexes = std::list>; static bool same_type( const std::list &items ) { - return std::all_of( items.begin(), items.end(), [ &items ]( const item & it ) { + return std::all_of( items.begin(), items.end(), [&items]( const item & it ) { return it.type == items.begin()->type; } ); } @@ -234,8 +235,8 @@ static void stash_on_pet( const std::list &items, monster &pet, player *p } } -static void drop_on_map( Character &c, item_drop_reason reason, const std::list &items, - const tripoint &where ) +void drop_on_map( Character &c, item_drop_reason reason, const std::list &items, + const tripoint &where ) { if( items.empty() ) { return; @@ -403,8 +404,8 @@ static std::list convert_to_items( const player &p, const drop_indexes // Implements the "backpack" logic. static std::list reorder_for_dropping( const player &p, const drop_indexes &drop ) { - auto res = convert_to_items( p, drop, -1, -1 ); - auto inv = convert_to_items( p, drop, 0, INT_MAX ); + auto res = convert_to_items( p, drop, -1, -1 ); + auto inv = convert_to_items( p, drop, 0, INT_MAX ); auto worn = convert_to_items( p, drop, INT_MIN, -2 ); // Sort inventory items by volume in ascending order @@ -415,7 +416,7 @@ static std::list reorder_for_dropping( const player &p, const drop_ind for( const auto &wait : worn ) { for( const auto dit : p.get_dependent_worn_items( *wait.it ) ) { const auto iter = std::find_if( worn.begin(), worn.end(), - [ dit ]( const act_item & ait ) { + [dit]( const act_item & ait ) { return ait.it == dit; } ); @@ -674,10 +675,7 @@ void activity_on_turn_pickup() // Otherwise, we are done. if( !keep_going || g->u.activity.targets.empty() ) { g->u.cancel_activity(); - } - - // TODO: Move this to advanced inventory instead of hacking it in here - if( !keep_going ) { + // TODO: Move this to advanced inventory instead of hacking it in here cancel_aim_processing(); } } @@ -856,7 +854,8 @@ static int move_cost( const item &it, const tripoint &src, const tripoint &dest } static void move_item( player &p, item &it, int quantity, const tripoint &src, - const tripoint &dest, vehicle *src_veh, int src_part ) + const tripoint &dest, vehicle *src_veh, int src_part, + activity_id activity_to_restore = activity_id::NULL_ID() ) { item leftovers = it; @@ -873,6 +872,11 @@ static void move_item( player &p, item &it, int quantity, const tripoint &src, // Check that we can pick it up. if( !it.made_of_from_type( LIQUID ) ) { p.mod_moves( -move_cost( it, src, dest ) ); + if( activity_to_restore == activity_id( "ACT_TIDY_UP" ) ) { + it.erase_var( "activity_var" ); + } else if( activity_to_restore == activity_id( "ACT_FETCH_REQUIRED" ) ) { + it.set_var( "activity_var", p.name ); + } put_into_vehicle_or_drop( p, item_drop_reason::deliberate, { it }, dest ); // Remove from map or vehicle. if( src_veh ) { @@ -935,435 +939,1099 @@ static construction check_build_pre( const construction &con ) return pre_con; } -void activity_on_turn_blueprint_move( player_activity &, player &p ) +static bool character_has_skill_for( const player &p, const construction &con ) { - zone_manager &mgr = zone_manager::get_manager(); - - const tripoint abspos = g->m.getabs( p.pos() ); - const std::unordered_set &src_set = mgr.get_near( - zone_type_id( "CONSTRUCTION_BLUEPRINT" ), abspos ); - - const std::vector &src_sorted = get_sorted_tiles_by_distance( abspos, src_set ); - const activity_id act_multiple_construction = activity_id( "ACT_BLUEPRINT_CONSTRUCTION" ); - - // Nuke the current activity, leaving the backlog alone - p.activity = player_activity(); - - // sort source tiles by distance - for( const tripoint &src : src_sorted ) { - const tripoint &src_loc = g->m.getlocal( src ); - // check if somebodies already started it - partial_con *nc = g->m.partial_con_at( src_loc ); - if( nc ) { - continue; - } + return std::all_of( con.required_skills.begin(), con.required_skills.end(), + [&]( const std::pair &pr ) { + return p.get_skill_level( pr.first ) >= pr.second; + } ); +} - if( !g->m.inbounds( src_loc ) ) { - if( !g->m.inbounds( p.pos() ) ) { - // p is implicitly an NPC that has been moved off the map, so reset the activity - // and unload them - p.assign_activity( act_multiple_construction ); - p.set_moves( 0 ); - g->reload_npcs(); - return; +static bool are_requirements_nearby( const std::vector &loot_spots, + const requirement_id &needed_things, const player &p, const activity_id activity_to_restore, + bool in_loot_zones ) +{ + zone_manager &mgr = zone_manager::get_manager(); + inventory temp_inv; + units::volume volume_allowed = p.volume_capacity() - p.volume_carried(); + units::mass weight_allowed = p.weight_capacity() - p.weight_carried(); + const bool check_weight = p.backlog.front().id() == activity_id( "ACT_MULTIPLE_FARM" ) || + activity_to_restore == activity_id( "ACT_MULTIPLE_FARM" ); + for( const tripoint &elem : loot_spots ) { + // if we are searching for things to fetch, we can skip certain thngs. + // if, however they are already near the work spot, then the crafting / inventory fucntions will have their own method to use or discount them. + if( in_loot_zones ) { + // skip tiles in IGNORE zone and tiles on fire + // (to prevent taking out wood off the lit brazier) + // and inaccessible furniture, like filled charcoal kiln + if( mgr.has( zone_type_id( "LOOT_IGNORE" ), g->m.getabs( elem ) ) || + g->m.dangerous_field_at( elem ) || + !g->m.can_put_items_ter_furn( elem ) ) { + continue; } - const std::vector route = route_adjacent( p, src_loc ); - if( route.empty() ) { - // can't get there, can't do anything, skip it + } + for( const auto &elem2 : g->m.i_at( elem ) ) { + if( in_loot_zones && elem2.made_of_from_type( LIQUID ) ) { continue; } - p.set_destination( route, player_activity( act_multiple_construction ) ); - return; + if( check_weight ) { + // this fetch task will need to pick up an item. so check for its weight/volume before setting off. + if( in_loot_zones && ( elem2.volume() > volume_allowed || + elem2.weight() > weight_allowed ) ) { + continue; + } + } + temp_inv += elem2; } - // dont go there if it's dangerous. - bool dangerous_field = false; - for( const std::pair &e : g->m.field_at( src_loc ) ) { - if( p.is_dangerous_field( e.second ) ) { - dangerous_field = true; - break; + if( !in_loot_zones ) { + if( const cata::optional vp = g->m.veh_at( elem ).part_with_feature( "CARGO", + false ) ) { + vehicle &src_veh = vp->vehicle(); + int src_part = vp->part_index(); + for( auto &it : src_veh.get_items( src_part ) ) { + temp_inv += it; + } } } - if( dangerous_field ) { - continue; + } + return needed_things.obj().can_make_with_inventory( temp_inv, is_crafting_component ); +} + +static std::pair can_do_activity_there( const activity_id &act, player &p, + const tripoint &src_loc ) +{ + // see activity_handlers.h cant_do_activity_reason enums + zone_manager &mgr = zone_manager::get_manager(); + std::vector zones; + if( act == activity_id( "ACT_MOVE_LOOT" ) ) { + // skip tiles in IGNORE zone and tiles on fire + // (to prevent taking out wood off the lit brazier) + // and inaccessible furniture, like filled charcoal kiln + if( mgr.has( zone_type_id( "LOOT_IGNORE" ), g->m.getabs( src_loc ) ) || + g->m.get_field( src_loc, fd_fire ) != nullptr || + !g->m.can_put_items_ter_furn( src_loc ) ) { + return std::make_pair( false, BLOCKING_TILE ); + } else { + return std::make_pair( true, CAN_DO_FETCH ); } - // work out if we can build it before we move there. - const std::vector &zones = mgr.get_zones( zone_type_id( "CONSTRUCTION_BLUEPRINT" ), - g->m.getabs( src_loc ) ); - construction built_chosen; - const inventory pre_inv = p.crafting_inventory( src_loc, PICKUP_RANGE - 1 ); + } + if( act == activity_id( "ACT_TIDY_UP" ) ) { + if( mgr.has_near( z_loot_unsorted, g->m.getabs( src_loc ), 60 ) ) { + return std::make_pair( true, CAN_DO_FETCH ); + } + return std::make_pair( false, NO_ZONE ); + } + if( act == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ) { + const std::vector &list_constructions = get_constructions(); + zones = mgr.get_zones( zone_type_id( "CONSTRUCTION_BLUEPRINT" ), + g->m.getabs( src_loc ) ); + partial_con *nc = g->m.partial_con_at( src_loc ); + // if theres a partial construction on the tile, then we can work on it, no need to check inventories. + if( nc ) { + const construction &built = list_constructions[nc->id]; + if( !character_has_skill_for( p, built ) ) { + return std::make_pair( false, DONT_HAVE_SKILL ); + } + return std::make_pair( true, CAN_DO_CONSTRUCTION ); + } + construction built_pre; // PICKUP_RANGE -1 because we will be adjacent to the spot when arriving. - bool found_any_pre = false; - + const inventory pre_inv = p.crafting_inventory( src_loc, PICKUP_RANGE - 1 ); for( const zone_data &zone : zones ) { const blueprint_options options = dynamic_cast( zone.get_options() ); const int index = options.get_index(); - const std::vector &list_constructions = get_constructions(); const construction &built = list_constructions[index]; // maybe it's already built? if( !built.post_terrain.empty() ) { if( built.post_is_furniture ) { - furn_id f = furn_id( built.post_terrain ); - if( g->m.furn( src_loc ) == f ) { - break; + if( furn_id( built.post_terrain ) == g->m.furn( src_loc ) ) { + return std::make_pair( false, ALREADY_DONE ); } } else { - ter_id t = ter_id( built.post_terrain ); - if( g->m.ter( src_loc ) == t ) { - break; + if( ter_id( built.post_terrain ) == g->m.ter( src_loc ) ) { + return std::make_pair( false, ALREADY_DONE ); } } } + if( can_construct( built, src_loc ) && player_can_build( p, pre_inv, built ) ) { - found_any_pre = true; - built_chosen = list_constructions[index]; - break; + return std::make_pair( true, CAN_DO_CONSTRUCTION ); } else { + // if both return false, then there is a pre_special requirement that failed. + if( !player_can_build( p, pre_inv, built ) && !can_construct( built, src_loc ) ) { + return std::make_pair( false, NO_ZONE ); + } + auto stuff_there = g->m.i_at( src_loc ); + if( !stuff_there.empty() ) { + return std::make_pair( false, BLOCKING_TILE ); + } + // there are no pre-requisites. + // so we need to potentially fetch components + if( built.pre_terrain.empty() && built.pre_special( src_loc ) ) { + return std::make_pair( false, NO_COMPONENTS ); + } else if( !built.pre_special( src_loc ) ) { + return std::make_pair( false, BLOCKING_TILE ); + } // cant build it // maybe we can build the pre-requisite instead // see if the reason is because of pre-terrain requirement - bool place_okay = true; - if( !built.pre_terrain.empty() ) { - if( built.pre_is_furniture ) { - furn_id f = furn_id( built.pre_terrain ); - place_okay &= g->m.furn( src_loc ) == f; - } else { - ter_id t = ter_id( built.pre_terrain ); - place_okay &= g->m.ter( src_loc ) == t; - } + if( !built.pre_terrain.empty() && ( ( built.pre_is_furniture && + furn_id( built.pre_terrain ) == g->m.furn( src_loc ) ) || ( !built.pre_is_furniture && + ter_id( built.pre_terrain ) == g->m.ter( src_loc ) ) ) ) { + // the pre-req is already built, so the reason is due to lack of tools/components + return std::make_pair( false, NO_COMPONENTS ); } - if( !place_okay ) { - built_chosen = check_build_pre( built ); - // We only got here because the original choice cant be constructed - // Check again, if we still have the same construction, itll fail again. - if( can_construct( built_chosen, src_loc ) && player_can_build( p, pre_inv, built_chosen ) ) { - found_any_pre = true; - break; + built_pre = check_build_pre( built ); + // the pre-terrain requirement is not a possible construction. + if( built_pre.id == built.id ) { + return std::make_pair( false, NO_ZONE ); + } + // so lets check if we can build the pre-req + if( can_construct( built_pre, src_loc ) && player_can_build( p, pre_inv, built_pre ) ) { + return std::make_pair( true, CAN_DO_PREREQ ); + } else { + // Ok we'll go one more pre-req deep - for things like doors and walls that have 3 steps. + construction built_pre_2 = check_build_pre( built_pre ); + if( built_pre.id == built_pre_2.id ) { + //the 2nd pre-req down is not possible to build + return std::make_pair( false, NO_ZONE ); + } + if( can_construct( built_pre, src_loc ) && player_can_build( p, pre_inv, built_pre ) ) { + return std::make_pair( true, CAN_DO_PREREQ_2 ); } + return std::make_pair( false, NO_COMPONENTS_PREREQ_2 ); } - continue; } } - if( !found_any_pre ) { - continue; - } - bool adjacent = false; - for( const tripoint &elem : g->m.points_in_radius( src_loc, 1 ) ) { - if( p.pos() == elem ) { - adjacent = true; - break; + } else if( act == activity_id( "ACT_MULTIPLE_FARM" ) ) { + zones = mgr.get_zones( zone_type_id( "FARM_PLOT" ), + g->m.getabs( src_loc ) ); + for( const zone_data &zone : zones ) { + if( g->m.has_flag_furn( "GROWTH_HARVEST", src_loc ) ) { + // simple work, pulling up plants, nothing else required. + return std::make_pair( true, NEEDS_HARVESTING ); + } else if( g->m.has_flag( "PLOWABLE", src_loc ) && !g->m.has_furn( src_loc ) ) { + if( p.has_quality( quality_id( "DIG" ), 1 ) ) { + // we have a shovel/hoe already, great + return std::make_pair( true, NEEDS_TILLING ); + } else { + // we need a shovel/hoe + return std::make_pair( false, NEEDS_TILLING ); + } + } else if( g->m.has_flag_ter_or_furn( "PLANTABLE", src_loc ) && warm_enough_to_plant( src_loc ) ) { + if( g->m.has_items( src_loc ) ) { + return std::make_pair( false, BLOCKING_TILE ); + } else { + // do we have the required seed on our person? + const auto options = dynamic_cast( zone.get_options() ); + const std::string seed = options.get_seed(); + std::vector seed_inv = p.items_with( []( const item & itm ) { + return itm.is_seed(); + } ); + for( const auto elem : seed_inv ) { + if( elem->typeId() == itype_id( seed ) ) { + return std::make_pair( true, NEEDS_PLANTING ); + } + } + // didnt find the seed, but maybe there are overlapping farm zones + // and another of the zones is for a seed that we have + // so loop again, and return false once all zones done. + } + + } else { + // cant plant, till or harvest + return std::make_pair( false, ALREADY_DONE ); } + } - if( !adjacent ) { - std::vector route = route_adjacent( p, src_loc ); + // looped through all zones, and only got here if its plantable, but have no seeds. + return std::make_pair( false, NEEDS_PLANTING ); + } else if( act == activity_id( "ACT_FETCH_REQUIRED" ) ) { + // we check if its possible to get all the requirements for fetching at two other places. + // 1. before we even assign the fetch activity and; + // 2. when we form the src_set to loop through at the beginning of the fetch activity. + return std::make_pair( true, CAN_DO_FETCH ); + } + // Shouldnt get here because the zones were checked previously. if it does, set enum reason as "no zone" + return std::make_pair( false, NO_ZONE ); +} - // check if we found path to source / adjacent tile - if( route.empty() ) { - add_msg( m_info, _( "%s can't reach the source tile to construct." ), - p.disp_name() ); - return; +static std::vector> requirements_map( player &p ) +{ + const requirement_data things_to_fetch = requirement_id( p.backlog.front().str_values[0] ).obj(); + const activity_id activity_to_restore = p.backlog.front().id(); + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) + requirement_id things_to_fetch_id = things_to_fetch.id(); + std::vector> req_comps = things_to_fetch.get_components(); + std::vector> tool_comps = things_to_fetch.get_tools(); + std::vector> quality_comps = things_to_fetch.get_qualities(); + const units::volume volume_allowed = p.volume_capacity() - p.volume_carried(); + const units::mass weight_allowed = p.weight_capacity() - p.weight_carried(); + zone_manager &mgr = zone_manager::get_manager(); + const bool pickup_task = p.backlog.front().id() == activity_id( "ACT_MULTIPLE_FARM" ); + // where it is, what it is, how much of it, and how much in total is required of that item. + std::vector> requirement_map; + std::vector> final_map; + std::vector loot_spots; + std::vector already_there_spots; + std::vector combined_spots; + std::map total_map; + for( const auto elem : g->m.points_in_radius( g->m.getlocal( p.backlog.front().placement ), + PICKUP_RANGE - 1 ) ) { + already_there_spots.push_back( elem ); + combined_spots.push_back( elem ); + } + for( const tripoint elem : mgr.get_point_set_loot( g->m.getabs( p.pos() ), 60, p.is_npc() ) ) { + // if there is a loot zone thats already near the work spot, we dont want it to be added twice. + if( std::find( already_there_spots.begin(), already_there_spots.end(), + elem ) != already_there_spots.end() ) { + // construction tasks dont need the loot spot *and* the already_there/cmbined spots both added. + // but a farming task will need to go and fetch the tool no matter if its near the work spot. + // wheras the construction will automaticlaly use whats nearby anyway. + if( pickup_task ) { + loot_spots.push_back( elem ); + } else { + continue; + } + } else { + loot_spots.push_back( elem ); + combined_spots.push_back( elem ); + } + } + // if the requirements arent available, then stop. + if( !are_requirements_nearby( pickup_task ? loot_spots : combined_spots, things_to_fetch_id, p, + activity_to_restore, pickup_task ) ) { + return requirement_map; + } + // if the requirements are already near the work spot and its a construction/crafting task, then no need to fetch anything more. + if( !pickup_task && + are_requirements_nearby( already_there_spots, things_to_fetch_id, p, activity_to_restore, + false ) ) { + return requirement_map; + } + // a vector of every item in every tile that matches any part of the requirements. + // will be filtered for amounts/charges afterwards. + for( tripoint point_elem : pickup_task ? loot_spots : combined_spots ) { + std::map temp_map; + for( auto &stack_elem : g->m.i_at( point_elem ) ) { + for( std::vector &elem : req_comps ) { + for( item_comp &comp_elem : elem ) { + if( comp_elem.type == stack_elem.typeId() ) { + // if its near the work site, we can remove a count from the requirements. + // if two "lines" of the requirement have the same component appearing again + // that is fine, we will choose which "line" to fulfill later, and the decrement will count towards that then. + if( !pickup_task && + std::find( already_there_spots.begin(), already_there_spots.end(), + point_elem ) != already_there_spots.end() ) { + comp_elem.count -= stack_elem.count(); + } + temp_map[stack_elem.typeId()] += stack_elem.count(); + } + } + } + for( std::vector &elem : tool_comps ) { + for( tool_comp &comp_elem : elem ) { + if( comp_elem.type == stack_elem.typeId() ) { + if( !pickup_task && + std::find( already_there_spots.begin(), already_there_spots.end(), + point_elem ) != already_there_spots.end() ) { + comp_elem.count -= stack_elem.count(); + } + temp_map[stack_elem.typeId()] += stack_elem.count(); + } + } } - // set the destination and restart activity after player arrives there - // we don't need to check for safe mode, - // activity will be restarted only if - // player arrives on destination tile - p.set_destination( route, player_activity( act_multiple_construction ) ); - return; + for( std::vector &elem : quality_comps ) { + for( quality_requirement &comp_elem : elem ) { + const quality_id tool_qual = comp_elem.type; + const int qual_level = comp_elem.level; + if( stack_elem.has_quality( tool_qual, qual_level ) ) { + // check for weight/volume if its a task that involves needing a carried tool + // this is a shovel, we can just return this, nothing else is needed. + if( pickup_task && stack_elem.volume() < volume_allowed && + stack_elem.weight() < weight_allowed && + std::find( loot_spots.begin(), loot_spots.end(), point_elem ) != loot_spots.end() ) { + std::vector> ret; + ret.push_back( std::make_tuple( point_elem, stack_elem.typeId(), 1 ) ); + return ret; + } + if( !pickup_task && + std::find( already_there_spots.begin(), already_there_spots.end(), + point_elem ) != already_there_spots.end() ) { + comp_elem.count -= stack_elem.count(); + } + temp_map[stack_elem.typeId()] += stack_elem.count(); + } + } + } } - // if it's too dark to construct there - const bool enough_light = p.fine_detail_vision_mod() <= 4; - if( !enough_light ) { - p.add_msg_if_player( m_info, _( "It is too dark to construct anything." ) ); - return; + for( auto map_elem : temp_map ) { + total_map[map_elem.first] += map_elem.second; + // if its a construction/crafting task, we can discount any items already near the work spot + // we dont need to fetch those, they will be used automatically in the construction. + // a shovel for tilling, for example, however, needs to be picked up, no matter if its near the spot or not. + if( !pickup_task ) { + if( std::find( already_there_spots.begin(), already_there_spots.end(), + point_elem ) != already_there_spots.end() ) { + continue; + } + } + requirement_map.push_back( std::make_tuple( point_elem, map_elem.first, map_elem.second ) ); } - // check if can do the construction now we are actually there - const std::vector &post_zones = mgr.get_zones( zone_type_id( "CONSTRUCTION_BLUEPRINT" ), - g->m.getabs( src_loc ) ); - construction post_built_chosen; - p.invalidate_crafting_inventory(); - const inventory &total_inv = p.crafting_inventory(); - bool found_any = false; - - for( const zone_data &zone : post_zones ) { - const blueprint_options options = dynamic_cast( zone.get_options() ); - const int index = options.get_index(); - const std::vector &list_constructions = get_constructions(); - const construction &built = list_constructions[index]; - // maybe it's already built? - if( !built.post_terrain.empty() ) { - if( built.post_is_furniture ) { - furn_id f = furn_id( built.post_terrain ); - if( g->m.furn( src_loc ) == f ) { + } + // Ok we now have a list of all the items that match the requirements, their points, and a quantity for each one. + // we need to consolidate them, and winnow it down to the minimum required counts, instead of all matching. + for( std::vector &elem : req_comps ) { + bool line_found = false; + for( item_comp &comp_elem : elem ) { + if( line_found || comp_elem.count <= 0 ) { + break; + } + int quantity_required = comp_elem.count; + int item_quantity = 0; + auto it = requirement_map.begin(); + int remainder = 0; + while( it != requirement_map.end() ) { + tripoint pos_here = std::get<0>( *it ); + itype_id item_here = std::get<1>( *it ); + int quantity_here = std::get<2>( *it ); + if( comp_elem.type == item_here ) { + item_quantity += quantity_here; + } + if( item_quantity >= quantity_required ) { + // it's just this spot that can fulfil the requirement on its own + final_map.push_back( std::make_tuple( pos_here, item_here, std::min( quantity_here, + quantity_required ) ) ); + if( quantity_here >= quantity_required ) { + line_found = true; break; + } else { + remainder = quantity_required - quantity_here; } - } else { - ter_id t = ter_id( built.post_terrain ); - if( g->m.ter( src_loc ) == t ) { + break; + } + it++; + } + if( line_found ) { + while( true ) { + // go back over things + if( it == requirement_map.begin() ) { break; } + if( remainder <= 0 ) { + line_found = true; + break; + } + tripoint pos_here2 = std::get<0>( *it ); + itype_id item_here2 = std::get<1>( *it ); + int quantity_here2 = std::get<2>( *it ); + if( comp_elem.type == item_here2 ) { + if( quantity_here2 >= remainder ) { + final_map.push_back( std::make_tuple( pos_here2, item_here2, remainder ) ); + line_found = true; + } else { + final_map.push_back( std::make_tuple( pos_here2, item_here2, remainder ) ); + remainder -= quantity_here2; + } + } + it--; } } - if( can_construct( built, src_loc ) && player_can_build( p, total_inv, built ) ) { - found_any = true; - post_built_chosen = list_constructions[index]; + } + } + for( std::vector &elem : tool_comps ) { + bool line_found = false; + for( tool_comp &comp_elem : elem ) { + if( line_found || comp_elem.count <= 0 ) { break; - } else { - // cant build it - // maybe we can build the pre-requisite instead - // see if the reason is because of pre-terrain requirement - bool place_okay = true; - if( !built.pre_terrain.empty() ) { - if( built.pre_is_furniture ) { - furn_id f = furn_id( built.pre_terrain ); - place_okay &= g->m.furn( src_loc ) == f; + } + int quantity_required = comp_elem.count; + int item_quantity = 0; + auto it = requirement_map.begin(); + int remainder = 0; + while( it != requirement_map.end() ) { + tripoint pos_here = std::get<0>( *it ); + itype_id item_here = std::get<1>( *it ); + int quantity_here = std::get<2>( *it ); + if( comp_elem.type == item_here ) { + item_quantity += quantity_here; + } + if( item_quantity >= quantity_required ) { + // it's just this spot that can fulfil the requirement on its own + final_map.push_back( std::make_tuple( pos_here, item_here, std::min( quantity_here, + quantity_required ) ) ); + if( quantity_here >= quantity_required ) { + line_found = true; + break; } else { - ter_id t = ter_id( built.pre_terrain ); - place_okay &= g->m.ter( src_loc ) == t; + remainder = quantity_required - quantity_here; } + break; } - if( !place_okay ) { - post_built_chosen = check_build_pre( built ); - if( can_construct( post_built_chosen, src_loc ) && - player_can_build( p, total_inv, post_built_chosen ) ) { - found_any = true; + it++; + } + if( line_found ) { + while( true ) { + // go back over things + if( it == requirement_map.begin() ) { break; } + if( remainder <= 0 ) { + line_found = true; + break; + } + tripoint pos_here2 = std::get<0>( *it ); + itype_id item_here2 = std::get<1>( *it ); + int quantity_here2 = std::get<2>( *it ); + if( comp_elem.type == item_here2 ) { + if( quantity_here2 >= remainder ) { + final_map.push_back( std::make_tuple( pos_here2, item_here2, remainder ) ); + line_found = true; + } else { + final_map.push_back( std::make_tuple( pos_here2, item_here2, remainder ) ); + remainder -= quantity_here2; + } + } + it--; } - continue; } } - if( !found_any ) { - continue; + } + for( std::vector &elem : quality_comps ) { + bool line_found = false; + for( quality_requirement &comp_elem : elem ) { + if( line_found || comp_elem.count <= 0 ) { + break; + } + const quality_id tool_qual = comp_elem.type; + const int qual_level = comp_elem.level; + for( auto it = requirement_map.begin(); it != requirement_map.end(); ) { + tripoint pos_here = std::get<0>( *it ); + itype_id item_here = std::get<1>( *it ); + item test_item = item( item_here, 0 ); + if( test_item.has_quality( tool_qual, qual_level ) ) { + // it's just this spot that can fulfil the requirement on its own + final_map.push_back( std::make_tuple( pos_here, item_here, 1 ) ); + line_found = true; + break; + } + it++; + } } - std::list used; - // create the partial construction struct - partial_con pc; - pc.id = built_chosen.id; - pc.counter = 0; - // Set the trap that has the examine function - if( g->m.tr_at( src_loc ).loadid == tr_null ) { - g->m.trap_set( src_loc, tr_unfinished_construction ); - } - // Use up the components - for( const std::vector &it : built_chosen.requirements->get_components() ) { - std::list tmp = p.consume_items( it, 1, is_crafting_component ); - used.splice( used.end(), tmp ); - } - pc.components = used; - g->m.partial_con_set( src_loc, pc ); - for( const std::vector &it : built_chosen.requirements->get_tools() ) { - p.consume_tools( it ); - } - p.backlog.push_front( act_multiple_construction ); - p.assign_activity( activity_id( "ACT_BUILD" ) ); - p.activity.placement = g->m.getabs( src_loc ); - return; } - if( p.moves <= 0 ) { - // Restart activity and break from cycle. - p.assign_activity( act_multiple_construction ); - return; + return final_map; +} + +static bool plant_activity( player &p, const zone_data *zone, const tripoint src_loc ) +{ + const std::string seed = dynamic_cast( zone->get_options() ).get_seed(); + std::vector seed_inv = p.items_with( [seed]( const item & itm ) { + return itm.typeId() == itype_id( seed ); + } ); + // we dont have the required seed, even though we should at this point. + // move onto the next tile, and if need be that will prompt a fetch seeds activity. + if( seed_inv.empty() ) { + return false; } + iexamine::plant_seed( p, src_loc, itype_id( seed ) ); + return true; +} - // If we got here without restarting the activity, it means we're done. - if( p.is_npc() ) { - npc *guy = dynamic_cast( &p ); - guy->current_activity_id = activity_id::NULL_ID(); - guy->revert_after_activity(); +static void construction_activity( player &p, const zone_data *zone, const tripoint src_loc, + do_activity_reason reason, const std::vector &list_constructions, + activity_id activity_to_restore ) +{ + const blueprint_options options = dynamic_cast( zone->get_options() ); + // the actual desired construction + construction built_chosen; + if( reason == CAN_DO_CONSTRUCTION ) { + built_chosen = list_constructions[options.get_index()]; + } else if( reason == CAN_DO_PREREQ ) { + built_chosen = check_build_pre( list_constructions[options.get_index()] ); + } else { + built_chosen = check_build_pre( check_build_pre( list_constructions[options.get_index()] ) ); + } + std::list used; + // create the partial construction struct + partial_con pc; + pc.id = built_chosen.id; + pc.counter = 0; + // Set the trap that has the examine function + if( g->m.tr_at( src_loc ).loadid == tr_null ) { + g->m.trap_set( src_loc, tr_unfinished_construction ); + } + // Use up the components + for( const std::vector &it : built_chosen.requirements->get_components() ) { + std::list tmp = p.consume_items( it, 1, is_crafting_component ); + used.splice( used.end(), tmp ); + } + pc.components = used; + g->m.partial_con_set( src_loc, pc ); + for( const std::vector &it : built_chosen.requirements->get_tools() ) { + p.consume_tools( it ); } + p.backlog.push_front( activity_to_restore ); + p.assign_activity( activity_id( "ACT_BUILD" ) ); + p.activity.placement = g->m.getabs( src_loc ); } -void activity_on_turn_move_loot( player_activity &, player &p ) +static bool tidy_activity( player &p, const tripoint src_loc, activity_id activity_to_restore ) { - const activity_id act_move_loot = activity_id( "ACT_MOVE_LOOT" ); auto &mgr = zone_manager::get_manager(); - if( g->m.check_vehicle_zones( g->get_levz() ) ) { - mgr.cache_vzones(); + tripoint loot_abspos = g->m.getabs( src_loc ); + tripoint loot_src_lot; + if( mgr.has_near( z_loot_unsorted, loot_abspos, 60 ) ) { + const auto &zone_src_set = mgr.get_near( zone_type_id( "LOOT_UNSORTED" ), loot_abspos, 60 ); + const auto &zone_src_sorted = get_sorted_tiles_by_distance( loot_abspos, zone_src_set ); + // Find the nearest unsorted zone to dump objects at + for( auto &src_elem : zone_src_sorted ) { + if( !g->m.can_put_items_ter_furn( g->m.getlocal( src_elem ) ) ) { + continue; + } + loot_src_lot = g->m.getlocal( src_elem ); + break; + } + } + if( loot_src_lot == tripoint_zero ) { + return false; } - const auto abspos = g->m.getabs( p.pos() ); - const auto &src_set = mgr.get_near( zone_type_id( "LOOT_UNSORTED" ), abspos ); + auto items_there = g->m.i_at( src_loc ); + vehicle *dest_veh; + int dest_part; + if( const cata::optional vp = g->m.veh_at( + loot_src_lot ).part_with_feature( "CARGO", + false ) ) { + dest_veh = &vp->vehicle(); + dest_part = vp->part_index(); + } else { + dest_veh = nullptr; + dest_part = -1; + } + for( auto &it : items_there ) { + if( it.has_var( "activity_var" ) && it.get_var( "activity_var", "" ) == p.name ) { + move_item( p, it, it.count(), src_loc, loot_src_lot, dest_veh, dest_part, + activity_to_restore ); + break; + } + } + // we are adjacent to an unsorted zone, we came here to just drop items we are carrying + if( mgr.has( zone_type_id( z_loot_unsorted ), g->m.getabs( src_loc ) ) ) { + for( auto inv_elem : p.inv_dump() ) { + if( inv_elem->has_var( "activity_var" ) ) { + inv_elem->erase_var( "activity_var" ); + p.drop( p.get_item_position( inv_elem ), src_loc ); + } + } + } + return true; +} + +static void fetch_activity( player &p, const tripoint src_loc, activity_id activity_to_restore ) +{ + if( !g->m.can_put_items_ter_furn( g->m.getlocal( p.backlog.front().coords.back() ) ) ) { + return; + } + const std::vector> mental_map_2 = requirements_map( p ); + int pickup_count = 1; + auto items_there = g->m.i_at( src_loc ); + vehicle *src_veh = nullptr; + int src_part = 0; + if( const cata::optional vp = g->m.veh_at( src_loc ).part_with_feature( "CARGO", + false ) ) { + src_veh = &vp->vehicle(); + src_part = vp->part_index(); + } + std::string picked_up; + const units::volume volume_allowed = p.volume_capacity() - p.volume_carried(); + const units::mass weight_allowed = p.weight_capacity() - p.weight_carried(); + // TODO : vehicle_stack and map_stack into one loop. + if( src_veh ) { + for( auto &veh_elem : src_veh->get_items( src_part ) ) { + for( auto elem : mental_map_2 ) { + if( std::get<0>( elem ) == src_loc && veh_elem.typeId() == std::get<1>( elem ) ) { + if( !p.backlog.empty() && p.backlog.front().id() == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ) { + move_item( p, veh_elem, veh_elem.count_by_charges() ? std::get<2>( elem ) : 1, src_loc, + g->m.getlocal( p.backlog.front().coords.back() ), src_veh, src_part, activity_to_restore ); + return; + } + } + } + } + } + for( auto it = items_there.begin(); it != items_there.end(); it++ ) { + for( auto elem : mental_map_2 ) { + if( std::get<0>( elem ) == src_loc && it->typeId() == std::get<1>( elem ) ) { + // construction/crafting tasks want the requred item moved near the work spot. + if( !p.backlog.empty() && p.backlog.front().id() == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ) { + move_item( p, *it, it->count_by_charges() ? std::get<2>( elem ) : 1, src_loc, + g->m.getlocal( p.backlog.front().coords.back() ), src_veh, src_part, activity_to_restore ); + return; + // other tasks want the tool picked up + } else if( !p.backlog.empty() && p.backlog.front().id() == activity_id( "ACT_MULTIPLE_FARM" ) ) { + if( it->volume() > volume_allowed || it->weight() > weight_allowed ) { + continue; + } + item leftovers = *it; + + if( pickup_count != 1 && it->count_by_charges() ) { + // Reinserting leftovers happens after item removal to avoid stacking issues. + leftovers.charges = it->charges - pickup_count; + if( leftovers.charges > 0 ) { + it->charges = pickup_count; + } + } else { + leftovers.charges = 0; + } + it->set_var( "activity_var", p.name ); + p.i_add( *it ); + picked_up = it->tname(); + items_there.erase( it ); + // If we didn't pick up a whole stack, put the remainder back where it came from. + if( leftovers.charges > 0 ) { + g->m.add_item_or_charges( src_loc, leftovers ); + } + if( p.is_npc() && !picked_up.empty() ) { + if( pickup_count == 1 ) { + add_msg( _( "%1$s picks up a %2$s." ), p.disp_name(), picked_up ); + } else { + add_msg( _( "%s picks up several items." ), p.disp_name() ); + } + } + return; + } + } + } + } +} + +static bool move_loot_activity( player &p, tripoint src_loc, zone_manager &mgr, + activity_id activity_to_restore ) +{ + // the boolean in this pair being true indicates the item is from a vehicle storage space + auto items = std::vector>(); vehicle *src_veh, *dest_veh; int src_part, dest_part; + tripoint src = g->m.getabs( src_loc ); + tripoint abspos = g->m.getabs( p.pos() ); + //Check source for cargo part + //map_stack and vehicle_stack are different types but inherit from item_stack + // TODO: use one for loop + if( const cata::optional vp = g->m.veh_at( src_loc ).part_with_feature( "CARGO", + false ) ) { + src_veh = &vp->vehicle(); + src_part = vp->part_index(); + for( auto &it : src_veh->get_items( src_part ) ) { + items.push_back( std::make_pair( &it, true ) ); + } + } else { + src_veh = nullptr; + src_part = -1; + } + for( auto &it : g->m.i_at( src_loc ) ) { + items.push_back( std::make_pair( &it, false ) ); + } + //Skip items that have already been processed + for( auto it = items.begin() + mgr.get_num_processed( src ); it < items.end(); it++ ) { - // Nuke the current activity, leaving the backlog alone. - p.activity = player_activity(); + mgr.increment_num_processed( src ); + + const auto thisitem = it->first; + + if( thisitem->made_of_from_type( LIQUID ) ) { // skip unpickable liquid + continue; + } + + // Only if it's from a vehicle do we use the vehicle source location information. + vehicle *this_veh = it->second ? src_veh : nullptr; + const int this_part = it->second ? src_part : -1; - // sort source tiles by distance - const auto &src_sorted = get_sorted_tiles_by_distance( abspos, src_set ); + const auto id = mgr.get_near_zone_type_for_item( *thisitem, abspos ); - if( !mgr.is_sorting() ) { - mgr.start_sort( src_sorted ); + // checks whether the item is already on correct loot zone or not + // if it is, we can skip such item, if not we move the item to correct pile + // think empty bag on food pile, after you ate the content + if( !mgr.has( id, src ) ) { + const auto &dest_set = mgr.get_near( id, abspos ); + + for( auto &dest : dest_set ) { + const auto &dest_loc = g->m.getlocal( dest ); + + //Check destination for cargo part + if( const cata::optional vp = g->m.veh_at( dest_loc ).part_with_feature( "CARGO", + false ) ) { + dest_veh = &vp->vehicle(); + dest_part = vp->part_index(); + } else { + dest_veh = nullptr; + dest_part = -1; + } + + // skip tiles with inaccessible furniture, like filled charcoal kiln + if( !g->m.can_put_items_ter_furn( dest_loc ) ) { + continue; + } + + units::volume free_space; + // if there's a vehicle with space do not check the tile beneath + if( dest_veh ) { + free_space = dest_veh->free_volume( dest_part ); + } else { + free_space = g->m.free_volume( dest_loc ); + } + // check free space at destination + if( free_space >= thisitem->volume() ) { + move_item( p, *thisitem, thisitem->count(), src_loc, dest_loc, this_veh, this_part ); + + // moved item away from source so decrement + mgr.decrement_num_processed( src ); + + break; + } + } + if( p.moves <= 0 ) { + // Restart activity and break from cycle. + p.assign_activity( activity_to_restore ); + mgr.end_sort(); + return true; + } + } } + return false; +} - for( auto &src : src_sorted ) { - const auto &src_loc = g->m.getlocal( src ); +static std::string random_string( size_t length ) +{ + auto randchar = []() -> char { + const char charset[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = ( sizeof( charset ) - 1 ); + return charset[rand() % max_index]; + }; + std::string str( length, 0 ); + std::generate_n( str.begin(), length, randchar ); + return str; +} + +void generic_multi_activity_handler( player_activity &act, player &p ) +{ + // First get the things that are activity-agnostic. + zone_manager &mgr = zone_manager::get_manager(); + const tripoint abspos = g->m.getabs( p.pos() ); + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) + activity_id activity_to_restore = act.id(); + const tripoint localpos = p.pos(); + bool dark_capable = false; + // the set of target work spots - potentally after we have fetched required tools. + std::unordered_set src_set; + // we may need a list of all constructions later. + const std::vector &list_constructions = get_constructions(); + // Nuke the current activity, leaving the backlog alone + p.activity = player_activity(); + // now we setup the target spots based on whch activity is occuring + if( activity_to_restore == activity_id( "ACT_MOVE_LOOT" ) ) { + dark_capable = true; + if( g->m.check_vehicle_zones( g->get_levz() ) ) { + mgr.cache_vzones(); + } + src_set = mgr.get_near( zone_type_id( "LOOT_UNSORTED" ), abspos ); + } + if( activity_to_restore == activity_id( "ACT_TIDY_UP" ) ) { + dark_capable = true; + tripoint unsorted_spot; + for( const tripoint elem : g->m.points_in_radius( g->m.getlocal( abspos ), 60 ) ) { + if( mgr.has( zone_type_id( z_loot_unsorted ), g->m.getabs( elem ) ) ) { + // it already has a unsorted loot spot, and therefore dont need to go and pick up items there. + if( unsorted_spot == tripoint_zero ) { + unsorted_spot = elem; + } + continue; + } + for( const auto &stack_elem : g->m.i_at( elem ) ) { + if( stack_elem.has_var( "activity_var" ) && stack_elem.get_var( "activity_var", "" ) == p.name ) { + const furn_t &f = g->m.furn( elem ).obj(); + if( !f.has_flag( "PLANT" ) ) { + src_set.insert( g->m.getabs( elem ) ); + break; + } + } + } + } + if( src_set.empty() && unsorted_spot != tripoint_zero ) { + for( auto inv_elem : p.inv_dump() ) { + if( inv_elem->has_var( "activity_var" ) ) { + // we've gone to tidy up all the thngs lying around, now tidy up the things we picked up. + src_set.insert( g->m.getabs( unsorted_spot ) ); + break; + } + } + } + } + // multiple construction will form a list of targets based on blueprint zones and unfinished constructions + if( activity_to_restore == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ) { + src_set = mgr.get_near( zone_type_id( "CONSTRUCTION_BLUEPRINT" ), abspos, 60 ); + for( const tripoint &elem : g->m.points_in_radius( localpos, 40 ) ) { + partial_con *pc = g->m.partial_con_at( elem ); + if( pc ) { + src_set.insert( g->m.getabs( elem ) ); + } + } + // farming activies encompass tilling, planting, harvesting. + } + if( activity_to_restore == activity_id( "ACT_MULTIPLE_FARM" ) ) { + src_set = mgr.get_near( zone_type_id( "FARM_PLOT" ), abspos, 60 ); + // fetch required will always be following on from a previous activity + } + if( activity_to_restore == activity_id( "ACT_FETCH_REQUIRED" ) ) { + dark_capable = true; + // get the right zones for the items in the requirements. + // we previously checked if the items are nearby before we set the fetch task + // but we will check again later, to be sure nothings changed. + std::vector> mental_map = requirements_map( p ); + for( auto elem : mental_map ) { + tripoint elem_point = std::get<0>( elem ); + src_set.insert( g->m.getabs( elem_point ) ); + } + } + // prune the set to remove tiles that are never gonna work out. + for( auto it2 = src_set.begin(); it2 != src_set.end(); ) { + // remove dangerous tiles + tripoint set_pt = g->m.getlocal( *it2 ); + if( g->m.dangerous_field_at( set_pt ) ) { + it2 = src_set.erase( it2 ); + // remove tiles in darkness, if we arent lit-up ourselves + } else if( !dark_capable && p.fine_detail_vision_mod( set_pt ) > 4.0 ) { + it2 = src_set.erase( it2 ); + } else { + ++it2; + } + } + // now we have our final set of points + std::vector src_sorted = get_sorted_tiles_by_distance( abspos, src_set ); + if( activity_to_restore == activity_id( "ACT_MOVE_LOOT" ) ) { + if( !mgr.is_sorting() ) { + mgr.start_sort( src_sorted ); + } + } + // now loop through the work-spot tiles and judge whether its worth travelling to it yet + // or if we need to fetch something first. + for( const tripoint &src : src_sorted ) { + const tripoint &src_loc = g->m.getlocal( src ); if( !g->m.inbounds( src_loc ) ) { if( !g->m.inbounds( p.pos() ) ) { // p is implicitly an NPC that has been moved off the map, so reset the activity // and unload them - p.assign_activity( act_move_loot ); + p.assign_activity( activity_to_restore ); p.set_moves( 0 ); g->reload_npcs(); - mgr.end_sort(); + if( activity_to_restore == activity_id( "ACT_MOVE_LOOT" ) ) { + mgr.end_sort(); + } return; } - std::vector route; - route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(), - p.get_path_avoid() ); + const std::vector route = route_adjacent( p, src_loc ); if( route.empty() ) { // can't get there, can't do anything, skip it continue; } - p.set_destination( route, player_activity( act_move_loot ) ); - mgr.end_sort(); + p.set_moves( 0 ); + p.set_destination( route, player_activity( activity_to_restore ) ); return; } - - bool is_adjacent_or_closer = square_dist( p.pos(), src_loc ) <= 1; - - // skip tiles in IGNORE zone and tiles on fire - // (to prevent taking out wood off the lit brazier) - // and inaccessible furniture, like filled charcoal kiln - if( mgr.has( zone_type_id( "LOOT_IGNORE" ), src ) || - g->m.get_field( src_loc, fd_fire ) != nullptr || - !g->m.can_put_items_ter_furn( src_loc ) ) { + std::pair check_can_do = can_do_activity_there( activity_to_restore, p, + src_loc ); + const bool can_do_it = check_can_do.first; + const do_activity_reason reason = check_can_do.second; + const zone_data *zone = mgr.get_zone_at( src ); + const bool needs_to_be_in_zone = activity_to_restore == activity_id( "ACT_FETCH_REQUIRED" ) || + activity_to_restore == activity_id( "ACT_MULTIPLE_FARM" ) || + ( activity_to_restore == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) && + !g->m.partial_con_at( src_loc ) ); + // some activities require the target tile to be part of a zone. + // tidy up activity dosnt - it wants things that may not be in a zone already - things that may have been left lying around. + if( needs_to_be_in_zone && !zone ) { continue; } - - // the boolean in this pair being true indicates the item is from a vehicle storage space - auto items = std::vector>(); - - //Check source for cargo part - //map_stack and vehicle_stack are different types but inherit from item_stack - // TODO: use one for loop - if( const cata::optional vp = g->m.veh_at( src_loc ).part_with_feature( "CARGO", - false ) ) { - src_veh = &vp->vehicle(); - src_part = vp->part_index(); - for( auto &it : src_veh->get_items( src_part ) ) { - items.push_back( std::make_pair( &it, true ) ); + if( ( !can_do_it ) && ( reason == DONT_HAVE_SKILL || reason == NO_ZONE || reason == ALREADY_DONE || + reason == BLOCKING_TILE || reason == UNKNOWN_ACTIVITY ) ) { + // we can discount this tile, the work can't be done. + if( reason == DONT_HAVE_SKILL ) { + p.add_msg_if_player( m_info, _( "You don't have the skill for this task." ) ); + } else if( reason == BLOCKING_TILE ) { + p.add_msg_if_player( m_info, _( "There is something blocking the location for this task." ) ); + } else { + p.add_msg_if_player( m_info, _( "You cannot do this task there." ) ); } - } else { - src_veh = nullptr; - src_part = -1; - } - for( auto &it : g->m.i_at( src_loc ) ) { - items.push_back( std::make_pair( &it, false ) ); - } - //Skip items that have already been processed - for( auto it = items.begin() + mgr.get_num_processed( src ); it < items.end(); it++ ) { - - mgr.increment_num_processed( src ); - - const auto thisitem = it->first; - - if( thisitem->made_of_from_type( LIQUID ) ) { // skip unpickable liquid - continue; + continue; + } else if( ( !can_do_it ) && ( reason == NO_COMPONENTS || reason == NEEDS_PLANTING || + reason == NEEDS_TILLING ) ) { + // we can do it, but we need to fetch some stuff first + // before we set the task to fetch components - is it even worth it? are the components anywhere? + requirement_id what_we_need; + std::vector loot_zone_spots; + std::vector combined_spots; + for( const tripoint elem : mgr.get_point_set_loot( abspos, 60, p.is_npc() ) ) { + loot_zone_spots.push_back( elem ); + combined_spots.push_back( elem ); } - - // Only if it's from a vehicle do we use the vehicle source location information. - vehicle *this_veh = it->second ? src_veh : nullptr; - const int this_part = it->second ? src_part : -1; - - const auto id = mgr.get_near_zone_type_for_item( *thisitem, abspos ); - - // checks whether the item is already on correct loot zone or not - // if it is, we can skip such item, if not we move the item to correct pile - // think empty bag on food pile, after you ate the content - if( !mgr.has( id, src ) ) { - const auto &dest_set = mgr.get_near( id, abspos, 60, thisitem ); - - for( auto &dest : dest_set ) { - const auto &dest_loc = g->m.getlocal( dest ); - - //Check destination for cargo part - if( const cata::optional vp = g->m.veh_at( dest_loc ).part_with_feature( "CARGO", - false ) ) { - dest_veh = &vp->vehicle(); - dest_part = vp->part_index(); - } else { - dest_veh = nullptr; - dest_part = -1; + for( const tripoint elem : g->m.points_in_radius( src_loc, PICKUP_RANGE - 1 ) ) { + combined_spots.push_back( elem ); + } + if( ( reason == NO_COMPONENTS || reason == NO_COMPONENTS_PREREQ || + reason == NO_COMPONENTS_PREREQ_2 ) && + activity_to_restore == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ) { + // its a construction and we need the components. + const blueprint_options options = dynamic_cast( zone->get_options() ); + construction built_chosen; + if( reason == NO_COMPONENTS ) { + built_chosen = list_constructions[options.get_index()]; + } else if( reason == NO_COMPONENTS_PREREQ ) { + built_chosen = check_build_pre( list_constructions[options.get_index()] ); + } else { + built_chosen = check_build_pre( check_build_pre( list_constructions[options.get_index()] ) ); + } + what_we_need = built_chosen.requirements; + } else if( reason == NEEDS_TILLING || reason == NEEDS_PLANTING ) { + std::vector> requirement_comp_vector; + std::vector> quality_comp_vector; + std::vector> tool_comp_vector; + if( reason == NEEDS_TILLING ) { + quality_comp_vector.push_back( std::vector { quality_requirement( quality_id( "DIG" ), 1, 1 ) } ); + } else if( reason == NEEDS_PLANTING ) { + requirement_comp_vector.push_back( std::vector { item_comp( itype_id( dynamic_cast + ( zone->get_options() ).get_seed() ), 1 ) + } ); + } + // ok, we need a shovel/hoe/axe/etc + // this is an activity that only requires this one tool, so we will fetch and wield it. + requirement_data reqs_data = requirement_data( tool_comp_vector, quality_comp_vector, + requirement_comp_vector ); + const std::string ran_str = random_string( 10 ); + const requirement_id req_id( ran_str ); + requirement_data::save_requirement( reqs_data, req_id ); + what_we_need = req_id; + } + bool tool_pickup = reason == NEEDS_TILLING || reason == NEEDS_PLANTING; + // is it even worth fetching anything if there isnt enough nearby? + if( !are_requirements_nearby( tool_pickup ? loot_zone_spots : combined_spots, what_we_need, p, + activity_to_restore, tool_pickup ) ) { + p.add_msg_if_player( m_info, _( "The required items are not available to complete this task." ) ); + continue; + } else { + p.backlog.push_front( activity_to_restore ); + p.assign_activity( activity_id( "ACT_FETCH_REQUIRED" ) ); + p.backlog.front().str_values.push_back( what_we_need.str() ); + p.backlog.front().values.push_back( reason ); + // come back here after succesfully fetching your stuff + std::vector candidates; + if( p.backlog.front().coords.empty() ) { + std::vector local_src_set; + for( const auto elem : src_set ) { + local_src_set.push_back( g->m.getlocal( elem ) ); } - - // skip tiles with inaccessible furniture, like filled charcoal kiln - if( !g->m.can_put_items_ter_furn( dest_loc ) ) { - continue; + std::vector candidates; + for( const auto point_elem : g->m.points_in_radius( src_loc, PICKUP_RANGE - 1 ) ) { + // we dont want to place the components where they could interfere with our ( or someone elses ) construction spots + if( ( std::find( local_src_set.begin(), local_src_set.end(), + point_elem ) != local_src_set.end() ) || !g->m.can_put_items_ter_furn( point_elem ) ) { + continue; + } + candidates.push_back( point_elem ); } - - units::volume free_space; - // if there's a vehicle with space do not check the tile beneath - if( dest_veh ) { - free_space = dest_veh->free_volume( dest_part ); - } else { - free_space = g->m.free_volume( dest_loc ); + if( candidates.empty() ) { + p.activity = player_activity(); + p.backlog.clear(); + return; } - // check free space at destination - if( free_space >= thisitem->volume() ) { - // before we move any item, check if player is at or - // adjacent to the loot source tile - if( !is_adjacent_or_closer ) { - std::vector route; - bool adjacent = false; - - // get either direct route or route to nearest adjacent tile if - // source tile is impassable - if( g->m.passable( src_loc ) ) { - route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(), - p.get_path_avoid() ); - } else { - // immpassable source tile (locker etc.), - // get route to nerest adjacent tile instead - route = route_adjacent( p, src_loc ); - adjacent = true; - } - - // check if we found path to source / adjacent tile - if( route.empty() ) { - add_msg( m_info, _( "%s can't reach the source tile. Try to sort out loot without a cart." ), - p.disp_name() ); - mgr.end_sort(); - return; - } - - // shorten the route to adjacent tile, if necessary - if( !adjacent ) { - route.pop_back(); - } - - // set the destination and restart activity after player arrives there - // we don't need to check for safe mode, - // activity will be restarted only if - // player arrives on destination tile - p.set_destination( route, player_activity( act_move_loot ) ); - mgr.end_sort(); - return; - } - move_item( p, *thisitem, thisitem->count(), src_loc, dest_loc, this_veh, this_part ); + p.backlog.front().coords.push_back( g->m.getabs( candidates[std::max( 0, + static_cast( candidates.size() / 2 ) )] ) ); + } + p.backlog.front().placement = src; - // moved item away from source so decrement - mgr.decrement_num_processed( src ); + return; + } + } + if( square_dist( p.pos(), src_loc ) > 1 ) { // not adjacent + std::vector route = route_adjacent( p, src_loc ); - break; - } - } - if( p.moves <= 0 ) { - // Restart activity and break from cycle. - p.assign_activity( act_move_loot ); + // check if we found path to source / adjacent tile + if( route.empty() ) { + if( activity_to_restore == activity_id( "ACT_MOVE_LOOT" ) ) { mgr.end_sort(); - return; } + return; + } + if( p.moves <= 0 ) { + // Restart activity and break from cycle. + p.assign_activity( activity_to_restore ); + return; + } + // set the destination and restart activity after player arrives there + // we don't need to check for safe mode, + // activity will be restarted only if + // player arrives on destination tile + p.set_destination( route, player_activity( activity_to_restore ) ); + return; + } + // something needs to be done, now we are there. + // it was here earlier, in the space of one turn, maybe it got harvested by someone else. + if( reason == NEEDS_HARVESTING && g->m.has_flag_furn( "GROWTH_HARVEST", src_loc ) ) { + iexamine::harvest_plant( p, src_loc, true ); + } else if( reason == NEEDS_TILLING && g->m.has_flag( "PLOWABLE", src_loc ) && + p.has_quality( quality_id( "DIG" ), 1 ) && !g->m.has_furn( src_loc ) ) { + p.assign_activity( activity_id( "ACT_CHURN" ), 18000, -1 ); + p.backlog.push_front( activity_to_restore ); + p.activity.placement = src; + return; + } else if( reason == NEEDS_PLANTING && g->m.has_flag_ter_or_furn( "PLANTABLE", src_loc ) ) { + if( !plant_activity( p, zone, src_loc ) ) { + continue; + } + } else if( reason == CAN_DO_CONSTRUCTION && g->m.partial_con_at( src_loc ) ) { + p.backlog.push_front( activity_to_restore ); + p.assign_activity( activity_id( "ACT_BUILD" ) ); + p.activity.placement = src; + return; + } else if( reason == CAN_DO_CONSTRUCTION || reason == CAN_DO_PREREQ || reason == CAN_DO_PREREQ_2 ) { + construction_activity( p, zone, src_loc, reason, list_constructions, activity_to_restore ); + return; + } else if( reason == CAN_DO_FETCH && activity_to_restore == activity_id( "ACT_TIDY_UP" ) ) { + if( !tidy_activity( p, src_loc, activity_to_restore ) ) { + return; + } + } else if( reason == CAN_DO_FETCH && activity_to_restore == activity_id( "ACT_FETCH_REQUIRED" ) ) { + fetch_activity( p, src_loc, activity_to_restore ); + return; + } else if( reason == CAN_DO_FETCH && activity_to_restore == activity_id( "ACT_MOVE_LOOT" ) ) { + if( move_loot_activity( p, src_loc, mgr, activity_to_restore ) ) { + return; } + continue; } } - - // If we got here without restarting the activity, it means we're done - add_msg( m_info, _( "%s sorted out every item possible." ), p.disp_name() ); - if( p.is_npc() ) { - npc *guy = dynamic_cast( &p ); - guy->current_activity_id = activity_id::NULL_ID(); + if( p.moves <= 0 ) { + // Restart activity and break from cycle. + p.assign_activity( activity_to_restore ); + if( activity_to_restore == activity_id( "ACT_MOVE_LOOT" ) ) { + mgr.end_sort(); + } + return; + } + // if we got here, we need to revert otherwise NPC will be stuck in AI Limbo and have a head explosion. + if( p.backlog.empty() || src_set.empty() ) { + if( p.is_npc() ) { + npc *guy = dynamic_cast( &p ); + guy->revert_after_activity(); + } + // tidy up leftover moved parts and tools left lying near the work spots. + if( activity_to_restore == activity_id( "ACT_MULTIPLE_FARM" ) || + activity_to_restore == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ) { + p.assign_activity( activity_id( "ACT_TIDY_UP" ) ); + } else if( activity_to_restore == activity_id( "ACT_MOVE_LOOT" ) ) { + mgr.end_sort(); + } } - mgr.end_sort(); } static cata::optional find_best_fire( diff --git a/src/clzones.cpp b/src/clzones.cpp index 1f7280f187f31..c61972b506620 100644 --- a/src/clzones.cpp +++ b/src/clzones.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "avatar.h" @@ -19,6 +20,9 @@ #include "json.h" #include "line.h" #include "map.h" +#include "messages.h" +#include "map_iterator.h" +#include "map_selector.h" #include "output.h" #include "string_input_popup.h" #include "translations.h" @@ -586,6 +590,31 @@ std::unordered_set zone_manager::get_point_set( const zone_type_id &ty return type_iter->second; } +std::unordered_set zone_manager::get_point_set_loot( const tripoint &where, + int radius, const faction_id &fac ) const +{ + return get_point_set_loot( where, radius, false, fac ); +} + +std::unordered_set zone_manager::get_point_set_loot( const tripoint &where, + int radius, bool npc_search, const faction_id &fac ) const +{ + ( void )fac; + std::unordered_set res; + for( const tripoint elem : g->m.points_in_radius( g->m.getlocal( where ), radius ) ) { + const zone_data *zone = get_zone_at( g->m.getabs( elem ) ); + // if not a LOOT zone + if( ( !zone ) || ( zone->get_type().str().substr( 0, 4 ) != "LOOT" ) ) { + continue; + } + if( npc_search && ( has( zone_type_id( "NO_NPC_PICKUP" ), elem ) ) ) { + continue; + } + res.insert( elem ); + } + return res; +} + std::unordered_set zone_manager::get_vzone_set( const zone_type_id &type, const faction_id &fac ) const { @@ -757,7 +786,7 @@ cata::optional zone_manager::get_nearest( const zone_type_id &type, co } zone_type_id zone_manager::get_near_zone_type_for_item( const item &it, - const tripoint &where ) const + const tripoint &where, int range ) const { auto cat = it.get_category(); if( has_near( zone_type_id( "LOOT_CUSTOM" ), where ) ) { @@ -767,7 +796,7 @@ zone_type_id zone_manager::get_near_zone_type_for_item( const item &it, } } if( it.has_flag( "FIREWOOD" ) ) { - if( has_near( zone_type_id( "LOOT_WOOD" ), where ) ) { + if( has_near( zone_type_id( "LOOT_WOOD" ), where, range ) ) { return zone_type_id( "LOOT_WOOD" ); } } @@ -778,14 +807,14 @@ zone_type_id zone_manager::get_near_zone_type_for_item( const item &it, if( it_food.is_food() ) { // skip food without comestible, like MREs if( it_food.get_comestible()->comesttype == "DRINK" ) { - if( !preserves && it_food.goes_bad() && has_near( zone_type_id( "LOOT_PDRINK" ), where ) ) { + if( !preserves && it_food.goes_bad() && has_near( zone_type_id( "LOOT_PDRINK" ), where, range ) ) { return zone_type_id( "LOOT_PDRINK" ); - } else if( has_near( zone_type_id( "LOOT_DRINK" ), where ) ) { + } else if( has_near( zone_type_id( "LOOT_DRINK" ), where, range ) ) { return zone_type_id( "LOOT_DRINK" ); } } - if( !preserves && it_food.goes_bad() && has_near( zone_type_id( "LOOT_PFOOD" ), where ) ) { + if( !preserves && it_food.goes_bad() && has_near( zone_type_id( "LOOT_PFOOD" ), where, range ) ) { return zone_type_id( "LOOT_PFOOD" ); } } @@ -808,7 +837,7 @@ zone_type_id zone_manager::get_near_zone_type_for_item( const item &it, return zone_type_id( "LOOT_TOOLS" ); } if( cat.id() == "clothing" ) { - if( it.is_filthy() && has_near( zone_type_id( "LOOT_FCLOTHING" ), where ) ) { + if( it.is_filthy() && has_near( zone_type_id( "LOOT_FCLOTHING" ), where, range ) ) { return zone_type_id( "LOOT_FCLOTHING" ); } return zone_type_id( "LOOT_CLOTHING" ); @@ -850,7 +879,7 @@ zone_type_id zone_manager::get_near_zone_type_for_item( const item &it, return zone_type_id( "LOOT_ARTIFACTS" ); } if( cat.id() == "armor" ) { - if( it.is_filthy() && has_near( zone_type_id( "LOOT_FARMOR" ), where ) ) { + if( it.is_filthy() && has_near( zone_type_id( "LOOT_FARMOR" ), where, range ) ) { return zone_type_id( "LOOT_FARMOR" ); } return zone_type_id( "LOOT_ARMOR" ); @@ -875,6 +904,18 @@ std::vector zone_manager::get_zones( const zone_type_id &type, return zones; } +const zone_data *zone_manager::get_zone_at( const tripoint &where ) const +{ + for( auto it = zones.rbegin(); it != zones.rend(); ++it ) { + const auto &zone = *it; + + if( zone.has_inside( where ) ) { + return &zone; + } + } + return nullptr; +} + const zone_data *zone_manager::get_bottom_zone( const tripoint &where, const faction_id &fac ) const { diff --git a/src/clzones.h b/src/clzones.h index c4935dc50610a..7f085cebdb2ff 100644 --- a/src/clzones.h +++ b/src/clzones.h @@ -381,21 +381,25 @@ class zone_manager bool has_loot_dest_near( const tripoint &where ) const; bool custom_loot_has( const tripoint &where, const item *it ) const; std::unordered_set get_near( const zone_type_id &type, const tripoint &where, - int range = MAX_DISTANCE, const item *it = nullptr, - const faction_id &fac = your_fac ) const; + int range = MAX_DISTANCE, const item *it = nullptr, const faction_id &fac = your_fac ) const; cata::optional get_nearest( const zone_type_id &type, const tripoint &where, - 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; + 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, + int range = MAX_DISTANCE ) const; std::vector get_zones( const zone_type_id &type, const tripoint &where, const faction_id &fac = your_fac ) const; + const zone_data *get_zone_at( const tripoint &where ) 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, int turns ); - + // list of tripoints of zones that are loot zones only + std::unordered_set get_point_set_loot( const tripoint &where, int radius, + const faction_id &fac = your_fac ) const; + std::unordered_set get_point_set_loot( const tripoint &where, int radius, + bool npc_search, const faction_id &fac = your_fac ) const; void start_sort( const std::vector &src_sorted ); void end_sort(); bool is_sorting() const; diff --git a/src/construction.cpp b/src/construction.cpp index 6a325daa211b1..dc40eb9f9832b 100644 --- a/src/construction.cpp +++ b/src/construction.cpp @@ -759,7 +759,6 @@ bool can_construct( const construction &con, const tripoint &p ) [&p]( const std::string & flag ) { return g->m.has_flag( flag, p ); } ); - // make sure the construction would actually do something if( !con.post_terrain.empty() ) { if( con.post_is_furniture ) { @@ -936,6 +935,12 @@ void complete_construction( player *p ) // This comes after clearing the activity, in case the function interrupts // activities built.post_special( terp ); + // npcs will automatically resume backlog, players wont. + if( p->is_player() && !p->backlog.empty() && + p->backlog.front().id() == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ) { + p->backlog.clear(); + p->assign_activity( activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ); + } } bool construct::check_empty( const tripoint &p ) diff --git a/src/construction.h b/src/construction.h index c890d6f19d903..6961cb0c9a46a 100644 --- a/src/construction.h +++ b/src/construction.h @@ -79,7 +79,6 @@ struct construction { std::function post_special; // Custom error message display std::function explain_failure; - // Whether it's furniture or terrain bool pre_is_furniture; // Whether it's furniture or terrain diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 78a74187777c2..0f8a3192d921a 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -1006,6 +1006,7 @@ static void loot() FertilizePlots = 16, HarvestPlots = 32, ConstructPlots = 64, + MultiFarmPlots = 128, }; auto just_one = []( int flags ) { @@ -1032,6 +1033,7 @@ static void loot() flags |= PlantPlots; flags |= FertilizePlots; flags |= HarvestPlots; + flags |= MultiFarmPlots; } flags |= g->check_near_zone( zone_type_id( "CONSTRUCTION_BLUEPRINT" ), u.pos() ) ? ConstructPlots : 0; @@ -1080,6 +1082,10 @@ static void loot() menu.addentry_desc( ConstructPlots, true, 'c', _( "Construct Plots" ), _( "Work on any nearby Blueprint: construction zones" ) ); } + if( flags & MultiFarmPlots ) { + menu.addentry_desc( MultiFarmPlots, true, 'm', _( "Farm Plots" ), + _( "till and plant on any nearby farm plots - auto-fetch seeds and tools" ) ); + } menu.query(); flags = ( menu.ret >= 0 ) ? menu.ret : None; @@ -1115,7 +1121,10 @@ static void loot() u.assign_activity( activity_id( "ACT_HARVEST_PLOT" ) ); break; case ConstructPlots: - u.assign_activity( activity_id( "ACT_BLUEPRINT_CONSTRUCTION" ) ); + u.assign_activity( activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) ); + break; + case MultiFarmPlots: + u.assign_activity( activity_id( "ACT_MULTIPLE_FARM" ) ); break; default: debugmsg( "Unsupported flag" ); diff --git a/src/iexamine.cpp b/src/iexamine.cpp index 4ae5b3e443880..d4ca63a801279 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -1912,15 +1912,21 @@ void iexamine::plant_seed( player &p, const tripoint &examp, const itype_id &see } else { used_seed = p.use_amount( seed_id, 1 ); } - used_seed.front().set_age( 0_turns ); - g->m.add_item_or_charges( examp, used_seed.front() ); - if( g->m.has_flag_furn( "PLANTABLE", examp ) ) { - g->m.furn_set( examp, furn_str_id( g->m.furn( examp )->plant->transform ) ); - } else { - g->m.set( examp, t_dirt, f_plant_seed ); + if( !used_seed.empty() ) { + used_seed.front().set_age( 0_turns ); + if( used_seed.front().has_var( "activity_var" ) ) { + used_seed.front().erase_var( "activity_var" ); + } + g->m.add_item_or_charges( examp, used_seed.front() ); + if( g->m.has_flag_furn( "PLANTABLE", examp ) ) { + g->m.furn_set( examp, furn_str_id( g->m.furn( examp )->plant->transform ) ); + } else { + g->m.set( examp, t_dirt, f_plant_seed ); + } + p.moves -= to_moves( 30_seconds ); + p.add_msg_player_or_npc( _( "You plant some %s." ), _( " plants some %s." ), + item::nname( seed_id ) ); } - p.moves -= to_moves( 30_seconds ); - add_msg( _( "Planted %s." ), item::nname( seed_id ) ); } /** @@ -2017,7 +2023,7 @@ std::list iexamine::get_harvest_items( const itype &type, const int plant_ /** * Actual harvesting of selected plant */ -void iexamine::harvest_plant( player &p, const tripoint &examp ) +void iexamine::harvest_plant( player &p, const tripoint &examp, bool from_activity ) { // Can't use item_stack::only_item() since there might be fertilizer map_stack items = g->m.i_at( examp ); @@ -2071,6 +2077,9 @@ void iexamine::harvest_plant( player &p, const tripoint &examp ) } const int seedCount = std::max( 1, rng( plant_count / 4, plant_count / 2 ) ); for( auto &i : get_harvest_items( type, plant_count, seedCount, true ) ) { + if( from_activity ) { + i.set_var( "activity_var", p.name ); + } g->m.add_item_or_charges( examp, i ); } g->m.furn_set( examp, furn_str_id( g->m.furn( examp )->plant->transform ) ); diff --git a/src/iexamine.h b/src/iexamine.h index db9eece95c3ac..4f17faf1c4b3c 100644 --- a/src/iexamine.h +++ b/src/iexamine.h @@ -131,7 +131,7 @@ std::list get_harvest_items( const itype &type, int plant_count, std::vector get_seed_entries( const std::vector &seed_inv ); int query_seed( const std::vector &seed_entries ); void plant_seed( player &p, const tripoint &examp, const itype_id &seed_id ); -void harvest_plant( player &p, const tripoint &examp ); +void harvest_plant( player &p, const tripoint &examp, bool from_activity = false ); void fertilize_plant( player &p, const tripoint &tile, const itype_id &fertilizer ); itype_id choose_fertilizer( player &p, const std::string &pname, bool ask_player ); ret_val can_fertilize( player &p, const tripoint &tile, const itype_id &fertilizer ); diff --git a/src/inventory.cpp b/src/inventory.cpp index a442167159a48..96bc1da210dcf 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -181,6 +181,16 @@ inventory &inventory::operator+= ( const item &rhs ) return *this; } +inventory &inventory::operator+= ( const item_stack &rhs ) +{ + for( const auto &p : rhs ) { + if( !p.made_of( LIQUID ) ) { + add_item( p, true ); + } + } + return *this; +} + inventory inventory::operator+ ( const inventory &rhs ) { return inventory( *this ) += rhs; diff --git a/src/inventory.h b/src/inventory.h index 23f95c9daee7a..f0a42c840ff8e 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -16,6 +16,7 @@ #include "cata_utility.h" #include "item.h" +#include "item_stack.h" #include "visitable.h" #include "units.h" @@ -104,6 +105,7 @@ class inventory : public visitable inventory &operator+= ( const item &rhs ); inventory &operator+= ( const std::list &rhs ); inventory &operator+= ( const std::vector &rhs ); + inventory &operator+= ( const item_stack &rhs ); inventory operator+ ( const inventory &rhs ); inventory operator+ ( const item &rhs ); inventory operator+ ( const std::list &rhs ); diff --git a/src/iuse.cpp b/src/iuse.cpp index 435fef93aad0a..50f9989f9650b 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -2494,9 +2494,10 @@ int iuse::makemound( player *p, item *it, bool t, const tripoint & ) } if( g->m.has_flag( "PLOWABLE", pnt ) && !g->m.has_flag( "PLANT", pnt ) ) { - p->add_msg_if_player( _( "You churn up the earth here." ) ); - p->mod_moves( -300 ); - g->m.ter_set( pnt, t_dirtmound ); + p->add_msg_if_player( _( "You start churning up the earth here." ) ); + p->assign_activity( activity_id( "ACT_CHURN" ), to_turns( 3_minutes ), + -1, p->get_item_position( it ) ); + p->activity.placement = pnt; return it->type->charges_to_use(); } else { p->add_msg_if_player( _( "You can't churn up this ground." ) ); diff --git a/src/map.cpp b/src/map.cpp index 4294c1a7e28cf..175a24faa75b7 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -5407,6 +5407,17 @@ field_entry *map::get_field( const tripoint &p, const field_type_id type ) return current_submap->fld[l.x][l.y].find_field( type ); } +bool map::dangerous_field_at( const tripoint &p ) +{ + for( auto &pr : field_at( p ) ) { + auto &fd = pr.second; + if( fd.is_dangerous() ) { + return true; + } + } + return false; +} + bool map::add_field( const tripoint &p, const field_type_id type, int intensity, const time_duration &age ) { diff --git a/src/map.h b/src/map.h index 610ef9c5b45e4..3156c0ac37065 100644 --- a/src/map.h +++ b/src/map.h @@ -1122,6 +1122,7 @@ class map * @return NULL if there is no such field entry at that place. */ field_entry *get_field( const tripoint &p, field_type_id type ); + bool dangerous_field_at( const tripoint &p ); /** * Add field entry at point, or set intensity if present * @return false if the field could not be created (out of bounds), otherwise true. diff --git a/src/npcmove.cpp b/src/npcmove.cpp index c67dd6ead244a..2b4611fd6f346 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -829,7 +829,8 @@ void npc::move() // No items, so follow the player? action = npc_follow_player; } - + // Friendly NPCs who are followers/ doing tasks for the player should never get here. + // This will revert them to a dynamic NPC state. if( action == npc_undecided ) { // Do our long-term action action = long_term_goal_action(); @@ -854,7 +855,6 @@ void npc::move() } add_msg( m_debug, "%s chose action %s.", name, npc_action_name( action ) ); - execute_action( action ); } @@ -3022,12 +3022,12 @@ bool npc::do_player_activity() } /* if the activity is finished, grab any backlog or change the mission */ if( !has_destination() && !activity ) { - add_msg( m_info, _( "%s completed the assigned task." ), disp_name() ); if( !backlog.empty() ) { activity = backlog.front(); backlog.pop_front(); current_activity_id = activity.id(); } else { + add_msg( m_info, string_format( "%s completed the assigned task.", disp_name() ) ); current_activity_id = activity_id::NULL_ID(); revert_after_activity(); // if we loaded after being out of the bubble for a while, we might have more diff --git a/src/npctalk.cpp b/src/npctalk.cpp index d7e15146d5048..d770539843cb8 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -2370,7 +2370,7 @@ void talk_effect_t::parse_string_effect( const std::string &effect_id, JsonObjec WRAP( start_trade ), WRAP( sort_loot ), WRAP( do_construction ), - WRAP( do_blueprint_construction ), + WRAP( do_farming ), WRAP( assign_guard ), WRAP( stop_guard ), WRAP( start_camp ), diff --git a/src/npctalk.h b/src/npctalk.h index cf55f7a7b1b0e..47b23b0864607 100644 --- a/src/npctalk.h +++ b/src/npctalk.h @@ -36,7 +36,7 @@ void buy_100_logs( npc & ); void start_trade( npc & ); void sort_loot( npc & ); void do_construction( npc & ); -void do_blueprint_construction( npc & ); +void do_farming( npc & ); void revert_activity( npc & ); void goto_location( npc & ); void assign_base( npc & ); diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index 0ffdc6bbd5971..0a55756ad277c 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -207,10 +207,10 @@ void talk_function::do_construction( npc &p ) p.set_mission( NPC_MISSION_ACTIVITY ); } -void talk_function::do_blueprint_construction( npc &p ) +void talk_function::do_farming( npc &p ) { p.set_attitude( NPCATT_ACTIVITY ); - p.assign_activity( activity_id( "ACT_BLUEPRINT_CONSTRUCTION" ) ); + p.assign_activity( activity_id( "ACT_MULTIPLE_FARM" ) ); p.set_mission( NPC_MISSION_ACTIVITY ); } @@ -274,6 +274,10 @@ void talk_function::assign_guard( npc &p ) return; } + if( p.has_player_activity() ) { + p.revert_after_activity(); + } + if( p.is_travelling() ) { if( p.has_companion_mission() ) { p.reset_companion_mission(); diff --git a/src/player.cpp b/src/player.cpp index 9bffa0e60c4fe..26bf6ffa77439 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -9733,7 +9733,7 @@ std::string player::is_snuggling() const // 6.0 is LIGHT_AMBIENT_DIM // 7.3 is LIGHT_AMBIENT_MINIMAL, a dark cloudy night, unlit indoors // 11.0 is zero light or blindness -float player::fine_detail_vision_mod() const +float player::fine_detail_vision_mod( const tripoint &p ) const { // PER_SLIME_OK implies you can get enough eyes around the bile // that you can generally see. There still will be the haze, but @@ -9749,7 +9749,8 @@ float player::fine_detail_vision_mod() const float own_light = std::max( 1.0, LIGHT_AMBIENT_LIT - active_light() - 2 ); // Same calculation as above, but with a result 3 lower. - float ambient_light = std::max( 1.0, LIGHT_AMBIENT_LIT - g->m.ambient_light_at( pos() ) + 1.0 ); + float ambient_light = std::max( 1.0, + LIGHT_AMBIENT_LIT - g->m.ambient_light_at( p == tripoint_zero ? pos() : p ) + 1.0 ); return std::min( own_light, ambient_light ); } @@ -10486,6 +10487,8 @@ void player::assign_activity( const player_activity &act, bool allow_resume ) } if( is_npc() ) { npc *guy = dynamic_cast( this ); + guy->set_attitude( NPCATT_ACTIVITY ); + guy->set_mission( NPC_MISSION_ACTIVITY ); guy->current_activity_id = activity.id(); } } diff --git a/src/player.h b/src/player.h index a20e66e4d15d2..012d63869db4d 100644 --- a/src/player.h +++ b/src/player.h @@ -1181,8 +1181,10 @@ class player : public Character public: /** Returns a value from 1.0 to 5.0 that acts as a multiplier * for the time taken to perform tasks that require detail vision, - * above 4.0 means these activities cannot be performed. */ - float fine_detail_vision_mod() const; + * above 4.0 means these activities cannot be performed. + * takes pos as a parameter so that remote spots can be judged + * if they will potentially have enough light when player gets there */ + float fine_detail_vision_mod( const tripoint &p = tripoint_zero ) const; /** Used to determine player feedback on item use for the inventory code. * rates usability lower for non-tools (books, etc.) */ @@ -1579,8 +1581,8 @@ class player : public Character bool hauling; player_activity activity; std::list backlog; - int volume; cata::optional destination_point; + int volume; const profession *prof; start_location_id start_location;