From 1b5b501a33d94878eaf6b19a09e2420c8fe0c3c0 Mon Sep 17 00:00:00 2001 From: davidpwbrown <39344466+davidpwbrown@users.noreply.github.com> Date: Wed, 25 Sep 2019 09:04:39 +0200 Subject: [PATCH] Allow NPCs to repair vehicles (#34108) --- data/json/npcs/TALK_COMMON_ALLY.json | 6 + data/json/player_activities.json | 7 + src/activity_handlers.cpp | 11 +- src/activity_handlers.h | 2 + src/activity_item_handling.cpp | 265 +++++++++++++++++++-------- src/character.h | 2 + src/clzones.cpp | 3 + src/npctalk.cpp | 1 + src/npctalk.h | 1 + src/npctalk_funcs.cpp | 7 + src/player.h | 1 - src/savegame_json.cpp | 7 +- src/vehicle.h | 2 + src/vehicle_part.cpp | 5 + 14 files changed, 232 insertions(+), 88 deletions(-) diff --git a/data/json/npcs/TALK_COMMON_ALLY.json b/data/json/npcs/TALK_COMMON_ALLY.json index 1860e74fb75b5..20282065aa0b6 100644 --- a/data/json/npcs/TALK_COMMON_ALLY.json +++ b/data/json/npcs/TALK_COMMON_ALLY.json @@ -811,6 +811,12 @@ "condition": { "not": "npc_has_activity" }, "effect": "do_vehicle_deconstruct" }, + { + "text": "Please start repairing any vehicles in a repair zone.", + "topic": "TALK_DONE", + "condition": { "not": "npc_has_activity" }, + "effect": "do_vehicle_repair" + }, { "text": "Please chop logs into planks.", "topic": "TALK_DONE", diff --git a/data/json/player_activities.json b/data/json/player_activities.json index cc46fd9d7c43a..de32245fec6ea 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -46,6 +46,13 @@ "verb": "deconstructing a vehicle", "based_on": "neither" }, + { + "id": "ACT_VEHICLE_REPAIR", + "type": "activity_type", + "activity_level": "ACTIVE_EXERCISE", + "verb": "repairing a vehicle", + "based_on": "neither" + }, { "id": "ACT_MULTIPLE_CHOP_PLANKS", "type": "activity_type", diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 46e0ad6b5ac86..e04b7235fc1ff 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -142,6 +142,7 @@ activity_handlers::do_turn_functions = { { activity_id( "ACT_BUILD" ), build_do_turn }, { activity_id( "ACT_EAT_MENU" ), eat_menu_do_turn }, { activity_id( "ACT_VEHICLE_DECONSTRUCTION" ), vehicle_deconstruction_do_turn }, + { activity_id( "ACT_VEHICLE_REPAIR" ), vehicle_repair_do_turn }, { activity_id( "ACT_MULTIPLE_CHOP_TREES" ), chop_trees_do_turn }, { activity_id( "ACT_CONSUME_FOOD_MENU" ), consume_food_menu_do_turn }, { activity_id( "ACT_CONSUME_DRINK_MENU" ), consume_drink_menu_do_turn }, @@ -3196,8 +3197,9 @@ void activity_handlers::churn_finish( player_activity *act, player *p ) void activity_handlers::churn_do_turn( player_activity *act, player *p ) { ( void )act; - ( void )p; - p->set_moves( 0 ); + if( p->is_npc() ) { + p->set_moves( 0 ); + } } void activity_handlers::build_do_turn( player_activity *act, player *p ) @@ -3285,6 +3287,11 @@ void activity_handlers::vehicle_deconstruction_do_turn( player_activity *act, pl generic_multi_activity_handler( *act, *p ); } +void activity_handlers::vehicle_repair_do_turn( player_activity *act, player *p ) +{ + generic_multi_activity_handler( *act, *p ); +} + void activity_handlers::chop_trees_do_turn( player_activity *act, player *p ) { generic_multi_activity_handler( *act, *p ); diff --git a/src/activity_handlers.h b/src/activity_handlers.h index 13b8ab1daf26c..debf07b4d4fc4 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -51,6 +51,7 @@ enum do_activity_reason : int { NEEDS_BUTCHERING, // THere is at least one corpse there to butcher, and theres no need for additional tools ALREADY_WORKING, // somebody is already working there NEEDS_VEH_DECONST, // There is a vehicle part there that we can deconstruct, given the right tools. + NEEDS_VEH_REPAIR, // There is a vehicle part there that can be repaired, given the right tools. NEEDS_FISHING // This spot can be fished, if the right tool is present. }; @@ -136,6 +137,7 @@ void multiple_fish_do_turn( player_activity *act, player *p ); void multiple_construction_do_turn( player_activity *act, player *p ); void multiple_butcher_do_turn( player_activity *act, player *p ); void vehicle_deconstruction_do_turn( player_activity *act, player *p ); +void vehicle_repair_do_turn( player_activity *act, player *p ); void chop_trees_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 ); diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index 33046c00e1560..d019e07efb407 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -875,40 +875,52 @@ static int move_cost( const item &it, const tripoint &src, const tripoint &dest return move_cost_inv( it, src, dest ); } -static void vehicle_deconstruct_activity( player &p, const tripoint src_loc, int vpindex ) +static void vehicle_activity( player &p, const tripoint src_loc, int vpindex, char type ) { vehicle *veh = veh_pointer_or_null( g->m.veh_at( src_loc ) ); if( !veh ) { return; - } else { - if( vpindex >= static_cast( veh->parts.size() ) ) { + } + int time_to_take = 0; + if( vpindex >= static_cast( veh->parts.size() ) ) { + // if parts got removed dduring our work, we cant just carry on removing, we want to repair parts! + // so just bail out, as we dont know if th enext shifted part is suitable for repair. + if( type == 'r' ) { + return; + } else if( type == 'o' ) { vpindex = veh->get_next_shifted_index( vpindex, p ); if( vpindex == -1 ) { return; } } - const vpart_info &vp = veh->part_info( vpindex ); - p.assign_activity( activity_id( "ACT_VEHICLE" ), vp.removal_time( p ), static_cast( 'o' ) ); - // so , NPCs can remove the last part on a position, then there is no vehicle there anymore, - // for someone else who stored that position at the start of their activity. - // so we may need to go looking a bit further afield to find it , at activities end. - for( const auto pt : veh->get_points( true ) ) { - p.activity.coord_set.insert( g->m.getabs( pt ) ); - } - p.activity.values.push_back( g->m.getabs( src_loc ).x ); // values[0] - p.activity.values.push_back( g->m.getabs( src_loc ).y ); // values[1] - p.activity.values.push_back( point_zero.x ); // values[2] - p.activity.values.push_back( point_zero.y ); // values[3] - p.activity.values.push_back( -point_zero.x ); // values[4] - p.activity.values.push_back( -point_zero.y ); // values[5] - p.activity.values.push_back( veh->index_of_part( &veh->parts[vpindex] ) ); // values[6] - p.activity.str_values.push_back( vp.get_id().str() ); - // this would only be used for refilling tasks - item_location target; - p.activity.targets.emplace_back( std::move( target ) ); - p.activity.placement = g->m.getabs( src_loc ); - p.activity_vehicle_part_index = -1; } + const vpart_info &vp = veh->part_info( vpindex ); + const vehicle_part part = veh->parts[ vpindex ]; + if( type == 'r' ) { + time_to_take = vp.repair_time( p ) * part.damage() / part.max_damage(); + } else if( type == 'o' ) { + time_to_take = vp.removal_time( p ); + } + p.assign_activity( activity_id( "ACT_VEHICLE" ), time_to_take, static_cast( type ) ); + // so , NPCs can remove the last part on a position, then there is no vehicle there anymore, + // for someone else who stored that position at the start of their activity. + // so we may need to go looking a bit further afield to find it , at activities end. + for( const auto pt : veh->get_points( true ) ) { + p.activity.coord_set.insert( g->m.getabs( pt ) ); + } + p.activity.values.push_back( g->m.getabs( src_loc ).x ); // values[0] + p.activity.values.push_back( g->m.getabs( src_loc ).y ); // values[1] + p.activity.values.push_back( point_zero.x ); // values[2] + p.activity.values.push_back( point_zero.y ); // values[3] + p.activity.values.push_back( -point_zero.x ); // values[4] + p.activity.values.push_back( -point_zero.y ); // values[5] + p.activity.values.push_back( veh->index_of_part( &veh->parts[vpindex] ) ); // values[6] + p.activity.str_values.push_back( vp.get_id().str() ); + // this would only be used for refilling tasks + item_location target; + p.activity.targets.emplace_back( std::move( target ) ); + p.activity.placement = g->m.getabs( src_loc ); + p.activity_vehicle_part_index = -1; } static void move_item( player &p, item &it, int quantity, const tripoint &src, @@ -1122,7 +1134,7 @@ static std::string random_string( size_t length ) } static bool are_requirements_nearby( const std::vector &loot_spots, - const requirement_id &needed_things, const player &p, const activity_id activity_to_restore, + const requirement_id &needed_things, player &p, const activity_id activity_to_restore, bool in_loot_zones ) { zone_manager &mgr = zone_manager::get_manager(); @@ -1137,6 +1149,8 @@ static bool are_requirements_nearby( const std::vector &loot_spots, activity_to_restore == activity_id( "ACT_MULTIPLE_BUTCHER" ) || p.backlog.front().id() == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) || activity_to_restore == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) || + p.backlog.front().id() == activity_id( "ACT_VEHICLE_REPAIR" ) || + activity_to_restore == activity_id( "ACT_VEHICLE_REPAIR" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_CHOP_TREES" ) || activity_to_restore == activity_id( "ACT_MULTIPLE_CHOP_TREES" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_FISH" ) || @@ -1178,28 +1192,67 @@ static bool are_requirements_nearby( const std::vector &loot_spots, } } } + for( item *elem : p.inv_dump() ) { + temp_inv += *elem; + } return needed_things.obj().can_make_with_inventory( temp_inv, is_crafting_component ); } +static bool has_skill_for_vehicle_work( std::map required_skills, player &p ) +{ + for( const auto &e : required_skills ) { + bool hasSkill = p.get_skill_level( e.first ) >= e.second; + if( !hasSkill ) { + return false; + } + } + return true; +} + static activity_reason_info 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_VEHICLE_DECONSTRUCTION" ) ) { - if( g->m.getlocal( g->u.activity.placement ) == src_loc ) { - return activity_reason_info::fail( ALREADY_WORKING ); + if( act == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) || + act == activity_id( "ACT_VEHICLE_REPAIR" ) ) { + std::vector already_working_indexes; + vehicle *veh = veh_pointer_or_null( g->m.veh_at( src_loc ) ); + if( !veh ) { + return activity_reason_info::fail( NO_ZONE ); + } + // if the vehicle is moving or player is controlling it. + if( abs( veh->velocity ) > 100 || veh->player_in_control( g->u ) ) { + return activity_reason_info::fail( NO_ZONE ); } for( const npc &guy : g->all_npcs() ) { - if( g->m.getlocal( guy.activity.placement ) == src_loc || guy.pos() == src_loc ) { + if( &guy == &p ) { + continue; + } + // If the NPC has an activity - make sure theyre not duplicating work. + tripoint guy_work_spot; + if( guy.has_player_activity() && guy.activity.placement != tripoint_min ) { + guy_work_spot = g->m.getlocal( guy.activity.placement ); + } + // If their position or intended position or player position/intended position + // then discount, dont need to move each other out of the way. + if( g->m.getlocal( g->u.activity.placement ) == src_loc || + guy_work_spot == src_loc || guy.pos() == src_loc || ( p.is_npc() && g->u.pos() == src_loc ) ) { return activity_reason_info::fail( ALREADY_WORKING ); } + if( guy_work_spot != tripoint_zero ) { + vehicle *other_veh = veh_pointer_or_null( g->m.veh_at( guy_work_spot ) ); + // working on same vehicle - store the index to check later. + if( other_veh && other_veh == veh && guy.activity_vehicle_part_index != -1 ) { + already_working_indexes.push_back( guy.activity_vehicle_part_index ); + } + } + if( g->u.activity_vehicle_part_index != -1 ) { + already_working_indexes.push_back( g->u.activity_vehicle_part_index ); + } } - vehicle *veh = veh_pointer_or_null( g->m.veh_at( src_loc ) ); - if( !veh ) { - return activity_reason_info::fail( NO_ZONE ); - } else { + if( act == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) ) { // find out if there is a vehicle part here we can remove. std::vector parts = veh->get_parts_at( src_loc, "", part_status_flag::any ); for( vehicle_part *part_elem : parts ) { @@ -1209,30 +1262,20 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe if( vpindex == -1 || !veh->can_unmount( vpindex ) ) { continue; } - for( const npc &guy : g->all_npcs() ) { - if( guy.disp_name() != p.disp_name() && guy.activity_vehicle_part_index != -1 && - guy.activity_vehicle_part_index == vpindex ) { - continue; - } - } - // if the vehicle is moving or player is controlling it. - if( abs( veh->velocity ) > 100 || veh->engine_on ) { + // this is the same part that somebody else wants to work on, or already is. + if( std::find( already_working_indexes.begin(), already_working_indexes.end(), + vpindex ) != already_working_indexes.end() ) { continue; } // dont have skill to remove it - std::map removal_skills = vpinfo.removal_skills; - for( const auto &e : removal_skills ) { - bool hasSkill = p.get_skill_level( e.first ) >= e.second; - if( !hasSkill ) { - continue; - } + if( !has_skill_for_vehicle_work( vpinfo.removal_skills, p ) ) { + continue; } item base( vpinfo.item ); if( base.is_wheel() ) { // no wheel removal yet continue; } - const quality_id qual = LIFT; const int max_lift = p.best_nearby_lifting_assist( src_loc ); const int lvl = ceil( units::quantity( base.weight() ) / TOOL_LIFT_FACTOR ); @@ -1242,13 +1285,6 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe continue; } const auto &reqs = vpinfo.removal_requirements(); - const std::string ran_str = random_string( 10 ); - const requirement_id req_id( ran_str ); - requirement_data::save_requirement( reqs, req_id ); - std::vector points_to_check; - for( const auto elem : g->m.points_in_radius( src_loc, PICKUP_RANGE - 1 ) ) { - points_to_check.push_back( elem ); - } const inventory &inv = p.crafting_inventory(); const bool can_make = reqs.can_make_with_inventory( inv, is_crafting_component ); p.set_value( "veh_index_type", vpinfo.name() ); @@ -1260,8 +1296,39 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe return activity_reason_info::ok( NEEDS_VEH_DECONST ); } } - return activity_reason_info::fail( NO_ZONE ); + } else if( act == activity_id( "ACT_VEHICLE_REPAIR" ) ) { + // find out if there is a vehicle part here we can repair. + std::vector parts = veh->get_parts_at( src_loc, "", part_status_flag::any ); + for( vehicle_part *part_elem : parts ) { + const vpart_info &vpinfo = part_elem->info(); + int vpindex = veh->index_of_part( part_elem, true ); + // if part is undamaged or beyond repair - can skip it. + if( part_elem->is_broken() || part_elem->damage() == 0 ) { + continue; + } + if( std::find( already_working_indexes.begin(), already_working_indexes.end(), + vpindex ) != already_working_indexes.end() ) { + continue; + } + // dont have skill to repair it + if( !has_skill_for_vehicle_work( vpinfo.repair_skills, p ) ) { + continue; + } + const auto &reqs = vpinfo.repair_requirements(); + const inventory &inv = p.crafting_inventory(); + const bool can_make = reqs.can_make_with_inventory( inv, is_crafting_component ); + p.set_value( "veh_index_type", vpinfo.name() ); + // temporarily store the intended index, we do this so two NPCs dont try and work on the same part at same time. + p.activity_vehicle_part_index = vpindex; + if( !can_make ) { + return activity_reason_info::fail( NEEDS_VEH_REPAIR ); + } else { + return activity_reason_info::ok( NEEDS_VEH_REPAIR ); + } + } } + p.activity_vehicle_part_index = -1; + return activity_reason_info::fail( NO_ZONE ); } if( act == activity_id( "ACT_MULTIPLE_FISH" ) ) { if( !g->m.has_flag( "FISHABLE", src_loc ) ) { @@ -1447,14 +1514,13 @@ static std::vector> requirements_map( player 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" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_CHOP_PLANKS" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_BUTCHER" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_CHOP_TREES" ) || p.backlog.front().id() == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) || + p.backlog.front().id() == activity_id( "ACT_VEHICLE_REPAIR" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_FISH" ); // where it is, what it is, how much of it, and how much in total is required of that item. std::vector> requirement_map; @@ -1524,7 +1590,15 @@ static std::vector> requirements_map( player point_elem ) != already_there_spots.end() ) { comp_elem.count -= stack_elem.count(); } - temp_map[stack_elem.typeId()] += stack_elem.count(); + if( comp_elem.by_charges() ) { + // we dont care if there are 10 welders with 5 charges each + // we only want the one welder that has the required charge. + if( stack_elem.ammo_remaining() >= comp_elem.count ) { + temp_map[stack_elem.typeId()] += stack_elem.ammo_remaining(); + } + } else { + temp_map[stack_elem.typeId()] += stack_elem.count(); + } } } } @@ -1534,15 +1608,6 @@ static std::vector> requirements_map( player 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() ) { @@ -1707,6 +1772,10 @@ static std::vector> requirements_map( player } } } + for( const std::tuple elem : final_map ) { + add_msg( m_debug, "%s is fetching %s from x: %d y: %d ", p.disp_name(), std::get<1>( elem ), + std::get<0>( elem ).x, std::get<0>( elem ).y ); + } return final_map; } @@ -1855,6 +1924,7 @@ static void fetch_activity( player &p, const tripoint src_loc, activity_id activ } else if( !p.backlog.empty() && ( p.backlog.front().id() == activity_id( "ACT_MULTIPLE_FARM" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_CHOP_PLANKS" ) || p.backlog.front().id() == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) || + p.backlog.front().id() == activity_id( "ACT_VEHICLE_REPAIR" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_BUTCHER" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_CHOP_TREES" ) || p.backlog.front().id() == activity_id( "ACT_MULTIPLE_FISH" ) ) ) { @@ -2220,6 +2290,16 @@ static bool chop_tree_activity( player &p, const tripoint &src_loc ) return false; } +static void check_npc_revert( player &p ) +{ + if( p.is_npc() ) { + npc *guy = dynamic_cast( &p ); + if( guy ) { + guy->revert_after_activity(); + } + } +} + void generic_multi_activity_handler( player_activity &act, player &p ) { // First get the things that are activity-agnostic. @@ -2270,6 +2350,9 @@ void generic_multi_activity_handler( player_activity &act, player &p ) if( activity_to_restore == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) ) { src_set = mgr.get_near( zone_type_id( "VEHICLE_DECONSTRUCT" ), abspos, 60 ); } + if( activity_to_restore == activity_id( "ACT_VEHICLE_REPAIR" ) ) { + src_set = mgr.get_near( zone_type_id( "VEHICLE_REPAIR" ), abspos, 60 ); + } if( activity_to_restore == activity_id( "ACT_MULTIPLE_CHOP_TREES" ) ) { src_set = mgr.get_near( zone_type_id( "CHOP_TREES" ), abspos, 60 ); } @@ -2364,6 +2447,7 @@ void generic_multi_activity_handler( player_activity &act, player &p ) activity_to_restore == activity_id( "ACT_MULTIPLE_CHOP_PLANKS" ) || activity_to_restore == activity_id( "ACT_MULTIPLE_CHOP_TREES" ) || activity_to_restore == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) || + activity_to_restore == activity_id( "ACT_VEHICLE_REPAIR" ) || activity_to_restore == activity_id( "ACT_MULTIPLE_FISH" ) || ( activity_to_restore == activity_id( "ACT_MULTIPLE_CONSTRUCTION" ) && !g->m.partial_con_at( src_loc ) ); @@ -2383,7 +2467,8 @@ void generic_multi_activity_handler( player_activity &act, player &p ) continue; } else if( ( !can_do_it ) && ( reason == NO_COMPONENTS || reason == NEEDS_PLANTING || reason == NEEDS_TILLING || reason == NEEDS_CHOPPING || reason == NEEDS_BUTCHERING || - reason == NEEDS_BIG_BUTCHERING || reason == NEEDS_VEH_DECONST || reason == NEEDS_TREE_CHOPPING || + reason == NEEDS_BIG_BUTCHERING || reason == NEEDS_VEH_DECONST || reason == NEEDS_VEH_REPAIR || + reason == NEEDS_TREE_CHOPPING || reason == NEEDS_FISHING ) ) { // 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? @@ -2407,15 +2492,20 @@ void generic_multi_activity_handler( player_activity &act, player &p ) // its a construction and we need the components. const construction &built_chosen = list_constructions[ *act_info.con_idx ]; what_we_need = built_chosen.requirements; - } else if( reason == NEEDS_VEH_DECONST ) { + } else if( reason == NEEDS_VEH_DECONST || reason == NEEDS_VEH_REPAIR ) { vehicle *veh = veh_pointer_or_null( g->m.veh_at( src_loc ) ); // we already checked this in can_do_activity() but check again just incase. if( !veh ) { - p.activity_vehicle_part_index = 1; + p.activity_vehicle_part_index = -1; continue; } const vpart_info &vpinfo = veh->part_info( p.activity_vehicle_part_index ); - const auto &reqs = vpinfo.removal_requirements(); + requirement_data reqs; + if( reason == NEEDS_VEH_DECONST ) { + reqs = vpinfo.removal_requirements(); + } else if( reason == NEEDS_VEH_REPAIR ) { + reqs = vpinfo.repair_requirements(); + } const std::string ran_str = random_string( 10 ); const requirement_id req_id( ran_str ); requirement_data::save_requirement( reqs, req_id ); @@ -2454,12 +2544,12 @@ void generic_multi_activity_handler( player_activity &act, player &p ) } bool tool_pickup = reason == NEEDS_TILLING || reason == NEEDS_PLANTING || reason == NEEDS_CHOPPING || reason == NEEDS_BUTCHERING || reason == NEEDS_BIG_BUTCHERING || - reason == NEEDS_TREE_CHOPPING || reason == NEEDS_VEH_DECONST; + reason == NEEDS_TREE_CHOPPING || reason == NEEDS_VEH_DECONST || reason == NEEDS_VEH_REPAIR; // 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." ) ); - if( reason == NEEDS_VEH_DECONST ) { + if( reason == NEEDS_VEH_DECONST || reason == NEEDS_VEH_REPAIR ) { p.activity_vehicle_part_index = -1; } continue; @@ -2487,6 +2577,7 @@ void generic_multi_activity_handler( player_activity &act, player &p ) if( candidates.empty() ) { p.activity = player_activity(); p.backlog.clear(); + check_npc_revert( p ); return; } p.backlog.front().coords.push_back( g->m.getabs( candidates[std::max( 0, @@ -2502,12 +2593,12 @@ void generic_multi_activity_handler( player_activity &act, player &p ) // check if we found path to source / adjacent tile if( route.empty() ) { + check_npc_revert( p ); return; } if( p.moves <= 0 ) { // Restart activity and break from cycle. p.assign_activity( activity_to_restore ); - p.activity_vehicle_part_index = -1; return; } // set the destination and restart activity after player arrives there @@ -2573,32 +2664,44 @@ void generic_multi_activity_handler( player_activity &act, player &p ) return; } else if( reason == NEEDS_VEH_DECONST ) { p.backlog.push_front( activity_to_restore ); - vehicle_deconstruct_activity( p, src_loc, p.activity_vehicle_part_index ); + vehicle_activity( p, src_loc, p.activity_vehicle_part_index, 'o' ); + p.activity_vehicle_part_index = -1; + return; + } else if( reason == NEEDS_VEH_REPAIR ) { + p.backlog.push_front( activity_to_restore ); + vehicle_activity( p, src_loc, p.activity_vehicle_part_index, 'r' ); + p.activity_vehicle_part_index = -1; return; } } if( p.moves <= 0 ) { // Restart activity and break from cycle. p.assign_activity( activity_to_restore ); + p.activity_vehicle_part_index = -1; 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(); - } + check_npc_revert( p ); // 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" ) || activity_to_restore == activity_id( "ACT_MULTIPLE_CHOP_PLANKS" ) || activity_to_restore == activity_id( "ACT_MULTIPLE_BUTCHER" ) || activity_to_restore == activity_id( "ACT_VEHICLE_DECONSTRUCTION" ) || + activity_to_restore == activity_id( "ACT_VEHICLE_REPAIR" ) || activity_to_restore == activity_id( "ACT_MULTIPLE_CHOP_TREES" ) ) { p.assign_activity( activity_id( "ACT_TIDY_UP" ) ); + if( p.is_npc() ) { + npc *guy = dynamic_cast( &p ); + if( guy ) { + guy->set_attitude( NPCATT_ACTIVITY ); + guy->set_mission( NPC_MISSION_ACTIVITY ); + } + } } - p.activity_vehicle_part_index = -1; } + p.activity_vehicle_part_index = -1; } static cata::optional find_best_fire( const std::vector &from, diff --git a/src/character.h b/src/character.h index 88de3f9567dc7..1b9ae1d9fdfaa 100644 --- a/src/character.h +++ b/src/character.h @@ -877,6 +877,8 @@ class Character : public Creature, public visitable std::shared_ptr mounted_creature; // for loading NPC mounts int mounted_creature_id; + // for vehicle work + int activity_vehicle_part_index = -1; void initialize_stomach_contents(); diff --git a/src/clzones.cpp b/src/clzones.cpp index 85e8c56f2d5f5..ecf6e99954d37 100644 --- a/src/clzones.cpp +++ b/src/clzones.cpp @@ -160,6 +160,9 @@ zone_manager::zone_manager() types.emplace( zone_type_id( "VEHICLE_DECONSTRUCT" ), zone_type( translate_marker( "Vehicle Deconstruct Zone" ), translate_marker( "Any vehicles in this area are marked for deconstruction." ) ) ); + types.emplace( zone_type_id( "VEHICLE_REPAIR" ), + zone_type( translate_marker( "Vehicle Repair Zone" ), + translate_marker( "Any vehicles in this area are marked for repair work." ) ) ); types.emplace( zone_type_id( "CAMP_FOOD" ), zone_type( translate_marker( "Basecamp: Food" ), translate_marker( "Items in this zone will be added to a basecamp's food supply in the Distribute Food mission." ) ) ); diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 5d8d7916f4583..925c91830a00a 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -2508,6 +2508,7 @@ void talk_effect_t::parse_string_effect( const std::string &effect_id, JsonObjec WRAP( dismount ), WRAP( do_chop_plank ), WRAP( do_vehicle_deconstruct ), + WRAP( do_vehicle_repair ), WRAP( do_chop_trees ), WRAP( do_fishing ), WRAP( do_construction ), diff --git a/src/npctalk.h b/src/npctalk.h index 8ad16dc94bdde..73b0ef4ae59f4 100644 --- a/src/npctalk.h +++ b/src/npctalk.h @@ -41,6 +41,7 @@ void do_construction( npc & ); void do_read( npc & ); void do_chop_plank( npc & ); void do_vehicle_deconstruct( npc & ); +void do_vehicle_repair( npc & ); void do_chop_trees( npc & ); void do_fishing( npc & ); void do_farming( npc & ); diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index 0896943e9d1c6..a19f10e26fb93 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -267,6 +267,13 @@ void talk_function::do_vehicle_deconstruct( npc &p ) p.set_mission( NPC_MISSION_ACTIVITY ); } +void talk_function::do_vehicle_repair( npc &p ) +{ + p.set_attitude( NPCATT_ACTIVITY ); + p.assign_activity( activity_id( "ACT_VEHICLE_REPAIR" ) ); + p.set_mission( NPC_MISSION_ACTIVITY ); +} + void talk_function::do_chop_trees( npc &p ) { p.set_attitude( NPCATT_ACTIVITY ); diff --git a/src/player.h b/src/player.h index 0252c82d8ab69..8d2c1f1642b6b 100644 --- a/src/player.h +++ b/src/player.h @@ -1575,7 +1575,6 @@ class player : public Character player_activity activity; std::list backlog; cata::optional destination_point; - int activity_vehicle_part_index = -1; int volume; const profession *prof; diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 6a3bf7764f593..c7fee7e97613a 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -387,7 +387,8 @@ void Character::load( JsonObject &data ) data.read( "stored_calories", stored_calories ); data.read( "radiation", radiation ); data.read( "oxygen", oxygen ); - + // npc activity on vehicles. + data.read( "activity_vehicle_part_index", activity_vehicle_part_index ); // health data.read( "healthy", healthy ); data.read( "healthy_mod", healthy_mod ); @@ -541,6 +542,7 @@ void Character::store( JsonOut &json ) const json.member( "per_bonus", per_bonus ); json.member( "int_bonus", int_bonus ); + json.member( "activity_vehicle_part_index", activity_vehicle_part_index ); // NPC activity // health json.member( "healthy", healthy ); json.member( "healthy_mod", healthy_mod ); @@ -642,7 +644,6 @@ void player::store( JsonOut &json ) const json.end_array(); json.member( "worn", worn ); // also saves contents - json.member( "activity_vehicle_part_index", activity_vehicle_part_index ); // NPC activity json.member( "inv" ); inv.json_save_items( json ); @@ -790,8 +791,6 @@ void player::load( JsonObject &data ) on_stat_change( "pkill", pkill ); on_stat_change( "perceived_pain", get_perceived_pain() ); - data.read( "activity_vehicle_part_index", activity_vehicle_part_index ); - int tmptar; int tmptartyp = 0; diff --git a/src/vehicle.h b/src/vehicle.h index 1a0b4c908be00..4cb429da32476 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -332,6 +332,8 @@ struct vehicle_part { /** Current part damage in same units as item::damage. */ int damage() const; + /** max damage of part base */ + int max_damage() const; /** Current part damage level in same units as item::damage_level */ int damage_level( int max ) const; diff --git a/src/vehicle_part.cpp b/src/vehicle_part.cpp index 7109ccda8cde0..80fe685e7167f 100644 --- a/src/vehicle_part.cpp +++ b/src/vehicle_part.cpp @@ -123,6 +123,11 @@ int vehicle_part::damage() const return base.damage(); } +int vehicle_part::max_damage() const +{ + return base.max_damage(); +} + int vehicle_part::damage_level( int max ) const { return base.damage_level( max );