diff --git a/data/json/player_activities.json b/data/json/player_activities.json index 343578aeee888..c0422e9f07b2e 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -51,6 +51,7 @@ "based_on": "neither", "can_resume": false, "multi_activity": true, + "fetch_items_to_zone": false, "auto_needs": true }, { @@ -199,6 +200,7 @@ "based_on": "neither", "can_resume": false, "multi_activity": true, + "fetch_items_to_zone": false, "auto_needs": true }, { diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 7dddc1a2a158e..368151e013a5d 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -190,8 +191,10 @@ static const activity_id ACT_MILK( "ACT_MILK" ); static const activity_id ACT_MOP( "ACT_MOP" ); static const activity_id ACT_MOVE_ITEMS( "ACT_MOVE_ITEMS" ); static const activity_id ACT_MOVE_LOOT( "ACT_MOVE_LOOT" ); +static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); static const activity_id ACT_MULTIPLE_CHOP_PLANKS( "ACT_MULTIPLE_CHOP_PLANKS" ); static const activity_id ACT_MULTIPLE_CHOP_TREES( "ACT_MULTIPLE_CHOP_TREES" ); +static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); static const activity_id ACT_MULTIPLE_CRAFT( "ACT_MULTIPLE_CRAFT" ); static const activity_id ACT_MULTIPLE_DIS( "ACT_MULTIPLE_DIS" ); static const activity_id ACT_MULTIPLE_FARM( "ACT_MULTIPLE_FARM" ); @@ -4762,10 +4765,13 @@ std::optional multi_zone_activity_actor::multi_activity_requirem void multi_zone_activity_actor::do_turn( player_activity &act, Character &you ) { + activity_id prior_act = get_type(); simulate_turn( act, you, false ); // If this activity still exists, end it - if( !act.is_null() && act.is_multi_type() ) { - act.set_to_null(); + if( !act.is_null() && ( prior_act == you.activity.id() ) ) { + // Nuke the current activity, leaving the backlog alone + // No multi_zone_activity_actor functions will be called from this point, so `this` can be nullptr + you.activity = player_activity(); } } @@ -4783,19 +4789,16 @@ bool multi_zone_activity_actor::simulate_turn( player_activity &act, Character & // now loop through the work-spot tiles and judge whether its worth traveling to it yet // or if we need to fetch something first. - // check: if a fetch activity was assigned but there's nothing to fetch, restore previous activity from backlog + // check: if a fetch activity was assigned but there's nothing to fetch, end fetch activity (restoring backlog) // may cause infinite loop if something goes wrong - //TODO: check whether a fetch activity should be assigned before it is + // TODO: this could be moved to a multi_zone_activity_actor virtual function for post-location checking if( current_activity == ACT_FETCH_REQUIRED && src_sorted.empty() ) { // remind what you failed to fetch if( !check_only ) { - if( !you.backlog.empty() ) { - player_activity &act_prev = you.backlog.front(); - if( !act_prev.str_values.empty() && you.as_npc() ) { - you.as_npc()->job.fetch_history[act_prev.str_values.back()] = calendar::turn; - } - } + fetch_required_activity_actor *as_fetch = static_cast( this ); + as_fetch->set_npc_fetch_history( you ); } + act.set_to_null(); return true; } @@ -4833,7 +4836,9 @@ bool multi_zone_activity_actor::simulate_turn( player_activity &act, Character & continue; } - //route to destination if needed + // route to destination if needed + // this stores a destination_activity that is resumed via + // can_resume_with_internal() in Character::assign_activity() std::optional route_result = multi_activity_actor::route( you, current_act_copy, src_bub, req_fail_reason, check_only ); if( !route_result ) { @@ -4971,18 +4976,16 @@ requirement_check_result multi_zone_activity_actor::check_requirements( Characte bool tool_pickup = multi_activity_actor::activity_reason_picks_up_tools( reason ); // is it even worth fetching anything if there isn't enough nearby? if( !multi_activity_actor::are_requirements_nearby( tool_pickup ? loot_zone_spots : combined_spots, - what_we_need, you, - act_id, tool_pickup, src_loc ) ) { + what_we_need, you, act_id, tool_pickup, src_loc ) ) { + const tripoint_bub_ms you_pos_bub = you.pos_bub(); if( zone ) { - you.add_msg_player_or_npc( m_info, - _( "The required items are not available to complete the %s task at zone %s." ), act_id.c_str(), - zone->get_name(), - _( "The required items are not available to complete the %s task at zone %s." ), act_id.c_str(), - zone->get_name() ); + add_msg_if_player_sees( you_pos_bub, m_info, string_format( + _( "The required items are not available to complete the %s task at zone %s." ), + act_id.c_str(), zone->get_name() ) ); } else { - you.add_msg_player_or_npc( m_info, - _( "The required items are not available to complete the %s task." ), act_id.c_str(), - _( "The required items are not available to complete the %s task." ), act_id.c_str() ); + add_msg_if_player_sees( you_pos_bub, m_info, string_format( + _( "The required items are not available to complete the %s task." ), + act_id.c_str() ) ); } //TODO: this is hacky, move it if( reason == do_activity_reason::NEEDS_VEH_DECONST || @@ -4992,8 +4995,7 @@ requirement_check_result multi_zone_activity_actor::check_requirements( Characte return requirement_check_result::SKIP_LOCATION; } else { if( !check_only ) { - return multi_activity_actor::fetch_requirements( you, what_we_need, act_id, - act_info, src, src_loc, src_set ); + return fetch_requirements( you, what_we_need, act_info, src, src_loc, src_set ); } return requirement_check_result::RETURN_EARLY; } @@ -5001,12 +5003,129 @@ requirement_check_result multi_zone_activity_actor::check_requirements( Characte return requirement_check_result::SKIP_LOCATION_NO_MATCH; } +bool multi_zone_activity_actor::can_resume_with_internal( const activity_actor &, + const Character & ) const +{ + return true; +} + +requirement_check_result multi_zone_activity_actor::fetch_requirements( Character &you, + requirement_id what_we_need, activity_reason_info &act_info, const tripoint_abs_ms &src, + const tripoint_bub_ms &src_loc, const std::unordered_set &src_set ) +{ + + map &here = get_map(); + + if( you.as_npc() && you.as_npc()->job.fetch_history.count( what_we_need.str() ) != 0 && + you.as_npc()->job.fetch_history[what_we_need.str()] == calendar::turn ) { + // this may be a failed fetch already. Quit task to avoid infinite loop. + you.activity = player_activity(); + you.backlog.clear(); + multi_activity_actor::check_npc_revert( you ); + return requirement_check_result::SKIP_LOCATION; + } + // come back here after successfully fetching your stuff + std::vector local_src_set; + local_src_set.reserve( src_set.size() ); + for( const tripoint_abs_ms &elem : src_set ) { + local_src_set.push_back( here.get_bub( elem ) ); + } + std::vector candidates; + for( const tripoint_bub_ms &point_elem : + here.points_in_radius( src_loc, PICKUP_RANGE - 1, 0 ) ) { + // we don't want to place the components where they could interfere with our ( or someone else's ) construction spots + if( ( std::find( local_src_set.begin(), local_src_set.end(), + point_elem ) != local_src_set.end() ) || !here.can_put_items_ter_furn( point_elem ) ) { + continue; + } + candidates.push_back( point_elem ); + } + if( candidates.empty() ) { + you.activity = player_activity(); + you.backlog.clear(); + multi_activity_actor::check_npc_revert( you ); + return requirement_check_result::SKIP_LOCATION_NO_LOCATION; + } + + you.assign_activity( fetch_required_activity_actor( what_we_need, act_info.reason, here.get_abs( + candidates[std::max( 0, static_cast( candidates.size() / 2 ) )] ), src ) ); + return requirement_check_result::RETURN_EARLY; +} + void multi_zone_activity_actor::serialize( JsonOut &jsout ) const { jsout.start_object(); jsout.end_object(); } +activity_reason_info fetch_required_activity_actor::multi_activity_can_do( + Character &you, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::fetch_can_do( ACT_FETCH_REQUIRED, you, + src_loc ); +} + +void fetch_required_activity_actor::set_npc_fetch_history( Character &you ) +{ + if( !fetch_requirements.is_empty() && you.as_npc() ) { + you.as_npc()->job.fetch_history[fetch_requirements.str()] = calendar::turn; + } +} + +bool fetch_required_activity_actor::fetch_activity_valid( const Character &you ) const +{ + if( you.backlog.empty() ) { + debugmsg( "fetch activity assigned without a corresponding multi-activity" ); + return false; + } + return true; +} + +std::unordered_set fetch_required_activity_actor::multi_activity_locations( + Character &you ) +{ + map &here = get_map(); + std::unordered_set src_set; + + // 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( you, MAX_VIEW_DISTANCE ); + for( const auto &elem : mental_map ) { + const tripoint_bub_ms &elem_point = std::get<0>( elem ); + src_set.insert( here.get_abs( elem_point ) ); + } + multi_activity_actor::prune_dangerous_field_locations( src_set ); + + return src_set; +} + +bool fetch_required_activity_actor::multi_activity_do( Character &you, + const activity_reason_info &act_info, + const tripoint_abs_ms &, const tripoint_bub_ms &src_loc ) +{ + const do_activity_reason &reason = act_info.reason; + + if( reason == do_activity_reason::CAN_DO_FETCH ) { + if( fetch_activity( you, src_loc, ACT_FETCH_REQUIRED, MAX_VIEW_DISTANCE ) ) { + if( !you.is_npc() ) { + // Npcs will automatically start the next thing in the backlog, players need to be manually prompted + // Because some player activities are necessarily not marked as auto-resume. + activity_handlers::resume_for_multi_activities( you ); + } + return false; + } + } + return true; +} + +std::unique_ptr fetch_required_activity_actor::deserialize( JsonValue & ) +{ + fetch_required_activity_actor actor; + return actor.clone(); +} + std::unique_ptr multi_mine_activity_actor::deserialize( JsonValue & ) { multi_mine_activity_actor actor; @@ -8113,6 +8232,40 @@ std::unique_ptr build_construction_activity_actor::deserialize( return actor.clone(); } +activity_reason_info multi_build_construction_activity_actor::multi_activity_can_do( + Character &you, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::construction_can_do( ACT_MULTIPLE_CONSTRUCTION, you, + src_loc ); +} + +std::optional multi_build_construction_activity_actor::multi_activity_requirements( + Character &you, + activity_reason_info &act_info, const tripoint_bub_ms &src_loc, const zone_data * ) +{ + return multi_activity_actor::construction_requirements( you, act_info, src_loc ); +} + +std::unordered_set +multi_build_construction_activity_actor::multi_activity_locations( + Character &you ) +{ + return multi_activity_actor::construction_locations( you, get_type() ); +} + +bool multi_build_construction_activity_actor::multi_activity_do( Character &you, + const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::construction_do( you, act_info, src, src_loc ); +} + +std::unique_ptr multi_build_construction_activity_actor::deserialize( JsonValue & ) +{ + multi_build_construction_activity_actor actor; + return actor.clone(); +} + void reel_cable_activity_actor::start( player_activity &act, Character & ) { act.moves_total = moves_total; @@ -12258,6 +12411,33 @@ std::unique_ptr butchery_activity_actor::deserialize( JsonValue return actor.clone(); } +activity_reason_info multi_butchery_activity_actor::multi_activity_can_do( + Character &you, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::butcher_can_do( ACT_MULTIPLE_BUTCHER, you, + src_loc ); +} + +std::optional multi_butchery_activity_actor::multi_activity_requirements( + Character &you, + activity_reason_info &act_info, const tripoint_bub_ms &src_loc, const zone_data * ) +{ + return multi_activity_actor::butcher_requirements( you, act_info, src_loc ); +} + +bool multi_butchery_activity_actor::multi_activity_do( Character &you, + const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) +{ + return multi_activity_actor::butcher_do( you, act_info, src, src_loc ); +} + +std::unique_ptr multi_butchery_activity_actor::deserialize( JsonValue & ) +{ + multi_butchery_activity_actor actor; + return actor.clone(); +} + void wait_activity_actor::start( player_activity &act, Character & ) { act.moves_total = to_moves( initial_wait_time ); @@ -12896,6 +13076,7 @@ deserialize_functions = { { ACT_DROP, &drop_activity_actor::deserialize }, { ACT_E_FILE, &efile_activity_actor::deserialize }, { ACT_EBOOKSAVE, &ebooksave_activity_actor::deserialize }, + { ACT_FETCH_REQUIRED, &fetch_required_activity_actor::deserialize }, { ACT_FIELD_DRESS, &butchery_activity_actor::deserialize }, { ACT_FIRSTAID, &firstaid_activity_actor::deserialize }, { ACT_FISH, &fish_activity_actor::deserialize }, @@ -12925,8 +13106,10 @@ deserialize_functions = { { ACT_MOP, &mop_activity_actor::deserialize }, { ACT_MOVE_ITEMS, &move_items_activity_actor::deserialize }, { ACT_MOVE_LOOT, &zone_sort_activity_actor::deserialize }, + { ACT_MULTIPLE_BUTCHER, &multi_butchery_activity_actor::deserialize }, { ACT_MULTIPLE_CHOP_PLANKS, &multi_chop_planks_activity_actor::deserialize }, { ACT_MULTIPLE_CHOP_TREES, &multi_chop_trees_activity_actor::deserialize }, + { ACT_MULTIPLE_CONSTRUCTION, &multi_build_construction_activity_actor::deserialize }, { ACT_MULTIPLE_CRAFT, &multi_craft_activity_actor::deserialize }, { ACT_MULTIPLE_DIS, &multi_disassemble_activity_actor::deserialize }, { ACT_MULTIPLE_FARM, &multi_farm_activity_actor::deserialize }, diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 8bd9a5c45c7d5..df0ccc5fab544 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -120,6 +120,13 @@ class multi_zone_activity_actor : public activity_actor requirement_check_result check_requirements( Character &you, activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc, const std::unordered_set &src_set, bool check_only = false ); + // assigns fetch activity to find provided requirements + requirement_check_result fetch_requirements( Character &you, requirement_id what_we_need, + activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc, + const std::unordered_set &src_set ); + // @return equal types + bool can_resume_with_internal( const activity_actor &, + const Character & ) const override; // BEGIN interface block virtual std::unordered_set multi_activity_locations( Character &you ); @@ -387,6 +394,93 @@ class multi_disassemble_activity_actor : public multi_zone_activity_actor static std::unique_ptr deserialize( JsonValue & ); }; +class multi_build_construction_activity_actor : public multi_zone_activity_actor +{ + public: + using multi_zone_activity_actor::multi_zone_activity_actor; + const activity_id &get_type() const override { + static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); + return ACT_MULTIPLE_CONSTRUCTION; + } + std::unordered_set multi_activity_locations( Character &you ) override; + activity_reason_info multi_activity_can_do( Character &you, + const tripoint_bub_ms &src_loc ) override; + std::optional multi_activity_requirements( Character &you, + activity_reason_info &act_info, const tripoint_bub_ms &src_loc, + const zone_data *zone = nullptr ) override; + bool multi_activity_do( Character &you, const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) override; + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + static std::unique_ptr deserialize( JsonValue & ); +}; + +class multi_butchery_activity_actor : public multi_zone_activity_actor +{ + public: + using multi_zone_activity_actor::multi_zone_activity_actor; + const activity_id &get_type() const override { + static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); + return ACT_MULTIPLE_BUTCHER; + } + activity_reason_info multi_activity_can_do( Character &you, + const tripoint_bub_ms &src_loc ) override; + std::optional multi_activity_requirements( Character &you, + activity_reason_info &act_info, const tripoint_bub_ms &src_loc, + const zone_data *zone = nullptr ) override; + bool multi_activity_do( Character &you, const activity_reason_info &act_info, + const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ) override; + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + static std::unique_ptr deserialize( JsonValue & ); +}; + +// find and retrieve items for the front multi-activity of the backlog +// NOTE: ACT_FETCH_REQUIRED is not a multi-activity but still uses the framework +class fetch_required_activity_actor : public multi_zone_activity_actor +{ + public: + using multi_zone_activity_actor::multi_zone_activity_actor; + explicit fetch_required_activity_actor( requirement_id fetch_requirements, + do_activity_reason act_reason, tripoint_abs_ms fetched_item_destination, + tripoint_abs_ms fetch_for_activity_position ) : + fetch_requirements( fetch_requirements ), act_reason( act_reason ), + fetched_item_destination( fetched_item_destination ), + fetch_for_activity_position( fetch_for_activity_position ) { + }; + const activity_id &get_type() const override { + static const activity_id ACT_FETCH_REQUIRED( "ACT_FETCH_REQUIRED" ); + return ACT_FETCH_REQUIRED; + } + + void set_npc_fetch_history( Character &you ); + std::vector> requirements_map( Character &you, + int distance = MAX_VIEW_DISTANCE ); + bool fetch_activity( Character &you, const tripoint_bub_ms &src_loc, + const activity_id &activity_to_restore, int distance = MAX_VIEW_DISTANCE ); + // is the front of the activity backlog a multi-activity? + bool fetch_activity_valid( const Character &you ) const; + + std::unordered_set multi_activity_locations( Character &you ) override; + activity_reason_info multi_activity_can_do( Character &you, + const tripoint_bub_ms &src_loc ) override; + bool multi_activity_do( Character &you, const activity_reason_info &act_info, + const tripoint_abs_ms &, const tripoint_bub_ms &src_loc ) override; + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + static std::unique_ptr deserialize( JsonValue & ); + private: + requirement_id fetch_requirements; + do_activity_reason act_reason = do_activity_reason::UNKNOWN_ACTIVITY; + // where should this item be placed + tripoint_abs_ms fetched_item_destination; + // what zone tile was the fetching multi-activity currently processing? + tripoint_abs_ms fetch_for_activity_position; +}; + class aim_activity_actor : public activity_actor { private: diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index b9fa3f317ed23..b71002fcd4704 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -60,11 +60,8 @@ #include "weather.h" static const activity_id ACT_FERTILIZE_PLOT( "ACT_FERTILIZE_PLOT" ); -static const activity_id ACT_FETCH_REQUIRED( "ACT_FETCH_REQUIRED" ); static const activity_id ACT_FILL_LIQUID( "ACT_FILL_LIQUID" ); static const activity_id ACT_FIND_MOUNT( "ACT_FIND_MOUNT" ); -static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); -static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); static const activity_id ACT_REPAIR_ITEM( "ACT_REPAIR_ITEM" ); static const activity_id ACT_START_FIRE( "ACT_START_FIRE" ); static const activity_id ACT_TIDY_UP( "ACT_TIDY_UP" ); @@ -102,9 +99,6 @@ const std::map< activity_id, std::function #include "activity_actor_definitions.h" +#include "activity_type.h" #include "avatar.h" #include "butchery.h" #include "calendar.h" @@ -242,7 +243,9 @@ struct act_item { consumed_moves( consumed_moves ) {} }; -static void check_npc_revert( Character &you ) +namespace multi_activity_actor +{ +void check_npc_revert( Character &you ) { if( you.is_npc() ) { npc *guy = dynamic_cast( &you ); @@ -251,6 +254,7 @@ static void check_npc_revert( Character &you ) } } } +} //namespace multi_activity_actor // TODO: Deliberately unified with multidrop. Unify further. static bool same_type( const std::list &items ) @@ -2286,7 +2290,10 @@ requirement_id remove_met_requirements( requirement_id base_req_id, Character &y requirement_data::alter_tool_comp_vector reduced_tool_reqs_vector; requirement_data::alter_quali_req_vector reduced_quality_reqs_vector; - const inventory &inv = you.crafting_inventory(); + // We cannot assume that required items on the ground when the multi-activity starts + // will *always* be in the multi-activity work area to meet requirements. + // Therefore, items on the ground aren't counted here for met requirements. + const inventory &inv = you.crafting_inventory( tripoint_bub_ms::zero, -1 ); for( std::vector &tools : tool_reqs_vector ) { bool found = false; @@ -2532,29 +2539,23 @@ void add_basecamp_storage_to_loot_zone_list( } } //namespace multi_activity_actor -static std::vector> requirements_map( Character &you, - const int distance = MAX_VIEW_DISTANCE ) +std::vector> + fetch_required_activity_actor::requirements_map( Character &you, + const int distance ) { std::vector> requirement_map; - if( you.backlog.empty() || you.backlog.front().str_values.empty() ) { + if( !fetch_activity_valid( you ) ) { return requirement_map; } - const requirement_data things_to_fetch = requirement_id( you.backlog.front().str_values[0] ).obj(); - const activity_id activity_to_restore = you.backlog.front().id(); // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - requirement_id things_to_fetch_id = things_to_fetch.id(); + requirement_id things_to_fetch_id = fetch_requirements; + const requirement_data things_to_fetch = *fetch_requirements; + const activity_id activity_to_restore = you.backlog.front().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(); zone_manager &mgr = zone_manager::get_manager(); - const bool pickup_task = you.backlog.front().id() == ACT_MULTIPLE_FARM || - you.backlog.front().id() == ACT_MULTIPLE_CHOP_PLANKS || - you.backlog.front().id() == ACT_MULTIPLE_BUTCHER || - you.backlog.front().id() == ACT_MULTIPLE_CHOP_TREES || - you.backlog.front().id() == ACT_VEHICLE_DECONSTRUCTION || - you.backlog.front().id() == ACT_VEHICLE_REPAIR || - you.backlog.front().id() == ACT_MULTIPLE_FISH || - you.backlog.front().id() == ACT_MULTIPLE_MINE; + const bool pickup_task = activity_to_restore->fetch_items_to_zone(); // where it is, what it is, how much of it, and how much in total is required of that item. std::vector> final_map; std::vector loot_spots; @@ -2562,7 +2563,7 @@ static std::vector> requirements_map( std::vector combined_spots; std::map total_map; map &here = get_map(); - tripoint_bub_ms src_loc = here.get_bub( you.backlog.front().placement ); + const tripoint_bub_ms &src_loc = here.get_bub( fetch_for_activity_position ); for( const tripoint_bub_ms &elem : here.points_in_radius( src_loc, PICKUP_RANGE - 1 ) ) { already_there_spots.push_back( elem ); @@ -2918,29 +2919,35 @@ static bool tidy_activity( Character &you, const tripoint_bub_ms &src_loc, return true; } -static bool fetch_activity( +bool fetch_required_activity_actor::fetch_activity( Character &you, const tripoint_bub_ms &src_loc, const activity_id &activity_to_restore, - const int distance = MAX_VIEW_DISTANCE ) + int distance ) { + if( !fetch_activity_valid( you ) ) { + return false; + } + const activity_id fetch_for_activity = you.backlog.front().id(); map &here = get_map(); - if( !here.can_put_items_ter_furn( here.get_bub( - you.backlog.front().coords.back() ) ) ) { + if( !here.can_put_items_ter_furn( here.get_bub( fetched_item_destination ) ) ) { return false; } - const std::vector> mental_map_2 = + const std::vector> mental_item_map = requirements_map( you, distance ); int pickup_count = 1; map_stack items_there = here.i_at( src_loc ); const units::volume volume_allowed = you.free_space(); const units::mass weight_allowed = you.weight_capacity() - you.weight_carried(); + + // if the items are in vehicle cargo, move them to their position const std::optional ovp = here.veh_at( src_loc ).cargo(); if( ovp ) { for( item &veh_elem : ovp->items() ) { - for( auto elem : mental_map_2 ) { + for( auto elem : mental_item_map ) { if( std::get<0>( elem ) == src_loc && veh_elem.typeId() == std::get<1>( elem ) ) { - if( !you.backlog.empty() && you.backlog.front().id() == ACT_MULTIPLE_CONSTRUCTION ) { + if( fetch_for_activity == ACT_MULTIPLE_CONSTRUCTION ) { + //TODO: this teleports the item, assign an item moving activity move_item( you, veh_elem, veh_elem.count_by_charges() ? std::get<2>( elem ) : 1, src_loc, - here.get_bub( you.backlog.front().coords.back() ), ovp, + here.get_bub( fetched_item_destination ), ovp, activity_to_restore ); return true; } @@ -2950,26 +2957,19 @@ static bool fetch_activity( } for( auto item_iter = items_there.begin(); item_iter != items_there.end(); item_iter++ ) { item &it = *item_iter; - for( auto elem : mental_map_2 ) { + for( auto elem : mental_item_map ) { if( std::get<0>( elem ) == src_loc && it.typeId() == std::get<1>( elem ) ) { - // construction/crafting tasks want the required item moved near the work spot. - if( !you.backlog.empty() && ( you.backlog.front().id() == ACT_MULTIPLE_CONSTRUCTION || - you.backlog.front().id() == ACT_MULTIPLE_DIS ) ) { + // some tasks (construction/crafting) want the required item moved near the work spot. + if( !fetch_for_activity->fetch_items_to_zone() ) { + //TODO: this teleports the item, assign an item moving activity move_item( you, it, it.count_by_charges() ? std::get<2>( elem ) : 1, src_loc, - here.get_bub( you.backlog.front().coords.back() ), ovp, - activity_to_restore ); + here.get_bub( fetched_item_destination ), ovp, activity_to_restore ); return true; - // other tasks want the tool picked up - } else if( !you.backlog.empty() && ( you.backlog.front().id() == ACT_MULTIPLE_FARM || - you.backlog.front().id() == ACT_MULTIPLE_CHOP_PLANKS || - you.backlog.front().id() == ACT_VEHICLE_DECONSTRUCTION || - you.backlog.front().id() == ACT_VEHICLE_REPAIR || - you.backlog.front().id() == ACT_MULTIPLE_BUTCHER || - you.backlog.front().id() == ACT_MULTIPLE_CHOP_TREES || - you.backlog.front().id() == ACT_MULTIPLE_FISH || - you.backlog.front().id() == ACT_MULTIPLE_MINE || - you.backlog.front().id() == ACT_MULTIPLE_MOP ) ) { + // other tasks want the item picked up + } else { + // TODO: most of this `else` block should be handled by exactly one function call + if( it.volume() > volume_allowed || it.weight() > weight_allowed ) { add_msg_if_player_sees( you, _( "%1s failed to fetch tools." ), you.name ); continue; @@ -3004,7 +3004,7 @@ static bool fetch_activity( } } } - // if we got here, then the fetch failed for reasons that weren't predicted before setting it. + // if we got here, then the fetch failed for reasons that weren't predicted before assigning it. // nothing was moved or picked up, and nothing can be moved or picked up // so call the whole thing off to stop it looping back to this point ad nauseum. you.set_moves( 0 ); @@ -3430,26 +3430,6 @@ std::unordered_set craft_locations( Character &you, const activ return src_set; } -std::unordered_set fetch_locations( Character &you, const activity_id & ) -{ - - map &here = get_map(); - std::unordered_set src_set; - - // 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( you, MAX_VIEW_DISTANCE ); - for( const auto &elem : mental_map ) { - const tripoint_bub_ms &elem_point = std::get<0>( elem ); - src_set.insert( here.get_abs( elem_point ) ); - } - multi_activity_actor::prune_dangerous_field_locations( src_set ); - - return src_set; -} - std::unordered_set fish_locations( Character &you, const activity_id &act_id ) { @@ -3555,7 +3535,7 @@ bool butcher_do( Character &you, const activity_reason_info &act_info, if( reason == do_activity_reason::NEEDS_BUTCHERING || reason == do_activity_reason::NEEDS_BIG_BUTCHERING ) { if( butcher_corpse_activity( you, src_loc, reason ) ) { - you.backlog.emplace_front( ACT_MULTIPLE_BUTCHER ); + you.backlog.emplace_front( multi_butchery_activity_actor() ); return false; } } @@ -3622,7 +3602,7 @@ bool construction_do( Character &you, const activity_reason_info &act_info, if( reason == do_activity_reason::CAN_DO_CONSTRUCTION ) { if( here.partial_con_at( src_loc ) ) { - you.backlog.emplace_front( ACT_MULTIPLE_CONSTRUCTION ); + you.backlog.emplace_front( multi_build_construction_activity_actor() ); you.assign_activity( build_construction_activity_actor( src ) ); return false; } @@ -3647,19 +3627,19 @@ bool tidy_up_do( Character &you, const activity_reason_info &act_info, } bool fetch_do( Character &you, const activity_reason_info &act_info, - const tripoint_abs_ms &, const tripoint_bub_ms &src_loc ) + const tripoint_abs_ms &, const tripoint_bub_ms & ) { const do_activity_reason &reason = act_info.reason; if( reason == do_activity_reason::CAN_DO_FETCH ) { - if( fetch_activity( you, src_loc, ACT_FETCH_REQUIRED, MAX_VIEW_DISTANCE ) ) { - if( !you.is_npc() ) { - // Npcs will automatically start the next thing in the backlog, players need to be manually prompted - // Because some player activities are necessarily not marked as auto-resume. - activity_handlers::resume_for_multi_activities( you ); - } - return false; + //if( fetch_activity( you, src_loc, ACT_FETCH_REQUIRED, MAX_VIEW_DISTANCE ) ) { + if( !you.is_npc() ) { + // Npcs will automatically start the next thing in the backlog, players need to be manually prompted + // Because some player activities are necessarily not marked as auto-resume. + activity_handlers::resume_for_multi_activities( you ); } + return false; + //} } return true; } @@ -3949,7 +3929,7 @@ static std::unordered_set generic_multi_activity_locations( } else if( act_id == ACT_MULTIPLE_CRAFT ) { src_set = multi_activity_actor::craft_locations( you, act_id ); } else if( act_id == ACT_FETCH_REQUIRED ) { - src_set = multi_activity_actor::fetch_locations( you, act_id ); + //src_set = multi_activity_actor::fetch_locations( you, act_id ); } else if( act_id == ACT_MULTIPLE_MOP ) { src_set = multi_activity_actor::mop_locations( you, act_id ); } else if( act_id == ACT_MULTIPLE_CONSTRUCTION ) { diff --git a/src/activity_item_handling.h b/src/activity_item_handling.h index b0dbaf802c779..610fe97a0febb 100644 --- a/src/activity_item_handling.h +++ b/src/activity_item_handling.h @@ -134,6 +134,7 @@ bool activity_reason_continue( do_activity_reason reason ); bool activity_reason_quit( do_activity_reason reason ); // the activity (reason) requires picking up tools bool activity_reason_picks_up_tools( do_activity_reason reason ); +void check_npc_revert( Character &you ); // assigns fetch activity to find requirements requirement_check_result fetch_requirements( Character &you, requirement_id what_we_need, const activity_id &act_id, @@ -183,7 +184,6 @@ std::unordered_set tidy_up_locations( Character &you, const act std::unordered_set read_locations( Character &you, const activity_id &act_id ); std::unordered_set study_locations( Character &you, const activity_id &act_id ); std::unordered_set craft_locations( Character &you, const activity_id &act_id ); -std::unordered_set fetch_locations( Character &you, const activity_id & ); std::unordered_set fish_locations( Character &you, const activity_id &act_id ); std::unordered_set mop_locations( Character &you, const activity_id &act_id ); void prune_same_tile_locations( Character &you, std::unordered_set &src_set ); @@ -253,7 +253,7 @@ bool construction_do( Character &you, const activity_reason_info &act_info, bool farm_do( Character &you, const activity_reason_info &act_info, const tripoint_abs_ms &src, const tripoint_bub_ms &src_loc ); bool fetch_do( Character &you, const activity_reason_info &act_info, - const tripoint_abs_ms &, const tripoint_bub_ms &src_loc ); + const tripoint_abs_ms &, const tripoint_bub_ms & ); bool craft_do( Character &you, const activity_reason_info &act_info, const tripoint_abs_ms &, const tripoint_bub_ms & ); bool disassemble_do( Character &you, const activity_reason_info &act_info, diff --git a/src/activity_type.cpp b/src/activity_type.cpp index 8332d46e33c14..0b45c3c8cc917 100644 --- a/src/activity_type.cpp +++ b/src/activity_type.cpp @@ -93,6 +93,7 @@ void activity_type::load( const JsonObject &jo ) optional( jo, was_loaded, "interruptable_with_kb", interruptable_with_kb_, true ); optional( jo, was_loaded, "can_resume", can_resume_, true ); optional( jo, was_loaded, "multi_activity", multi_activity_, false ); + optional( jo, was_loaded, "fetch_items_to_zone", fetch_items_to_zone_, true ); optional( jo, was_loaded, "refuel_fires", refuel_fires, false ); optional( jo, was_loaded, "auto_needs", auto_needs, false ); optional( jo, was_loaded, "completion_eoc", completion_EOC ); diff --git a/src/activity_type.h b/src/activity_type.h index ce9b14d47edd1..ab58be6446a3b 100644 --- a/src/activity_type.h +++ b/src/activity_type.h @@ -46,6 +46,7 @@ class activity_type based_on_type based_on_ = based_on_type::SPEED; bool can_resume_ = true; bool multi_activity_ = false; + bool fetch_items_to_zone_ = true; bool refuel_fires = false; bool auto_needs = false; float activity_level = NO_EXERCISE; @@ -82,6 +83,9 @@ class activity_type bool multi_activity() const { return multi_activity_; } + bool fetch_items_to_zone() const { + return fetch_items_to_zone_; + } /** * If true, player will refuel one adjacent fire if there is firewood spot adjacent. */ diff --git a/src/character.cpp b/src/character.cpp index 6e21b58bf38a5..9e6d6ae2d4ded 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -5215,6 +5215,9 @@ void Character::assign_activity( const player_activity &act ) } else { if( activity ) { backlog.push_front( activity ); + if( backlog.size() > 100 ) { + debugmsg( "activity backlog exceeded 100, likely an infinite loop" ); + } } activity = act; diff --git a/src/construction.cpp b/src/construction.cpp index 19441e66c8e51..06e906b482e1a 100644 --- a/src/construction.cpp +++ b/src/construction.cpp @@ -1547,7 +1547,7 @@ void build_construction_activity_actor::complete_construction( player_activity & if( you.is_avatar() && !you.backlog.empty() && you.backlog.front().id() == ACT_MULTIPLE_CONSTRUCTION ) { you.backlog.clear(); - you.assign_activity( ACT_MULTIPLE_CONSTRUCTION ); + you.assign_activity( multi_build_construction_activity_actor() ); } } diff --git a/src/handle_action.cpp b/src/handle_action.cpp index ecc25ff6fb1fe..54e42b28f6cc5 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -112,8 +112,6 @@ enum class direction : unsigned int; #endif static const activity_id ACT_FERTILIZE_PLOT( "ACT_FERTILIZE_PLOT" ); -static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); -static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); static const bionic_id bio_remote( "bio_remote" ); @@ -1669,7 +1667,7 @@ static void loot() player_character.assign_activity( ACT_FERTILIZE_PLOT ); break; case ConstructPlots: - player_character.assign_activity( ACT_MULTIPLE_CONSTRUCTION ); + player_character.assign_activity( multi_build_construction_activity_actor() ); break; case MultiFarmPlots: player_character.assign_activity( multi_farm_activity_actor() ); @@ -1687,7 +1685,7 @@ static void loot() player_character.assign_activity( multi_vehicle_repair_activity_actor() ); break; case MultiButchery: - player_character.assign_activity( ACT_MULTIPLE_BUTCHER ); + player_character.assign_activity( multi_butchery_activity_actor() ); break; case MultiMining: player_character.assign_activity( multi_mine_activity_actor() ); diff --git a/src/npcmove.cpp b/src/npcmove.cpp index fd3e3215e1c28..802c36bf4f75c 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -115,8 +115,10 @@ enum class side : int; static const activity_id ACT_CRAFT( "ACT_CRAFT" ); static const activity_id ACT_FIRSTAID( "ACT_FIRSTAID" ); static const activity_id ACT_MOVE_LOOT( "ACT_MOVE_LOOT" ); +static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); static const activity_id ACT_MULTIPLE_CHOP_PLANKS( "ACT_MULTIPLE_CHOP_PLANKS" ); static const activity_id ACT_MULTIPLE_CHOP_TREES( "ACT_MULTIPLE_CHOP_TREES" ); +static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); static const activity_id ACT_MULTIPLE_CRAFT( "ACT_MULTIPLE_CRAFT" ); static const activity_id ACT_MULTIPLE_DIS( "ACT_MULTIPLE_DIS" ); static const activity_id ACT_MULTIPLE_FARM( "ACT_MULTIPLE_FARM" ); @@ -3442,6 +3444,12 @@ bool npc::find_job_to_perform() } else if( elem == ACT_MULTIPLE_DIS ) { assign_activity( multi_disassemble_activity_actor() ); return true; + } else if( elem == ACT_MULTIPLE_CONSTRUCTION ) { + assign_activity( multi_build_construction_activity_actor() ); + return true; + } else if( elem == ACT_MULTIPLE_BUTCHER ) { + assign_activity( multi_butchery_activity_actor() ); + return true; } else if( generic_multi_activity_handler( scan_act, *this->as_character(), true ) ) { assign_activity( elem ); return true; diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index dd1b87113520a..821463e29c725 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -64,8 +64,6 @@ #include "viewer.h" static const activity_id ACT_FIND_MOUNT( "ACT_FIND_MOUNT" ); -static const activity_id ACT_MULTIPLE_BUTCHER( "ACT_MULTIPLE_BUTCHER" ); -static const activity_id ACT_MULTIPLE_CONSTRUCTION( "ACT_MULTIPLE_CONSTRUCTION" ); static const efftype_id effect_allow_sleep( "allow_sleep" ); static const efftype_id effect_asked_for_item( "asked_for_item" ); @@ -238,7 +236,7 @@ void talk_function::sort_loot( npc &p ) void talk_function::do_construction( npc &p ) { - p.assign_activity( ACT_MULTIPLE_CONSTRUCTION ); + p.assign_activity( multi_build_construction_activity_actor() ); } void talk_function::do_mining( npc &p ) @@ -296,7 +294,7 @@ void talk_function::find_mount( npc &p ) void talk_function::do_butcher( npc &p ) { - p.assign_activity( ACT_MULTIPLE_BUTCHER ); + p.assign_activity( multi_butchery_activity_actor() ); } void talk_function::do_chop_plank( npc &p ) diff --git a/tests/act_build_test.cpp b/tests/act_build_test.cpp index 583985191dd04..eda1346e77bcd 100644 --- a/tests/act_build_test.cpp +++ b/tests/act_build_test.cpp @@ -9,6 +9,7 @@ #include #include +#include "activity_actor_definitions.h" #include "activity_handlers.h" #include "avatar.h" #include "build_reqs.h" @@ -79,9 +80,13 @@ void run_activities( Character &u, int max_moves ) { map &here = get_map(); - u.assign_activity( ACT_MULTIPLE_CONSTRUCTION ); + u.assign_activity( multi_build_construction_activity_actor() ); int turns = 0; - while( ( !u.activity.is_null() || u.is_auto_moving() ) && turns < max_moves ) { + while( !u.activity.is_null() || u.is_auto_moving() ) { + if( turns == max_moves ) { + FAIL( "turn count exceeded, infinite loop possible" ); + return; + } u.set_moves( u.get_speed() ); if( u.is_auto_moving() ) { u.setpos( here, here.get_bub( *u.destination_point ) ); @@ -165,6 +170,7 @@ void run_test_case( Character &u ) u.wear_item( item( itype_test_backpack ), false, false ); u.wear_item( item( itype_wearable_test_lamp ), false, true ); + //TODO: this test assumes that tools are on-person, but it should also test without tools on-person u.i_add( item( itype_test_multitool ) ); u.i_add( item( itype_hammer ) ); u.i_add( item( itype_bow_saw ) );