diff --git a/data/mods/TEST_DATA/vehicle.json b/data/mods/TEST_DATA/vehicle.json index 4280403f73846..27c454259fb6f 100644 --- a/data/mods/TEST_DATA/vehicle.json +++ b/data/mods/TEST_DATA/vehicle.json @@ -207,7 +207,7 @@ { "x": 0, "y": -2, "parts": [ "frame_vertical", "seat" ] }, { "x": 1, "y": -2, "parts": [ "frame_vertical", "seat", "wheel_mount_medium_steerable", "wheel" ] }, { "x": -1, "y": -2, "parts": [ "frame_vertical", "seat", "wheel_mount_medium_steerable", "wheel" ] }, - { "x": 1, "y": 0, "parts": [ "frame_vertical", "door" ] }, + { "x": 1, "y": 0, "parts": [ "frame_vertical", "solar_panel" ] }, { "x": -1, "y": 0, "parts": [ "frame_vertical", "engine_electric", "storage_battery" ] } ] }, @@ -231,9 +231,9 @@ { "x": -2, "y": -2, "parts": [ "frame_vertical", "seat" ] }, { "x": -1, "y": -1, "parts": [ "frame_vertical", "seat" ] }, { "x": 2, "y": -2, "parts": [ "frame_vertical", "seat" ] }, - { "x": 1, "y": 0, "parts": [ "frame_vertical", "door" ] }, - { "x": 2, "y": 0, "parts": [ "frame_vertical", "door" ] }, - { "x": 2, "y": -1, "parts": [ "frame_vertical", "door" ] }, + { "x": 1, "y": 0, "parts": [ "frame_vertical", "solar_panel" ] }, + { "x": 2, "y": 0, "parts": [ "frame_vertical", "solar_panel" ] }, + { "x": 2, "y": -1, "parts": [ "frame_vertical", "solar_panel" ] }, { "x": -1, "y": 0, "parts": [ "frame_vertical", "engine_v6" ] }, { "x": -2, "y": 0, "parts": [ "frame_vertical", "engine_v6" ] } ] @@ -362,5 +362,71 @@ { "item": "motor_tiny", "prob": 25 } ], "damage_reduction": { "all": 32 } + }, + { + "id": "test_van", + "type": "vehicle", + "name": "Test Van", + "blueprint": [ + [ "o o " ], + [ "-O---+-O" ], + [ "+===|#'|" ], + [ "+===|o'>" ], + [ "+===|#'|" ], + [ "-O---+-O" ], + [ "o o " ] + ], + "parts": [ + { "x": 0, "y": 0, "parts": [ "frame_vertical", "seat", "controls", "dashboard", "roof" ] }, + { "x": 0, "y": 1, "parts": [ "frame_vertical", "box", "roof" ] }, + { "x": 0, "y": 2, "parts": [ "frame_vertical", "seat", "roof" ] }, + { "x": 0, "y": -1, "parts": [ "frame_vertical", "door" ] }, + { "x": 0, "y": 3, "parts": [ "frame_vertical", "door" ] }, + { "x": 1, "y": -1, "parts": [ "frame_horizontal", "windshield" ] }, + { "x": 1, "y": 0, "parts": [ "frame_horizontal", "windshield" ] }, + { "x": 1, "y": 1, "parts": [ "frame_horizontal", "windshield" ] }, + { "x": 1, "y": 2, "parts": [ "frame_horizontal", "windshield" ] }, + { "x": 1, "y": 3, "parts": [ "frame_horizontal" ] }, + { "x": 1, "y": -2, "part": "wing_mirror" }, + { "x": 1, "y": 4, "part": "wing_mirror" }, + { "x": 2, "y": -1, "parts": [ "frame_nw", "halfboard_nw", "headlight" ] }, + { "x": 2, "y": -1, "parts": [ "wheel_mount_medium_steerable", "wheel_wide" ] }, + { "x": 2, "y": 0, "parts": [ "frame_horizontal", "halfboard_horizontal" ] }, + { "x": 2, "y": 1, "parts": [ "frame_horizontal", "halfboard_horizontal" ] }, + { "x": 2, "y": 1, "parts": [ "engine_v6", "alternator_car", "battery_car" ] }, + { "x": 2, "y": 2, "parts": [ "frame_horizontal", "halfboard_horizontal" ] }, + { "x": 2, "y": 3, "parts": [ "frame_ne", "halfboard_ne", "headlight" ] }, + { "x": 2, "y": 3, "parts": [ "wheel_mount_medium_steerable", "wheel_wide" ] }, + { "x": -1, "y": -1, "parts": [ "frame_horizontal", "board_horizontal" ] }, + { "x": -1, "y": 0, "parts": [ "frame_horizontal", "board_horizontal" ] }, + { "x": -1, "y": 1, "parts": [ "frame_horizontal", "board_horizontal" ] }, + { "x": -1, "y": 2, "parts": [ "frame_horizontal", "board_horizontal" ] }, + { "x": -1, "y": 3, "parts": [ "frame_horizontal", "board_horizontal" ] }, + { "x": -1, "y": -1, "part": "tank", "fuel": "gasoline" }, + { "x": -2, "y": -1, "parts": [ "frame_vertical", "door" ] }, + { "x": -2, "y": 0, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -2, "y": 1, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -2, "y": 2, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -2, "y": 3, "parts": [ "frame_vertical", "door" ] }, + { "x": -3, "y": -1, "parts": [ "frame_vertical", "board_vertical", "roof" ] }, + { "x": -3, "y": 0, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -3, "y": 1, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -3, "y": 2, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -3, "y": 3, "parts": [ "frame_vertical", "board_vertical", "roof" ] }, + { "x": -4, "y": -1, "parts": [ "frame_vertical", "board_vertical", "roof" ] }, + { "x": -4, "y": 0, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -4, "y": 1, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -4, "y": 2, "parts": [ "frame_vertical", "cargo_space", "roof" ] }, + { "x": -4, "y": 3, "parts": [ "frame_vertical", "board_vertical", "roof" ] }, + { "x": -4, "y": -1, "parts": [ "wheel_mount_medium", "wheel_wide" ] }, + { "x": -4, "y": 3, "parts": [ "wheel_mount_medium", "wheel_wide" ] }, + { "x": -5, "y": -1, "parts": [ "frame_sw", "board_sw", "roof" ] }, + { "x": -5, "y": 0, "parts": [ "frame_horizontal", "door_shutter", "roof" ] }, + { "x": -5, "y": 1, "parts": [ "frame_horizontal", "door_shutter", "roof" ] }, + { "x": -5, "y": 2, "parts": [ "frame_horizontal", "door_shutter", "roof" ] }, + { "x": -5, "y": 3, "parts": [ "frame_se", "board_se", "roof" ] }, + { "x": -5, "y": -2, "part": "wing_mirror" }, + { "x": -5, "y": 4, "part": "external_tank_small" } + ] } ] diff --git a/src/explosion.cpp b/src/explosion.cpp index 2be08a1d466ca..6daaeec2e960f 100644 --- a/src/explosion.cpp +++ b/src/explosion.cpp @@ -301,8 +301,8 @@ static void do_blast( const tripoint &p, const float power, if( const optional_vpart_position vp = here.veh_at( pt ) ) { // TODO: Make this weird unit used by vehicle::damage more sensible - vp->vehicle().damage( vp->part_index(), force, fire ? damage_type::HEAT : damage_type::BASH, - false ); + vp->vehicle().damage( here, vp->part_index(), force, + fire ? damage_type::HEAT : damage_type::BASH, false ); } Creature *critter = creatures.creature_at( pt, true ); @@ -457,7 +457,7 @@ static std::vector shrapnel( const tripoint &src, int power, } if( here.impassable( target ) ) { if( optional_vpart_position vp = here.veh_at( target ) ) { - vp->vehicle().damage( vp->part_index(), damage / 10 ); + vp->vehicle().damage( here, vp->part_index(), damage / 10 ); } else { here.bash( target, damage / 100, true ); } diff --git a/src/game.cpp b/src/game.cpp index c9946b9d32596..70182ed0eaaf9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5040,7 +5040,7 @@ bool game::forced_door_closing( const tripoint &p, const ter_id &door_type, int if( bash_dmg <= 0 ) { return false; } - vp->vehicle().damage( vp->part_index(), bash_dmg ); + vp->vehicle().damage( m, vp->part_index(), bash_dmg ); if( m.veh_at( p ) ) { // Check again in case all parts at the door tile // have been destroyed, if there is still a vehicle diff --git a/src/grab.cpp b/src/grab.cpp index eeb08e61723f4..a477f4a516828 100644 --- a/src/grab.cpp +++ b/src/grab.cpp @@ -14,6 +14,7 @@ #include "units_utility.h" #include "vehicle.h" #include "vpart_position.h" +#include "vpart_range.h" static const efftype_id effect_harnessed( "harnessed" ); @@ -31,8 +32,8 @@ bool game::grabbed_veh_move( const tripoint &dp ) return false; } const int grabbed_part = grabbed_vehicle_vp->part_index(); - for( int part_index = 0; part_index < grabbed_vehicle->part_count(); ++part_index ) { - monster *mon = grabbed_vehicle->get_monster( part_index ); + for( const vpart_reference &vpr : grabbed_vehicle->get_all_parts() ) { + monster *mon = grabbed_vehicle->get_monster( vpr.part_index() ); if( mon != nullptr && mon->has_effect( effect_harnessed ) ) { add_msg( m_info, _( "You cannot move this vehicle whilst your %s is harnessed!" ), mon->get_name() ); diff --git a/src/iuse.cpp b/src/iuse.cpp index 2acd5d5561fd7..3635e44444511 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -5247,6 +5247,7 @@ cata::optional iuse::unfold_generic( Character *p, item *it, bool, const tr } map &here = get_map(); vehicle *veh = here.add_vehicle( vehicle_prototype_none, p->pos(), 0_degrees, 0, 0, false ); + veh->suspend_refresh(); if( veh == nullptr ) { p->add_msg_if_player( m_info, _( "There's no room to unfold the %s." ), it->tname() ); return cata::nullopt; @@ -5273,7 +5274,7 @@ cata::optional iuse::unfold_generic( Character *p, item *it, bool, const tr return 0; } } - + veh->enable_refresh(); here.add_vehicle_to_cache( veh ); std::string unfold_msg = it->get_var( "unfold_msg" ); diff --git a/src/map.cpp b/src/map.cpp index b8a6265aba0c8..5c893403fabec 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -262,7 +262,7 @@ void map::add_vehicle_to_cache( vehicle *veh ) } // Get parts - for( const vpart_reference &vpr : veh->get_all_parts() ) { + for( const vpart_reference &vpr : veh->get_all_parts_with_fakes() ) { if( vpr.part().removed ) { continue; } @@ -575,20 +575,21 @@ vehicle *map::move_vehicle( vehicle &veh, const tripoint &dp, const tileray &fac if( coll.type == veh_coll_veh ) { continue; } - if( coll.part > veh.part_count() || - veh.part( coll.part ).removed ) { - continue; - } + int part_num = veh.get_non_fake_part( coll.part ); const point &collision_point = veh.part( coll.part ).mount; const int coll_dmg = coll.imp; + // Shock damage, if the target part is a rotor treat as an aimed hit. - if( veh.part_info( coll.part ).rotor_diameter() > 0 ) { - veh.damage( coll.part, coll_dmg, damage_type::BASH, true ); - } else { - impulse += coll_dmg; - veh.damage( coll.part, coll_dmg, damage_type::BASH ); - veh.damage_all( coll_dmg / 2, coll_dmg, damage_type::BASH, collision_point ); + // don't try to deal damage to invalid part (probably removed or destroyed) + if( part_num != -1 ) { + if( veh.part_info( part_num ).rotor_diameter() > 0 ) { + veh.damage( *this, part_num, coll_dmg, damage_type::BASH, true ); + } else { + impulse += coll_dmg; + veh.damage( *this, part_num, coll_dmg, damage_type::BASH ); + veh.damage_all( coll_dmg / 2, coll_dmg, damage_type::BASH, collision_point ); + } } } @@ -877,21 +878,23 @@ float map::vehicle_vehicle_collision( vehicle &veh, vehicle &veh2, if( &veh2 != static_cast( veh_veh_coll.target ) ) { continue; } + int coll_part = veh.get_non_fake_part( veh_veh_coll.part ); + int target_part = veh2.get_non_fake_part( veh_veh_coll.target_part ); - int parm1 = veh.part_with_feature( veh_veh_coll.part, VPFLAG_ARMOR, true ); - if( parm1 < 0 ) { - parm1 = veh_veh_coll.part; + int coll_parm = veh.part_with_feature( coll_part, VPFLAG_ARMOR, true ); + if( coll_parm < 0 ) { + coll_parm = coll_part; } - int parm2 = veh2.part_with_feature( veh_veh_coll.target_part, VPFLAG_ARMOR, true ); - if( parm2 < 0 ) { - parm2 = veh_veh_coll.target_part; + int target_parm = veh2.part_with_feature( target_part, VPFLAG_ARMOR, true ); + if( target_parm < 0 ) { + target_parm = target_part; } - epicenter1 += veh.part( parm1 ).mount; - veh.damage( parm1, dmg1_part, damage_type::BASH ); + epicenter1 += veh.part( coll_parm ).mount; + veh.damage( *this, coll_parm, dmg1_part, damage_type::BASH ); - epicenter2 += veh2.part( parm2 ).mount; - veh2.damage( parm2, dmg2_part, damage_type::BASH ); + epicenter2 += veh2.part( target_parm ).mount; + veh2.damage( *this, target_parm, dmg2_part, damage_type::BASH ); } epicenter2.x /= coll_parts_cnt; @@ -1260,6 +1263,8 @@ bool map::displace_vehicle( vehicle &veh, const tripoint &dp, const bool adjust_ veh.shed_loose_parts(); smzs = veh.advance_precalc_mounts( dst_offset, src, dp, ramp_offset, adjust_pos, parts_to_move ); + veh.update_active_fakes(); + if( src_submap != dst_submap ) { veh.set_submap_moved( tripoint( dst.x / SEEX, dst.y / SEEY, dst.z ) ); auto src_submap_veh_it = src_submap->vehicles.begin() + our_i; @@ -3707,7 +3712,7 @@ void map::bash_vehicle( const tripoint &p, bash_params ¶ms ) { // Smash vehicle if present if( const optional_vpart_position vp = veh_at( p ) ) { - vp->vehicle().damage( vp->part_index(), params.strength, damage_type::BASH ); + vp->vehicle().damage( *this, vp->part_index(), params.strength, damage_type::BASH ); if( !params.silent ) { sounds::sound( p, 18, sounds::sound_t::combat, _( "crash!" ), false, "smash_success", "hit_vehicle" ); @@ -3813,7 +3818,7 @@ void map::crush( const tripoint &p ) if( const optional_vpart_position vp = veh_at( p ) ) { // Arbitrary number is better than collapsing house roof crushing APCs - vp->vehicle().damage( vp->part_index(), rng( 100, 1000 ), damage_type::BASH, false ); + vp->vehicle().damage( *this, vp->part_index(), rng( 100, 1000 ), damage_type::BASH, false ); } } @@ -3842,7 +3847,7 @@ void map::shoot( const tripoint &p, projectile &proj, const bool hit_items ) const bool laser = ammo_effects.count( "LASER" ); if( const optional_vpart_position vp = veh_at( p ) ) { - dam = vp->vehicle().damage( vp->part_index(), dam, main_damage_type, hit_items ); + dam = vp->vehicle().damage( *this, vp->part_index(), dam, main_damage_type, hit_items ); } const auto shoot_furn_ter = [&]( const map_data_common_t &data ) { @@ -5754,7 +5759,7 @@ void map::add_splatter( const field_type_id &type, const tripoint &where, int in if( const optional_vpart_position vp = veh_at( where ) ) { vehicle *const veh = &vp->vehicle(); // Might be -1 if all the vehicle's parts at where are marked for removal - const int part = veh->part_displayed_at( vp->mount() ); + const int part = veh->part_displayed_at( vp->mount(), true ); if( part != -1 ) { veh->part( part ).blood += 200 * std::min( intensity, 3 ) / 3; return; @@ -8348,7 +8353,7 @@ void map::do_vehicle_caching( int z ) return; } for( vehicle *v : ch->vehicle_list ) { - for( const vpart_reference &vp : v->get_all_parts() ) { + for( const vpart_reference &vp : v->get_all_parts_with_fakes() ) { const tripoint part_pos = v->global_part_pos3( vp.part() ); if( !inbounds( part_pos.xy() ) ) { continue; @@ -8758,19 +8763,11 @@ void map::scent_blockers( std::array, MAPSIZE_Y> &bl auto vehs = get_vehicles(); for( auto &wrapped_veh : vehs ) { vehicle &veh = *( wrapped_veh.v ); - for( const vpart_reference &vp : veh.get_any_parts( VPFLAG_OBSTACLE ) ) { - const tripoint part_pos = vp.pos(); - if( local_bounds.contains( part_pos.xy() ) ) { - reduces_scent[part_pos.x][part_pos.y] = true; - } - } - - // Doors, but only the closed ones - for( const vpart_reference &vp : veh.get_any_parts( VPFLAG_OPENABLE ) ) { - if( vp.part().open ) { + for( const vpart_reference &vp : veh.get_all_parts_with_fakes() ) { + if( !vp.has_feature( VPFLAG_OBSTACLE ) && + ( !vp.has_feature( VPFLAG_OPENABLE ) || !vp.part().open ) ) { continue; } - const tripoint part_pos = vp.pos(); if( local_bounds.contains( part_pos.xy() ) ) { reduces_scent[part_pos.x][part_pos.y] = true; diff --git a/src/map_field.cpp b/src/map_field.cpp index 83940411ad9ef..adfec8af97057 100644 --- a/src/map_field.cpp +++ b/src/map_field.cpp @@ -1057,7 +1057,7 @@ void field_processor_fd_fire( const tripoint &p, field_entry &cur, field_proc_da // Get the part of the vehicle in the fire (_internal skips the boundary check) vehicle *veh = here.veh_at_internal( p, part ); if( veh != nullptr ) { - veh->damage( part, cur.get_field_intensity() * 10, damage_type::HEAT, true ); + veh->damage( here, part, cur.get_field_intensity() * 10, damage_type::HEAT, true ); // Damage the vehicle in the fire. } if( can_burn ) { diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 2ad513472fc14..7500031d572e2 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6481,7 +6481,6 @@ vehicle *map::add_vehicle( const vproto_id &type, const tripoint &p, const units rebuild_vehicle_level_caches(); placed_vehicle->place_zones( *this ); - } return placed_vehicle; } @@ -6511,6 +6510,9 @@ std::unique_ptr map::add_vehicle_to_map( part != frame_indices.end(); part++ ) { const tripoint p = veh_to_add->global_part_pos3( *part ); + if( veh_to_add->part( *part ).is_fake ) { + continue; + } //Don't spawn anything in water if( has_flag_ter( ter_furn_flag::TFLAG_DEEP_WATER, p ) && !can_float ) { return nullptr; @@ -6546,45 +6548,63 @@ std::unique_ptr map::add_vehicle_to_map( * * the overlap span is still a mess, though. */ - + std::unique_ptr handler_ptr; + bool did_merge = false; for( const tripoint &map_pos : first_veh->get_points( true ) ) { std::vector parts_to_move = veh_to_add->get_parts_at( map_pos, "", part_status_flag::any ); if( !parts_to_move.empty() ) { // Store target_point by value because first_veh->parts may reallocate // to a different address after install_part() - const point target_point = first_veh->get_parts_at( map_pos, "", - part_status_flag:: any ).front()->mount; + std::vector first_veh_parts = first_veh->get_parts_at( map_pos, "", + part_status_flag:: any ); + // This happens if this location is occupied by a fake part. + if( first_veh_parts.empty() || first_veh_parts.front()->is_fake ) { + continue; + } + did_merge = true; + const point target_point = first_veh_parts.front()->mount; const point source_point = parts_to_move.front()->mount; for( const vehicle_part *vp : parts_to_move ) { // TODO: change mount points to be tripoint first_veh->install_part( target_point, *vp ); } + if( !handler_ptr ) { + // This is a heuristic: we just assume the default handler is good enough when called + // on the main game map. And assume that we run from some mapgen code if called on + // another instance. + if( !g || &get_map() != this ) { + handler_ptr = std::make_unique( *this ); + } + } // this could probably be done in a single loop with installing parts above std::vector parts_in_square = veh_to_add->parts_at_relative( source_point, true ); std::set parts_to_check; for( int index = parts_in_square.size() - 1; index >= 0; index-- ) { - veh_to_add->remove_part( parts_in_square[index] ); + if( handler_ptr ) { + veh_to_add->remove_part( parts_in_square[index], *handler_ptr ); + } else { + veh_to_add->remove_part( parts_in_square[index] ); + } parts_to_check.insert( parts_in_square[index] ); } - veh_to_add->find_and_split_vehicles( parts_to_check ); + veh_to_add->find_and_split_vehicles( *this, parts_to_check ); } } - // TODO: more targeted damage around the impact site - first_veh->smash( *this ); - first_veh->enable_refresh(); - - // TODO: entangle the old vehicle and the new vehicle somehow, perhaps with tow cables - // or something like them, to make them harder to separate - std::unique_ptr new_veh = add_vehicle_to_map( std::move( veh_to_add ), true ); - if( new_veh != nullptr ) { - new_veh->smash( *this ); - return new_veh; + if( did_merge ) { + // TODO: more targeted damage around the impact site + first_veh->smash( *this ); + // TODO: entangle the old vehicle and the new vehicle somehow, perhaps with tow cables + // or something like them, to make them harder to separate + std::unique_ptr new_veh = add_vehicle_to_map( std::move( veh_to_add ), true ); + if( new_veh != nullptr ) { + new_veh->smash( *this ); + return new_veh; + } + return nullptr; } - return nullptr; - } else if( impassable( p ) ) { if( !merge_wrecks ) { return nullptr; @@ -6606,6 +6626,7 @@ std::unique_ptr map::add_vehicle_to_map( veh_to_add->smash( *this ); } + veh_to_add->refresh(); return veh_to_add; } diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index ed54d9e6c1920..ecd7f61bd28e6 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -3477,7 +3477,7 @@ void vehicle::serialize( JsonOut &json ) const json.member( "owner", owner ); json.member( "old_owner", old_owner ); json.member( "theft_time", theft_time ); - json.member( "parts", parts ); + json.member( "parts", real_parts() ); json.member( "tags", tags ); json.member( "fuel_remainder", fuel_remainder ); json.member( "fuel_used_last_turn", fuel_used_last_turn ); diff --git a/src/veh_interact.cpp b/src/veh_interact.cpp index 9b38547a64c14..511429a6bad11 100644 --- a/src/veh_interact.cpp +++ b/src/veh_interact.cpp @@ -391,7 +391,7 @@ shared_ptr_fast veh_interact::create_or_get_ui_adaptor() werase( w_parts ); veh->print_part_list( w_parts, 0, getmaxy( w_parts ) - 1, getmaxx( w_parts ), cpart, highlight_part, - true ); + true, false ); wnoutrefresh( w_parts ); werase( w_msg ); @@ -2502,8 +2502,8 @@ void veh_interact::display_veh() std::vector structural_parts = veh->all_parts_at_location( "structure" ); for( auto &structural_part : structural_parts ) { const int p = structural_part; - int sym = veh->part_sym( p ); - nc_color col = veh->part_color( p ); + int sym = veh->part_sym( p, false, false ); + nc_color col = veh->part_color( p, false, false ); const point q = ( veh->part( p ).mount + dd ).rotate( 3 ); @@ -2525,8 +2525,8 @@ void veh_interact::display_veh() if( ovp && &ovp->vehicle() != veh ) { obstruct = true; } - nc_color col = cpart >= 0 ? veh->part_color( cpart ) : c_black; - int sym = cpart >= 0 ? veh->part_sym( cpart ) : ' '; + nc_color col = cpart >= 0 ? veh->part_color( cpart, false, false ) : c_black; + int sym = cpart >= 0 ? veh->part_sym( cpart, false, false ) : ' '; mvwputch( w_disp, point( hw, hh ), obstruct ? red_background( col ) : hilite( col ), special_symbol( sym ) ); wnoutrefresh( w_disp ); diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 949161fb48884..f16c79e66724d 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -128,119 +128,33 @@ static bool is_sm_tile_over_water( const tripoint &real_global_pos ); // 1 kJ per battery charge static const int bat_energy_j = 1000; -// For reference what each function is supposed to do, see their implementation in -// @ref DefaultRemovePartHandler. Add compatible code for it into @ref MapgenRemovePartHandler, -// if needed. -class RemovePartHandler -{ - public: - virtual ~RemovePartHandler() = default; - - virtual void unboard( const tripoint &loc ) = 0; - virtual void add_item_or_charges( const tripoint &loc, item it, bool permit_oob ) = 0; - virtual void set_transparency_cache_dirty( int z ) = 0; - virtual void set_floor_cache_dirty( int z ) = 0; - virtual void removed( vehicle &veh, int part ) = 0; - virtual void spawn_animal_from_part( item &base, const tripoint &loc ) = 0; -}; - -class DefaultRemovePartHandler : public RemovePartHandler +void DefaultRemovePartHandler::removed( vehicle &veh, const int part ) { - public: - ~DefaultRemovePartHandler() override = default; - - void unboard( const tripoint &loc ) override { - get_map().unboard_vehicle( loc ); - } - void add_item_or_charges( const tripoint &loc, item it, bool /*permit_oob*/ ) override { - get_map().add_item_or_charges( loc, std::move( it ) ); - } - void set_transparency_cache_dirty( const int z ) override { - map &here = get_map(); - here.set_transparency_cache_dirty( z ); - here.set_seen_cache_dirty( tripoint_zero ); - } - void set_floor_cache_dirty( const int z ) override { - get_map().set_floor_cache_dirty( z ); - } - void removed( vehicle &veh, const int part ) override { - avatar &player_character = get_avatar(); - // If the player is currently working on the removed part, stop them as it's futile now. - const player_activity &act = player_character.activity; - map &here = get_map(); - if( act.id() == ACT_VEHICLE && act.moves_left > 0 && act.values.size() > 6 ) { - if( veh_pointer_or_null( here.veh_at( tripoint( act.values[0], act.values[1], - player_character.posz() ) ) ) == &veh ) { - if( act.values[6] >= part ) { - player_character.cancel_activity(); - add_msg( m_info, _( "The vehicle part you were working on has gone!" ) ); - } - } - } - // TODO: maybe do this for all the nearby NPCs as well? - - if( player_character.get_grab_type() == object_type::VEHICLE && - player_character.pos() + player_character.grab_point == veh.global_part_pos3( part ) ) { - if( veh.parts_at_relative( veh.part( part ).mount, false ).empty() ) { - add_msg( m_info, _( "The vehicle part you were holding has been destroyed!" ) ); - player_character.grab( object_type::NONE ); - } + avatar &player_character = get_avatar(); + // If the player is currently working on the removed part, stop them as it's futile now. + const player_activity &act = player_character.activity; + map &here = get_map(); + if( act.id() == ACT_VEHICLE && act.moves_left > 0 && act.values.size() > 6 ) { + if( veh_pointer_or_null( here.veh_at( tripoint( act.values[0], act.values[1], + player_character.posz() ) ) ) == &veh ) { + if( act.values[6] >= part ) { + player_character.cancel_activity(); + add_msg( m_info, _( "The vehicle part you were working on has gone!" ) ); } - - here.dirty_vehicle_list.insert( &veh ); } - void spawn_animal_from_part( item &base, const tripoint &loc ) override { - base.release_monster( loc, 1 ); + } + // TODO: maybe do this for all the nearby NPCs as well? + if( player_character.get_grab_type() == object_type::VEHICLE && + player_character.pos() + player_character.grab_point == veh.global_part_pos3( part ) ) { + if( veh.parts_at_relative( veh.part( part ).mount, false ).empty() ) { + add_msg( m_info, _( "The vehicle part you were holding has been destroyed!" ) ); + player_character.grab( object_type::NONE ); } -}; - -class MapgenRemovePartHandler : public RemovePartHandler -{ - private: - map &m; + } - public: - explicit MapgenRemovePartHandler( map &m ) : m( m ) { } + here.dirty_vehicle_list.insert( &veh ); +} - ~MapgenRemovePartHandler() override = default; - - void unboard( const tripoint &/*loc*/ ) override { - debugmsg( "Tried to unboard during mapgen!" ); - // Ignored. Will almost certainly not be called anyway, because - // there are no creatures that could have been mounted during mapgen. - } - void add_item_or_charges( const tripoint &loc, item it, bool permit_oob ) override { - if( !m.inbounds( loc ) ) { - if( !permit_oob ) { - debugmsg( "Tried to put item %s on invalid tile %s during mapgen!", - it.tname(), loc.to_string() ); - } - tripoint copy = loc; - m.clip_to_bounds( copy ); - cata_assert( m.inbounds( copy ) ); // prevent infinite recursion - add_item_or_charges( copy, std::move( it ), false ); - return; - } - m.add_item_or_charges( loc, std::move( it ) ); - } - void set_transparency_cache_dirty( const int /*z*/ ) override { - // Ignored for now. We don't initialize the transparency cache in mapgen anyway. - } - void set_floor_cache_dirty( const int /*z*/ ) override { - // Ignored for now. We don't initialize the floor cache in mapgen anyway. - } - void removed( vehicle &veh, const int /*part*/ ) override { - // TODO: check if this is necessary, it probably isn't during mapgen - m.dirty_vehicle_list.insert( &veh ); - } - void spawn_animal_from_part( item &/*base*/, const tripoint &/*loc*/ ) override { - debugmsg( "Tried to spawn animal from vehicle part during mapgen!" ); - // Ignored. The base item will not be changed and will spawn as is: - // still containing the animal. - // This should not happend during mapgen anyway. - // TODO: *if* this actually happens: create a spawn point for the animal instead. - } -}; // Vehicle stack methods. vehicle_stack::iterator vehicle_stack::erase( vehicle_stack::const_iterator it ) @@ -1263,7 +1177,7 @@ bool vehicle::can_mount( const point &dp, const vpart_id &id ) const return false; } - const std::vector parts_in_square = parts_at_relative( dp, false ); + const std::vector parts_in_square = parts_at_relative( dp, false, false ); //First part in an empty square MUST be a structural part or be an appliance if( parts_in_square.empty() && part.location != part_location_structure && @@ -1591,9 +1505,11 @@ int vehicle::install_part( const point &dp, const vehicle_part &new_part ) } } } - + // refresh will add them back if needed + remove_fake_parts( false ); parts.push_back( new_part ); vehicle_part &pt = parts.back(); + int new_part_index = parts.size() - 1; pt.enabled = enable; @@ -1601,7 +1517,7 @@ int vehicle::install_part( const point &dp, const vehicle_part &new_part ) refresh(); coeff_air_changed = true; - return parts.size() - 1; + return new_part_index; } bool vehicle::try_to_rack_nearby_vehicle( std::vector> &list_of_racks, @@ -1665,6 +1581,7 @@ bool vehicle::merge_rackable_vehicle( vehicle *carry_veh, const std::vector // the mount point on the old vehicle (carry_veh) that will be destroyed point old_mount; }; + remove_fake_parts(); invalidate_towing( true ); // By structs, we mean all the parts of the carry vehicle that are at the structure location // of the vehicle (i.e. frames) @@ -1932,6 +1849,9 @@ bool vehicle::remove_part( const int p, RemovePartHandler &handler ) zones_dirty = true; } parts[p].removed = true; + if( parts[p].has_fake && parts[p].fake_part_at < static_cast( parts.size() ) ) { + parts[parts[p].fake_part_at].removed = true; + } removed_part_count++; handler.removed( *this, p ); @@ -1955,29 +1875,46 @@ bool vehicle::remove_part( const int p, RemovePartHandler &handler ) handler.add_item_or_charges( dest, i, true ); } } - refresh(); + refresh( false ); coeff_air_changed = true; - return shift_if_needed(); + return shift_if_needed( handler.get_map_ref() ); } -void vehicle::part_removal_cleanup() +bool vehicle::__part_removal_actual() { bool changed = false; map &here = get_map(); - for( std::vector::iterator it = parts.begin(); it != parts.end(); /* noop */ ) { - if( it->removed ) { - vehicle_stack items = get_items( std::distance( parts.begin(), it ) ); - while( !items.empty() ) { - items.erase( items.begin() ); - } - const tripoint pt = global_part_pos3( *it ); - here.clear_vehicle_point_from_cache( this, pt ); + for( std::vector::iterator it = parts.end(); it != parts.begin(); /*noop*/ ) { + --it; + if( it->removed || it->is_fake ) { + // We are first stripping out removed parts and marking + // their corresponding real parts as "not fake" so they are regenerated, + // and then removing any parts that have been marked as removed. + // This is assured by iterating from the end to the beginning as + // fake parts are always at the end of the parts vector. + if( it->is_fake ) { + parts[it->fake_part_to].has_fake = false; + } else { + vehicle_stack items = get_items( std::distance( parts.begin(), it ) ); + while( !items.empty() ) { + items.erase( items.begin() ); + } + } + if( !it->is_fake || it->is_active_fake ) { + const tripoint pt = global_part_pos3( *it ); + here.clear_vehicle_point_from_cache( this, pt ); + } it = parts.erase( it ); changed = true; - } else { - ++it; } } + return changed; +} +void vehicle::part_removal_cleanup() +{ + map &here = get_map(); + remove_fake_parts( false ); + const bool changed = __part_removal_actual(); removed_part_count = 0; if( changed || parts.empty() ) { refresh(); @@ -1988,8 +1925,8 @@ void vehicle::part_removal_cleanup() here.add_vehicle_to_cache( this ); } } - shift_if_needed(); - refresh(); // Rebuild cached indices + shift_if_needed( here ); + refresh( false ); // Rebuild cached indices coeff_air_dirty = coeff_air_changed; coeff_air_changed = false; } @@ -2119,7 +2056,7 @@ bool vehicle::remove_carried_vehicle( const std::vector &carried_parts ) carried_vehicles.push_back( carried_parts ); std::vector> carried_mounts; carried_mounts.push_back( new_mounts ); - const bool success = split_vehicles( carried_vehicles, new_vehicles, carried_mounts ); + const bool success = split_vehicles( here, carried_vehicles, new_vehicles, carried_mounts ); if( success ) { //~ %s is the vehicle being loaded onto the bicycle rack add_msg( _( "You unload the %s from the bike rack." ), new_vehicle->name ); @@ -2144,16 +2081,16 @@ bool vehicle::remove_carried_vehicle( const std::vector &carried_parts ) } // split the current vehicle into up to 3 new vehicles that do not connect to each other -bool vehicle::find_and_split_vehicles( int exclude ) +bool vehicle::find_and_split_vehicles( map &here, int exclude ) { std::vector valid_parts = all_parts_at_location( part_location_structure ); std::set checked_parts; checked_parts.insert( exclude ); - return find_and_split_vehicles( checked_parts ); + return find_and_split_vehicles( here, checked_parts ); } -bool vehicle::find_and_split_vehicles( std::set exclude ) +bool vehicle::find_and_split_vehicles( map &here, std::set exclude ) { std::vector valid_parts = all_parts_at_location( part_location_structure ); std::set checked_parts = exclude; @@ -2224,10 +2161,10 @@ bool vehicle::find_and_split_vehicles( std::set exclude ) } if( !all_vehicles.empty() ) { - bool success = split_vehicles( all_vehicles ); + bool success = split_vehicles( here, all_vehicles ); if( success ) { // update the active cache - shift_parts( point_zero ); + shift_parts( here, point_zero ); return true; } } @@ -2257,13 +2194,13 @@ void vehicle::relocate_passengers( const std::vector &passengers ) // @param new_mounts vector of vector of mount points. must have one vector for every vehicle* // in new_vehicles, and forces the part indices in new_vehs to be mounted on the new vehicle // at those mount points -bool vehicle::split_vehicles( const std::vector> &new_vehs, +bool vehicle::split_vehicles( map &here, + const std::vector> &new_vehs, const std::vector &new_vehicles, const std::vector> &new_mounts ) { bool did_split = false; size_t i = 0; - map &here = get_map(); for( i = 0; i < new_vehs.size(); i ++ ) { std::vector split_parts = new_vehs[ i ]; if( split_parts.empty() ) { @@ -2271,6 +2208,8 @@ bool vehicle::split_vehicles( const std::vector> &new_vehs, } std::vector split_mounts = new_mounts[ i ]; did_split = true; + // Once a vehicle is split, we treat it differently, mostly for fake parts. + add_tag( "wreckage" ); vehicle *new_vehicle = nullptr; if( i < new_vehicles.size() ) { @@ -2312,6 +2251,7 @@ bool vehicle::split_vehicles( const std::vector> &new_vehs, new_vehicle->tracking_on = tracking_on; new_vehicle->camera_on = camera_on; } + new_vehicle->add_tag( "wreckage" ); std::vector passengers; for( size_t new_part = 0; new_part < split_parts.size(); new_part++ ) { @@ -2400,10 +2340,11 @@ bool vehicle::split_vehicles( const std::vector> &new_vehs, new_vehicle->refresh(); } else { // include refresh - new_vehicle->shift_parts( point_zero - mnt_offset ); + new_vehicle->shift_parts( here, point_zero - mnt_offset ); } // update the precalc points + new_vehicle->precalc_mounts( 0, new_vehicle->turn_dir, point() ); new_vehicle->precalc_mounts( 1, new_vehicle->skidding ? new_vehicle->turn_dir : new_vehicle->face.dir(), new_vehicle->pivot_point() ); @@ -2414,14 +2355,14 @@ bool vehicle::split_vehicles( const std::vector> &new_vehs, return did_split; } -bool vehicle::split_vehicles( const std::vector> &new_vehs ) +bool vehicle::split_vehicles( map &here, const std::vector> &new_vehs ) { std::vector null_vehicles; std::vector> null_mounts; std::vector nothing; null_vehicles.assign( new_vehs.size(), nullptr ); null_mounts.assign( new_vehs.size(), nothing ); - return split_vehicles( new_vehs, null_vehicles, null_mounts ); + return split_vehicles( here, new_vehs, null_vehicles, null_mounts ); } item_location vehicle::part_base( int p ) @@ -2431,10 +2372,14 @@ item_location vehicle::part_base( int p ) int vehicle::find_part( const item &it ) const { - auto idx = std::find_if( parts.begin(), parts.end(), [&it]( const vehicle_part & e ) { - return &e.base == ⁢ - } ); - return idx != parts.end() ? std::distance( parts.begin(), idx ) : INT_MIN; + int index = INT_MIN; + for( const vpart_reference &vpr : get_all_parts() ) { + if( &vpr.part().base == &it ) { + index = vpr.part_index(); + break; + } + } + return index; } item_group::ItemList vehicle_part::pieces_for_broken_part() const @@ -2448,26 +2393,39 @@ item_group::ItemList vehicle_part::pieces_for_broken_part() const return item_group::items_from( group, calendar::turn ); } -std::vector vehicle::parts_at_relative( const point &dp, - const bool use_cache ) const +std::vector vehicle::parts_at_relative( const point &dp, const bool use_cache, + bool include_fake ) const { + std::vector res; if( !use_cache ) { - std::vector res; - for( const vpart_reference &vp : get_all_parts() ) { - if( vp.mount() == dp && !vp.part().removed ) { - res.push_back( static_cast( vp.part_index() ) ); + if( include_fake ) { + for( const vpart_reference &vp : get_all_parts_with_fakes() ) { + if( vp.mount() == dp && !vp.part().removed ) { + res.push_back( static_cast( vp.part_index() ) ); + } + } + } else { + for( const vpart_reference &vp : get_all_parts() ) { + if( vp.mount() == dp && !vp.part().removed ) { + res.push_back( static_cast( vp.part_index() ) ); + } } } - return res; } else { const auto &iter = relative_parts.find( dp ); if( iter != relative_parts.end() ) { - return iter->second; - } else { - std::vector res; - return res; + if( include_fake ) { + return iter->second; + } else { + for( const int vp : iter->second ) { + if( !parts.at( vp ).is_fake ) { + res.push_back( vp ); + } + } + } } } + return res; } cata::optional vpart_position::obstacle_at_part() const @@ -2486,7 +2444,7 @@ cata::optional vpart_position::obstacle_at_part() const cata::optional vpart_position::part_displayed() const { - int part_id = vehicle().part_displayed_at( mount() ); + int part_id = vehicle().part_displayed_at( mount(), true ); if( part_id == -1 ) { return cata::nullopt; } @@ -2655,20 +2613,25 @@ int vehicle::avail_part_with_feature( const point &pt, const std::string &flag ) bool vehicle::has_part( const std::string &flag, bool enabled ) const { - return std::any_of( parts.begin(), parts.end(), [&flag, &enabled]( const vehicle_part & e ) { - return !e.removed && ( !enabled || e.enabled ) && !e.is_broken() && e.info().has_flag( flag ); - } ); + for( const vpart_reference &vpr : get_all_parts() ) { + if( !vpr.part().removed && ( !enabled || vpr.part().enabled ) && !vpr.part().is_broken() && + vpr.part().info().has_flag( flag ) ) { + return true; + } + } + return false; } bool vehicle::has_part( const tripoint &pos, const std::string &flag, bool enabled ) const { const tripoint relative_pos = pos - global_pos3(); - for( const vehicle_part &e : parts ) { - if( e.precalc[0] != relative_pos ) { + for( const vpart_reference &vpr : get_all_parts() ) { + if( vpr.part().precalc[0] != relative_pos ) { continue; } - if( !e.removed && ( !enabled || e.enabled ) && !e.is_broken() && e.info().has_flag( flag ) ) { + if( !vpr.part().removed && ( !enabled || vpr.part().enabled ) && !vpr.part().is_broken() && + vpr.part().info().has_flag( flag ) ) { return true; } } @@ -2678,17 +2641,18 @@ bool vehicle::has_part( const tripoint &pos, const std::string &flag, bool enabl std::vector vehicle::get_parts_at( const tripoint &pos, const std::string &flag, const part_status_flag condition ) { + // TODO: provide access to fake parts via argument ? const tripoint relative_pos = pos - global_pos3(); std::vector res; - for( vehicle_part &e : parts ) { - if( e.precalc[ 0 ] != relative_pos ) { + for( const vpart_reference &vpr : get_all_parts() ) { + if( vpr.part().precalc[ 0 ] != relative_pos ) { continue; } - if( !e.removed && - ( flag.empty() || e.info().has_flag( flag ) ) && - ( !( condition & part_status_flag::enabled ) || e.enabled ) && - ( !( condition & part_status_flag::working ) || !e.is_broken() ) ) { - res.push_back( &e ); + if( !vpr.part().removed && + ( flag.empty() || vpr.part().info().has_flag( flag ) ) && + ( !( condition & part_status_flag::enabled ) || vpr.part().enabled ) && + ( !( condition & part_status_flag::working ) || !vpr.part().is_broken() ) ) { + res.push_back( &vpr.part() ); } } return res; @@ -2700,15 +2664,15 @@ std::vector vehicle::get_parts_at( const tripoint &pos, { const tripoint relative_pos = pos - global_pos3(); std::vector res; - for( const vehicle_part &e : parts ) { - if( e.precalc[ 0 ] != relative_pos ) { + for( const vpart_reference &vpr : get_all_parts() ) { + if( vpr.part().precalc[ 0 ] != relative_pos ) { continue; } - if( !e.removed && - ( flag.empty() || e.info().has_flag( flag ) ) && - ( !( condition & part_status_flag::enabled ) || e.enabled ) && - ( !( condition & part_status_flag::working ) || !e.is_broken() ) ) { - res.push_back( &e ); + if( !vpr.part().removed && + ( flag.empty() || vpr.part().info().has_flag( flag ) ) && + ( !( condition & part_status_flag::enabled ) || vpr.part().enabled ) && + ( !( condition & part_status_flag::working ) || !vpr.part().is_broken() ) ) { + res.push_back( &vpr.part() ); } } return res; @@ -2742,7 +2706,7 @@ void vpart_position::set_label( const std::string &text ) const int vehicle::next_part_to_close( int p, bool outside ) const { - std::vector parts_here = parts_at_relative( parts[p].mount, true ); + std::vector parts_here = parts_at_relative( parts[p].mount, true, true ); // We want reverse, since we close the outermost thing first (curtains), and then the innermost thing (door) for( std::vector::reverse_iterator part_it = parts_here.rbegin(); @@ -2761,7 +2725,7 @@ int vehicle::next_part_to_close( int p, bool outside ) const int vehicle::next_part_to_open( int p, bool outside ) const { - std::vector parts_here = parts_at_relative( parts[p].mount, true ); + std::vector parts_here = parts_at_relative( parts[p].mount, true, true ); // We want forwards, since we open the innermost thing first (curtains), and then the innermost thing (door) for( const int &elem : parts_here ) { @@ -2816,7 +2780,8 @@ vehicle_part_with_feature_range vehicle::get_any_parts( part_status_flag::any ); } -vehicle_part_with_feature_range vehicle::get_enabled_parts( std::string feature ) const +vehicle_part_with_feature_range vehicle::get_enabled_parts( + std::string feature ) const { return vehicle_part_with_feature_range( const_cast( *this ), std::move( feature ), @@ -2843,9 +2808,10 @@ vehicle_part_with_feature_range vehicle::get_enabled_parts( std::vector vehicle::all_parts_at_location( const std::string &location ) const { std::vector parts_found; - for( size_t part_index = 0; part_index < parts.size(); ++part_index ) { - if( part_info( part_index ).location == location && !parts[part_index].removed ) { - parts_found.push_back( part_index ); + vehicle_part_range all_parts = get_all_parts(); + for( const vpart_reference &vpr : all_parts ) { + if( vpr.info().location == location && !parts[vpr.part_index()].removed ) { + parts_found.push_back( vpr.part_index() ); } } return parts_found; @@ -2859,9 +2825,9 @@ int vehicle::get_next_shifted_index( int original_index, Character &you ) { int ret_index = original_index; bool found_shifted_index = false; - for( std::vector::reverse_iterator it = parts.rbegin(); it != parts.rend(); ++it ) { - if( you.get_value( "veh_index_type" ) == it->info().name() ) { - ret_index = index_of_part( &*it ); + for( const vpart_reference &vpr : get_all_parts() ) { + if( you.get_value( "veh_index_type" ) == vpr.info().name() ) { + ret_index = vpr.part_index(); found_shifted_index = true; break; } @@ -3029,7 +2995,7 @@ int vehicle::index_of_part( const vehicle_part *const part, const bool check_rem * @param dp The local coordinate. * @return The index of the part that will be displayed. */ -int vehicle::part_displayed_at( const point &dp ) const +int vehicle::part_displayed_at( const point &dp, bool include_fake ) const { // Z-order is implicitly defined in game::load_vehiclepart, but as // numbers directly set on parts rather than constants that can be @@ -3037,7 +3003,7 @@ int vehicle::part_displayed_at( const point &dp ) const // it's clear where the magic number comes from. const int ON_ROOF_Z = 9; - std::vector parts_in_square = parts_at_relative( dp, true ); + std::vector parts_in_square = parts_at_relative( dp, true, include_fake ); if( parts_in_square.empty() ) { return -1; @@ -3059,15 +3025,22 @@ int vehicle::part_displayed_at( const point &dp ) const int hide_z_at_or_above = in_vehicle ? ON_ROOF_Z : INT_MAX; - int top_part = 0; - for( size_t index = 1; index < parts_in_square.size(); index++ ) { - if( ( part_info( parts_in_square[top_part] ).z_order < - part_info( parts_in_square[index] ).z_order ) && - ( part_info( parts_in_square[index] ).z_order < - hide_z_at_or_above ) ) { + int top_part = -1; + int top_z_order = -1; + for( size_t index = 0; index < parts_in_square.size(); index++ ) { + int test_index = parts_in_square[index]; + if( parts.at( test_index ).is_fake && !parts.at( test_index ).is_active_fake ) { + continue; + } + int test_z_order = part_info( test_index ).z_order; + if( ( top_z_order < test_z_order ) && ( test_z_order < hide_z_at_or_above ) ) { top_part = index; + top_z_order = test_z_order; } } + if( top_part < 0 ) { + return top_part; + } return parts_in_square[top_part]; } @@ -3100,7 +3073,8 @@ void vehicle::coord_translate( const units::angle &dir, const point &pivot, cons q.y = tdir.dy() + tdir.ortho_dy( p.y - pivot.y ); } -void vehicle::coord_translate( tileray tdir, const point &pivot, const point &p, tripoint &q ) const +void vehicle::coord_translate( tileray tdir, const point &pivot, const point &p, + tripoint &q ) const { tdir.clear_advance(); tdir.advance( p.x - pivot.x ); @@ -3120,13 +3094,15 @@ tripoint vehicle::mount_to_tripoint( const point &mount, const point &offset ) c return global_pos3() + mnt_translated; } -void vehicle::precalc_mounts( int idir, const units::angle &dir, const point &pivot ) +std::set vehicle::precalc_mounts( int idir, const units::angle &dir, const point &pivot ) { if( idir < 0 || idir > 1 ) { idir = 0; } tileray tdir( dir ); std::unordered_map mount_to_precalc; + std::set smzs; + const int pivot_z = global_pos3().z; for( vehicle_part &p : parts ) { if( p.removed ) { continue; @@ -3135,12 +3111,14 @@ void vehicle::precalc_mounts( int idir, const units::angle &dir, const point &pi if( q == mount_to_precalc.end() ) { coord_translate( tdir, pivot, p.mount, p.precalc[idir] ); mount_to_precalc.insert( { p.mount, p.precalc[idir] } ); + smzs.insert( p.precalc[0].z + pivot_z ); } else { p.precalc[idir] = q->second; } } pivot_anchor[idir] = pivot; pivot_rotation[idir] = dir; + return smzs; } std::vector vehicle::boarded_parts() const @@ -3350,23 +3328,25 @@ itype_id vehicle::engine_fuel_current( int e ) const int vehicle::fuel_capacity( const itype_id &ftype ) const { - return std::accumulate( parts.begin(), parts.end(), 0, [&ftype]( const int &lhs, - const vehicle_part & rhs ) { + vehicle_part_range vpr = get_all_parts(); + return std::accumulate( vpr.begin(), vpr.end(), 0, [&ftype]( const int &lhs, + const vpart_reference & rhs ) { cata::value_ptr a_val = item::find_type( ftype )->ammo; - return lhs + ( ( rhs.is_available() && rhs.ammo_current() == ftype ) ? - rhs.ammo_capacity( !!a_val ? a_val->type : ammotype::NULL_ID() ) : 0 ); + return lhs + ( rhs.part().ammo_current() == ftype ? + rhs.part().ammo_capacity( !!a_val ? a_val->type : ammotype::NULL_ID() ) : + 0 ); } ); } float vehicle::fuel_specific_energy( const itype_id &ftype ) const { float total_energy = 0.0f; // J - float total_mass = 0.0f; // g - for( const vehicle_part &vp : parts ) { - if( vp.is_tank() && vp.ammo_current() == ftype && - vp.base.only_item().made_of( phase_id::LIQUID ) ) { - total_energy += vp.base.only_item().get_item_thermal_energy(); - total_mass += to_gram( vp.base.only_item().weight() ); + float total_mass = 0.0f; // g + for( const vpart_reference &vpr : get_all_parts() ) { + if( vpr.part().is_tank() && vpr.part().ammo_current() == ftype && + vpr.part().base.only_item().made_of( phase_id::LIQUID ) ) { + total_energy += vpr.part().base.only_item().get_item_thermal_energy(); + total_mass += to_gram( vpr.part().base.only_item().weight() ); } } return total_energy / total_mass; @@ -3472,7 +3452,7 @@ int vehicle::consumption_per_hour( const itype_id &ftype, int fuel_rate_w ) cons // otherwise have been division-by-zero debugmsg( "Vehicle unexpectedly has zero acceleration" ); } else { - amount_pct += 600 * vslowdown / accel; + amount_pct += 3600 * vslowdown / accel; } } } @@ -3987,7 +3967,7 @@ double vehicle::coeff_air_drag() const // find the first instance of each item and compare against the ideal configuration. std::vector drag( width ); for( int p : structure_indices ) { - if( parts[ p ].removed ) { + if( parts[ p ].removed || parts[ p ].is_fake ) { continue; } int col = parts[ p ].mount.y - mount_min.y; @@ -5203,7 +5183,7 @@ void vehicle::do_engine_damage( size_t e, int strain ) if( is_engine_on( e ) && !is_perpetual_type( e ) && engine_fuel_left( e ) && rng( 1, 100 ) < strain ) { int dmg = rng( 0, strain * 4 ); - damage_direct( engines[e], dmg ); + damage_direct( get_map(), engines[e], dmg ); if( one_in( 2 ) ) { add_msg( _( "Your engine emits a high pitched whine." ) ); } else { @@ -5683,7 +5663,7 @@ void vehicle::enable_refresh() * Refreshes all caches and refinds all parts. Used after the vehicle has had a part added or removed. * Makes indices of different part types so they're easy to find. Also calculates power drain. */ -void vehicle::refresh() +void vehicle::refresh( const bool remove_fakes ) { if( no_refresh ) { return; @@ -5881,8 +5861,80 @@ void vehicle::refresh() rail_wheel_bounding_box.p2 = point_zero; } + const auto need_fake_part = [&]( const point & real_mount, const std::string & flag ) { + int real = part_with_feature( real_mount, flag, true ); + if( real >= 0 && real < num_parts() ) { + return real; + } + return -1; + }; + const auto add_fake_part = [&]( const point & real_mount, const std::string & flag ) { + // to be eligible for a fake copy, you have to be an obstacle or protrusion + const int real_index = need_fake_part( real_mount, flag ); + if( real_index < 0 ) { + return; + } + // find neighbor info for current mount + vpart_edge_info edge_info = get_edge_info( real_mount ); + // add fake mounts based on the edge info + if( edge_info.is_edge_mount() ) { + // get a copy of the real part and install it as an inactive fake part + vehicle_part &part_real = parts.at( real_index ); + if( part_real.has_fake && + static_cast( part_real.fake_part_at ) < parts.size() ) { + relative_parts[ parts[ part_real.fake_part_at ].mount ].push_back( + part_real.fake_part_at ); + return; + } + vehicle_part part_fake( parts.at( real_index ) ); + part_real.has_fake = true; + part_fake.is_fake = true; + part_fake.fake_part_to = real_index; + part_fake.mount += edge_info.is_left_edge() ? point_north : point_south; + if( part_real.info().has_flag( "PROTRUSION" ) ) { + for( const int vp : relative_parts.at( part_real.mount ) ) { + if( parts.at( vp ).is_fake ) { + part_fake.fake_protrusion_on = vp; + break; + } + } + } + int fake_index = parts.size(); + part_real.fake_part_at = fake_index; + fake_parts.push_back( fake_index ); + relative_parts[ part_fake.mount ].push_back( fake_index ); + edges.emplace( real_mount, edge_info ); + parts.push_back( part_fake ); + } + }; + // re-install fake parts - this could be done in a separate function, but we want to + // guarantee that the fake parts were removed before being added + if( remove_fakes && !has_tag( "wreckage" ) ) { + // add all the obstacles first + for( const std::pair > &rp : relative_parts ) { + add_fake_part( rp.first, "OBSTACLE" ); + } + // then add protrusions that hanging on top of fake obstacles. + + std::vector current_fakes = fake_parts; // copy, not a reference + for( const int fake_index : current_fakes ) { + add_fake_part( parts.at( fake_index ).mount, "PROTRUSION" ); + } + } else { + // Always repopulate fake parts in relative_parts cache since we cleared it. + for( const int fake_index : fake_parts ) { + if( parts[fake_index].removed ) { + continue; + } + point pt = parts[fake_index].mount; + relative_parts[pt].push_back( fake_index ); + } + } + // NB: using the _old_ pivot point, don't recalc here, we only do that when moving! - precalc_mounts( 0, pivot_rotation[0], pivot_anchor[0] ); + std::set smzs = precalc_mounts( 0, pivot_rotation[0], pivot_anchor[0] ); + // update the fakes, and then repopulate the cache + update_active_fakes(); check_environmental_effects = true; insides_dirty = true; zones_dirty = true; @@ -5890,6 +5942,87 @@ void vehicle::refresh() occupied_cache_pos = { -1, -1, -1 }; } +vpart_edge_info vehicle::get_edge_info( const point &mount ) const +{ + point forward = mount + point_east; + point aft = mount + point_west; + point left = mount + point_north; + point right = mount + point_south; + int f_index = -1; + int a_index = -1; + int l_index = -1; + int r_index = -1; + bool left_side = false; + bool right_side = false; + if( relative_parts.find( forward ) != relative_parts.end() && + !parts.at( relative_parts.at( forward ).front() ).is_fake ) { + f_index = relative_parts.at( forward ).front(); + } + if( relative_parts.find( aft ) != relative_parts.end() && + !parts.at( relative_parts.at( aft ).front() ).is_fake ) { + a_index = relative_parts.at( aft ).front(); + } + if( relative_parts.find( left ) != relative_parts.end() && + !parts.at( relative_parts.at( left ).front() ).is_fake ) { + l_index = relative_parts.at( left ).front(); + if( parts.at( relative_parts.at( left ).front() ).info().has_flag( "PROTRUSION" ) ) { + left_side = true; + } + } + if( relative_parts.find( right ) != relative_parts.end() && + !parts.at( relative_parts.at( right ).front() ).is_fake ) { + r_index = relative_parts.at( right ).front(); + if( parts.at( relative_parts.at( right ).front() ).info().has_flag( "PROTRUSION" ) ) { + right_side = true; + } + } + return vpart_edge_info( f_index, a_index, l_index, r_index, left_side, right_side ); +} + +void vehicle::remove_fake_parts( const bool cleanup ) +{ + if( fake_parts.empty() ) { + edges.clear(); + return; + } + for( const int fake_index : fake_parts ) { + if( fake_index >= num_parts() ) { + debugmsg( "tried to remove fake part at %d but only %zu parts!", fake_index, + parts.size() ); + continue; + } + vehicle_part &part_fake = parts.at( fake_index ); + int real_index = part_fake.fake_part_to; + if( real_index >= num_parts() ) { + debugmsg( "tried to remove fake part at %d with real at %d but only %zu parts!", + fake_index, real_index, parts.size() ); + } else { + vehicle_part &part_real = parts.at( real_index ); + part_real.has_fake = false; + part_real.fake_part_at = -1; + } + part_fake.removed = true; + } + edges.clear(); + fake_parts.clear(); + if( cleanup ) { + __part_removal_actual(); + } +} + +bool vehicle::real_or_active_fake_part( const int part_num ) const +{ + if( part_num < num_parts() ) { + return !parts.at( part_num ).is_fake || parts.at( part_num ).is_active_fake; + } + return false; +} + +tripoint vehicle::get_abs_diff( const tripoint &one, const tripoint &two ) const +{ + return ( one - two ).abs(); +} + const point &vehicle::pivot_point() const { if( pivot_dirty ) { @@ -6421,12 +6554,13 @@ void vehicle::unboard_all() } } -int vehicle::damage( int p, int dmg, damage_type type, bool aimed ) +int vehicle::damage( map &here, int p, int dmg, damage_type type, bool aimed ) { if( dmg < 1 ) { return dmg; } + p = get_non_fake_part( p ); std::vector pl = parts_at_relative( parts[p].mount, true ); if( pl.empty() ) { // We ran out of non removed parts at this location already. @@ -6476,7 +6610,7 @@ int vehicle::damage( int p, int dmg, damage_type type, bool aimed ) int armor_part = part_with_feature( p, "ARMOR", true ); if( armor_part < 0 ) { // Not covered by armor -- damage part - damage_dealt = damage_direct( target_part, dmg, type ); + damage_dealt = damage_direct( here, target_part, dmg, type ); } else { // Covered by armor -- hit both armor and part, but reduce damage by armor's reduction int protection = part_info( armor_part ).damage_reduction[ static_cast( type )]; @@ -6489,11 +6623,11 @@ int vehicle::damage( int p, int dmg, damage_type type, bool aimed ) // as removing a part only changes indices after the // removed part. if( armor_part < target_part ) { - damage_direct( target_part, overhead ? dmg : dmg - protection, type ); - damage_dealt = damage_direct( armor_part, dmg, type ); + damage_direct( here, target_part, overhead ? dmg : dmg - protection, type ); + damage_dealt = damage_direct( here, armor_part, dmg, type ); } else { - damage_dealt = damage_direct( armor_part, dmg, type ); - damage_direct( target_part, overhead ? dmg : dmg - protection, type ); + damage_dealt = damage_direct( here, armor_part, dmg, type ); + damage_direct( here, target_part, overhead ? dmg : dmg - protection, type ); } } @@ -6522,7 +6656,7 @@ void vehicle::damage_all( int dmg1, int dmg2, damage_type type, const point &imp net_dmg = std::max( 0, net_dmg - parts[ shock_absorber ].info().bonus ); } } - damage_direct( p, net_dmg, type ); + damage_direct( get_map(), p, net_dmg, type ); } } } @@ -6534,7 +6668,7 @@ void vehicle::damage_all( int dmg1, int dmg2, damage_type type, const point &imp * (0, 0) part is always present. * @param delta How much to shift along each axis */ -void vehicle::shift_parts( const point &delta ) +void vehicle::shift_parts( map &here, const point &delta ) { // Don't invalidate the active item cache's location! active_items.subtract_locations( delta ); @@ -6557,7 +6691,7 @@ void vehicle::shift_parts( const point &delta ) pivot_anchor[0] -= delta; refresh(); //Need to also update the map after this - get_map().rebuild_vehicle_level_caches(); + here.rebuild_vehicle_level_caches(); } /** @@ -6565,7 +6699,7 @@ void vehicle::shift_parts( const point &delta ) * adjust if necessary. * @return bool true if the shift was needed. */ -bool vehicle::shift_if_needed() +bool vehicle::shift_if_needed( map &here ) { std::vector vehicle_origin = parts_at_relative( point_zero, true ); if( !vehicle_origin.empty() && !parts[ vehicle_origin[ 0 ] ].removed ) { @@ -6577,7 +6711,7 @@ bool vehicle::shift_if_needed() if( vp.info().location == "structure" && !vp.has_feature( "PROTRUSION" ) && !vp.part().removed ) { - shift_parts( vp.mount() ); + shift_parts( here, vp.mount() ); refresh(); return true; } @@ -6585,7 +6719,7 @@ bool vehicle::shift_if_needed() // There are only parts with PROTRUSION left, choose one of them. for( const vpart_reference &vp : get_all_parts() ) { if( !vp.part().removed ) { - shift_parts( vp.mount() ); + shift_parts( here, vp.mount() ); refresh(); return true; } @@ -6594,6 +6728,11 @@ bool vehicle::shift_if_needed() } int vehicle::break_off( int p, int dmg ) +{ + return break_off( get_map(), p, dmg ); +} + +int vehicle::break_off( map &here, int p, int dmg ) { /* Already-destroyed part - chance it could be torn off into pieces. * Chance increases with damage, and decreases with part max durability @@ -6601,7 +6740,6 @@ int vehicle::break_off( int p, int dmg ) if( rng( 0, part_info( p ).durability / 10 ) >= dmg ) { return dmg; } - map &here = get_map(); const tripoint pos = global_part_pos3( p ); const auto scatter_parts = [&]( const vehicle_part & pt ) { for( const item &piece : pt.pieces_for_broken_part() ) { @@ -6616,6 +6754,12 @@ int vehicle::break_off( int p, int dmg ) } } }; + std::unique_ptr handler_ptr; + if( g && &get_map() == &here ) { + handler_ptr = std::make_unique(); + } else { + handler_ptr = std::make_unique( here ); + } if( part_info( p ).location == part_location_structure ) { // For structural parts, remove other parts first std::vector parts_in_square = parts_at_relative( parts[p].mount, true ); @@ -6639,20 +6783,20 @@ int vehicle::break_off( int p, int dmg ) here.add_item_or_charges( pos, part_as_item ); } } - remove_part( parts_in_square[index] ); + remove_part( parts_in_square[index], *handler_ptr ); } // After clearing the frame, remove it. add_msg_if_player_sees( pos, m_bad, _( "The %1$s's %2$s is destroyed!" ), name, parts[ p ].name() ); scatter_parts( parts[p] ); - remove_part( p ); - find_and_split_vehicles( p ); + remove_part( p, *handler_ptr ); + find_and_split_vehicles( here, p ); } else { //Just break it off add_msg_if_player_sees( pos, m_bad, _( "The %1$s's %2$s is destroyed!" ), name, parts[ p ].name() ); scatter_parts( parts[p] ); const point position = parts[p].mount; - remove_part( p ); + remove_part( p, *handler_ptr ); // remove parts for which required flags are not present anymore if( !part_info( p ).get_flags().empty() ) { @@ -6673,7 +6817,7 @@ int vehicle::break_off( int p, int dmg ) if( remove ) { item part_as_item = parts[part].properties_to_item(); here.add_item_or_charges( pos, part_as_item ); - remove_part( part ); + remove_part( part, *handler_ptr ); } } } @@ -6711,7 +6855,7 @@ bool vehicle::explode_fuel( int p, damage_type type ) return true; } -int vehicle::damage_direct( int p, int dmg, damage_type type ) +int vehicle::damage_direct( map &here, int p, int dmg, damage_type type ) { // Make sure p is within range and hasn't been removed already if( ( static_cast( p ) >= parts.size() ) || parts[p].removed ) { @@ -6721,10 +6865,9 @@ int vehicle::damage_direct( int p, int dmg, damage_type type ) if( is_autodriving ) { stop_autodriving(); } - map &here = get_map(); here.set_memory_seen_cache_dirty( global_part_pos3( p ) ); if( parts[p].is_broken() ) { - return break_off( p, dmg ); + return break_off( here, p, dmg ); } int tsh = std::min( 20, part_info( p ).durability / 10 ); @@ -6774,7 +6917,12 @@ int vehicle::damage_direct( int p, int dmg, damage_type type ) if( part_flag( p, "TOW_CABLE" ) ) { invalidate_towing( true ); } else { - remove_part( p ); + if( !g || &get_map() != &here ) { + MapgenRemovePartHandler handler( here ); + remove_part( p, handler ); + } else { + remove_part( p ); + } } } @@ -7158,7 +7306,7 @@ void vehicle::calc_mass_center( bool use_precalc ) const units::mass m_total = 0_gram; for( const vpart_reference &vp : get_all_parts() ) { const size_t i = vp.part_index(); - if( vp.part().removed ) { + if( vp.part().removed || vp.part().is_fake ) { continue; } @@ -7248,16 +7396,35 @@ bounding_box vehicle::get_bounding_box( bool use_precalc ) return b; } -vehicle_part_range vehicle::get_all_parts() const +bool vehicle::has_any_parts() const { - return vehicle_part_range( const_cast( *this ) ); + return !parts.empty(); } -int vehicle::part_count() const +int vehicle::num_parts() const { return static_cast( parts.size() ); } +int vehicle::num_true_parts() const +{ + return static_cast( parts.size() - fake_parts.size() ); +} + +int vehicle::num_fake_parts() const +{ + return static_cast( fake_parts.size() ); +} + +int vehicle::num_active_fake_parts() const +{ + int ret = 0; + for( const int fake_index : fake_parts ) { + ret += parts.at( fake_index ).is_active_fake ? 1 : 0; + } + return ret; +} + vehicle_part &vehicle::part( int part_num ) { return parts[part_num]; @@ -7265,12 +7432,21 @@ vehicle_part &vehicle::part( int part_num ) const vehicle_part &vehicle::part( int part_num ) const { - return const_cast( parts[part_num] ); + return parts[part_num]; } -bool vehicle::valid_part( int part_num ) const +int vehicle::get_non_fake_part( const int part_num ) { - return part_num >= 0 && part_num < static_cast( parts.size() ); + if( part_num != -1 && part_num < num_parts() ) { + if( parts.at( part_num ).is_fake ) { + return parts.at( part_num ).fake_part_to; + } else { + return part_num; + } + } + debugmsg( "Returning -1 for get_non_fake_part on part_num %d on %s, which has %d parts.", part_num, + disp_name(), parts.size() ); + return -1; } void vehicle::force_erase_part( int part_num ) @@ -7278,6 +7454,27 @@ void vehicle::force_erase_part( int part_num ) parts.erase( parts.begin() + part_num ); } +vehicle_part_range vehicle::get_all_parts() const +{ + return vehicle_part_range( const_cast( *this ) ); +} + +int vehicle::part_count() const +{ + return static_cast( parts.size() ); +} + +std::vector vehicle::real_parts() const +{ + std::vector ret; + for( const vehicle_part &vp : parts ) { + if( vp.removed || vp.is_fake ) { + continue; + } + ret.emplace_back( vp ); + } + return ret; +} std::set vehicle::advance_precalc_mounts( const point &new_pos, const tripoint &src, const tripoint &dp, int ramp_offset, const bool adjust_pos, std::set parts_to_move ) @@ -7311,7 +7508,9 @@ std::set vehicle::advance_precalc_mounts( const point &new_pos, const tripo int index = -1; for( vehicle_part &prt : parts ) { index += 1; - here.clear_vehicle_point_from_cache( this, src + prt.precalc[0] ); + if( !prt.is_fake || prt.is_active_fake ) { + here.clear_vehicle_point_from_cache( this, src + prt.precalc[0] ); + } // no parts means this is a normal horizontal or vertical move if( parts_to_move.empty() ) { prt.precalc[0] = prt.precalc[1]; @@ -7341,6 +7540,11 @@ std::set vehicle::advance_precalc_mounts( const point &new_pos, const tripo return smzs; } +vehicle_part_with_fakes_range vehicle::get_all_parts_with_fakes( bool with_inactive ) const +{ + return vehicle_part_with_fakes_range( const_cast( *this ), with_inactive ); +} + bool vehicle::refresh_zones() { if( zones_dirty ) { @@ -7413,8 +7617,7 @@ template<> bool vehicle_part_with_feature_range::matches( const size_t part ) const { const vehicle_part &vp = this->vehicle().part( part ); - return vp.info().has_flag( feature_ ) && - !vp.removed && + return !vp.removed && !vp.is_fake && vp.info().has_flag( feature_ ) && ( !( part_status_flag::working & required_ ) || !vp.is_broken() ) && ( !( part_status_flag::available & required_ ) || vp.is_available() ) && ( !( part_status_flag::enabled & required_ ) || vp.enabled ); @@ -7424,9 +7627,18 @@ template<> bool vehicle_part_with_feature_range::matches( const size_t part ) const { const vehicle_part &vp = this->vehicle().part( part ); - return vp.info().has_flag( feature_ ) && - !vp.removed && + return !vp.removed && !vp.is_fake && vp.info().has_flag( feature_ ) && ( !( part_status_flag::working & required_ ) || !vp.is_broken() ) && ( !( part_status_flag::available & required_ ) || vp.is_available() ) && ( !( part_status_flag::enabled & required_ ) || vp.enabled ); } + +bool vehicle_part_range::matches( const size_t part ) const +{ + return !this->vehicle().part( part ).is_fake; +} + +bool vehicle_part_with_fakes_range::matches( const size_t part ) const +{ + return this->with_inactive_fakes_ || this->vehicle().real_or_active_fake_part( part ); +} diff --git a/src/vehicle.h b/src/vehicle.h index 956e44358e45e..f4496307ba549 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -36,6 +36,7 @@ #include "tileray.h" #include "type_id.h" #include "units.h" +#include "vpart_range.h" class Character; class Creature; @@ -139,6 +140,26 @@ struct veh_collision { veh_collision() = default; }; +struct vpart_edge_info { + int forward; + int back; + int left; + int right; + bool left_edge = false; + bool right_edge = false; + vpart_edge_info() : forward( -1 ), back( -1 ), left( -1 ), right( -1 ) {} + vpart_edge_info( int forward, int back, int left, int right, bool left_edge, bool right_edge ) : + forward( forward ), back( back ), left( left ), right( right ), left_edge( left_edge ), + right_edge( right_edge ) {} + + bool is_edge_mount() { + return left_edge || right_edge || left == -1 || right == -1; + } + bool is_left_edge() { + return left_edge || left == -1; + } +}; + class vehicle_stack : public item_stack { private: @@ -501,6 +522,15 @@ struct vehicle_part { * this part. */ item_group::ItemList pieces_for_broken_part() const; + + /* probably should be private, but I'm too lazy to write setters and getters */ + bool is_fake = false; // NOLINT(cata-serialize) + bool is_active_fake = false; // NOLINT(cata-serialize) + int fake_part_to = -1; // NOLINT(cata-serialize) + int fake_protrusion_on = -1; // NOLINT(cata-serialize) + bool has_fake = false; // NOLINT(cata-serialize) + int fake_part_at = -1; // NOLINT(cata-serialize) + }; class turret_data @@ -711,9 +741,10 @@ class vehicle // direct damage to part (armor protection and internals are not counted) // returns damage bypassed - int damage_direct( int p, int dmg, damage_type type = damage_type::PURE ); + int damage_direct( map &here, int p, int dmg, damage_type type = damage_type::PURE ); // Removes the part, breaks it into pieces and possibly removes parts attached to it int break_off( int p, int dmg ); + int break_off( map &here, int p, int dmg ); // Returns if it did actually explode bool explode_fuel( int p, damage_type type ); //damages vehicle controls and security system @@ -730,9 +761,6 @@ class vehicle // convert vhp to watts. static int vhp_to_watts( int power ); - //Refresh all caches and re-locate all parts - void refresh(); - // Do stuff like clean up blood and produce smoke from broken parts. Returns false if nothing needs doing. bool do_environmental_effects(); @@ -799,6 +827,8 @@ class vehicle /** Disable or enable refresh() ; used to speed up performance when creating a vehicle */ void suspend_refresh(); void enable_refresh(); + //Refresh all caches and re-locate all parts + void refresh( bool remove_fakes = true ); /** * Set stat for part constrained by range [0,durability] @@ -831,7 +861,7 @@ class vehicle void deserialize( const JsonObject &data ); // Vehicle parts list - all the parts on a single tile int print_part_list( const catacurses::window &win, int y1, int max_y, int width, int p, - int hl = -1, bool detail = false ) const; + int hl = -1, bool detail = false, bool include_fakes = true ) const; // Vehicle parts descriptions - descriptions for all the parts on a single tile void print_vparts_descs( const catacurses::window &win, int max_y, int width, int p, @@ -955,6 +985,9 @@ class vehicle bool remove_part( int p, RemovePartHandler &handler ); bool remove_part( int p ); void part_removal_cleanup(); + // inner look for part_removal_cleanup. returns true if a part is removed + // also called by remove_fake_parts + bool __part_removal_actual(); // remove the carried flag from a vehicle after it has been removed from a rack void remove_carried_flag(); @@ -964,17 +997,17 @@ class vehicle bool remove_carried_vehicle( const std::vector &carried_parts ); // split the current vehicle into up to four vehicles if they have no connection other // than the structure part at exclude - bool find_and_split_vehicles( int exclude ); - bool find_and_split_vehicles( std::set exclude ); + bool find_and_split_vehicles( map &here, int exclude ); + bool find_and_split_vehicles( map &here, std::set exclude ); // relocate passengers to the same part on a new vehicle void relocate_passengers( const std::vector &passengers ); // remove a bunch of parts, specified by a vector indices, and move them to a new vehicle at // the same global position // optionally specify the new vehicle position and the mount points on the new vehicle - bool split_vehicles( const std::vector> &new_vehs, + bool split_vehicles( map &here, const std::vector> &new_vehs, const std::vector &new_vehicles, const std::vector> &new_mounts ); - bool split_vehicles( const std::vector> &new_veh ); + bool split_vehicles( map &here, const std::vector> &new_vehs ); /** Get handle for base item of part */ item_location part_base( int p ); @@ -994,6 +1027,8 @@ class vehicle // TODO: maybe not include broken ones? Have a separate function for that? // TODO: rename to just `parts()` and rename the data member to `parts_`. vehicle_part_range get_all_parts() const; + // Yields a range containing all parts including fake ones that only map cares about + vehicle_part_with_fakes_range get_all_parts_with_fakes( bool with_inactive = false ) const; /** * Yields a range of parts of this vehicle that each have the given feature * and are available: not broken, removed, or part of a carried vehicle. @@ -1032,7 +1067,8 @@ class vehicle /**@}*/ // returns the list of indices of parts at certain position (not accounting frame direction) - std::vector parts_at_relative( const point &dp, bool use_cache ) const; + std::vector parts_at_relative( const point &dp, bool use_cache, + bool include_fake = false ) const; // returns index of part, inner to given, with certain flag, or -1 int part_with_feature( int p, const std::string &f, bool unbroken ) const; @@ -1125,18 +1161,18 @@ class vehicle // Seek a vehicle part which obstructs tile with given coordinates relative to vehicle position int part_at( const point &dp ) const; - int part_displayed_at( const point &dp ) const; + int part_displayed_at( const point &dp, bool include_fake = false ) const; int roof_at_part( int p ) const; // Given a part, finds its index in the vehicle int index_of_part( const vehicle_part *part, bool check_removed = false ) const; // get symbol for map - char part_sym( int p, bool exact = false ) const; + char part_sym( int p, bool exact = false, bool include_fake = true ) const; std::string part_id_string( int p, char &part_mod ) const; // get color for map - nc_color part_color( int p, bool exact = false ) const; + nc_color part_color( int p, bool exact = false, bool include_fake = true ) const; // Get all printable fuel types std::vector get_printable_fuel_types() const; @@ -1156,8 +1192,10 @@ class vehicle */ void print_speed_gauge( const catacurses::window &win, const point &, int spacing = 0 ); - // Pre-calculate mount points for (idir=0) - current direction or (idir=1) - next turn direction - void precalc_mounts( int idir, const units::angle &dir, const point &pivot ); + // Pre-calculate mount points for (idir=0) - current direction or + // (idir=1) - next turn direction + // return the set of all z-levels that the vehicle is on + std::set precalc_mounts( int idir, const units::angle &dir, const point &pivot ); // get a list of part indices where is a passenger inside std::vector boarded_parts() const; @@ -1591,14 +1629,14 @@ class vehicle // must exceed certain threshold to be subtracted from hp // (a lot light collisions will not destroy parts) // Returns damage bypassed - int damage( int p, int dmg, damage_type type = damage_type::BASH, bool aimed = true ); + int damage( map &here, int p, int dmg, damage_type type = damage_type::BASH, bool aimed = true ); // damage all parts (like shake from strong collision), range from dmg1 to dmg2 void damage_all( int dmg1, int dmg2, damage_type type, const point &impact ); //Shifts the coordinates of all parts and moves the vehicle in the opposite direction. - void shift_parts( const point &delta ); - bool shift_if_needed(); + void shift_parts( map &here, const point &delta ); + bool shift_if_needed( map &here ); void shed_loose_parts(); @@ -1860,14 +1898,27 @@ class vehicle // Cached points occupied by the vehicle mutable std::set occupied_points; // NOLINT(cata-serialize) - std::vector parts; // Parts which occupy different tiles - /** - * checks carried_vehicles param for duplicate entries of bike racks/vehicle parts + // Master list of parts installed in the vehicle. + std::vector parts; // NOLINT(cata-serialize) + // Used in savegame.cpp to only save real parts to json + std::vector real_parts() const; + // Map of edge parts and their adjacency information + std::map edges; // NOLINT(cata-serialize) + // For a given mount point, returns it's adjacency info + vpart_edge_info get_edge_info( const point &mount ) const; + + // Removes fake parts from the parts vector + void remove_fake_parts( bool cleanup = true ); + tripoint get_abs_diff( const tripoint &one, const tripoint &two ) const; + bool should_enable_fake( const tripoint &fake_precalc, const tripoint &parent_precalc, + const tripoint &neighbor_precalc ) const; + /** + * checks carried_vehicles param for duplicate entries of bike racks/vehicle parts * this eliminates edge cases caused by overlapping bike_rack lanes - * @param carried_vehicles is a set of either vehicle_parts or bike_racks that need duplicate entries across the vectors rows removed + * @param carried_vehicles is a set of either vehicle_parts or bike_racks that need duplicate entries accross the vectors rows removed */ - void validate_carried_vehicles( std::vector> - &carried_vehicles ); + void validate_carried_vehicles( std::vector> &carried_vehicles ); + public: // Number of parts contained in this vehicle int part_count() const; @@ -1876,8 +1927,25 @@ class vehicle const vehicle_part &part( int part_num ) const; // Determines whether the given part_num is valid for this vehicle bool valid_part( int part_num ) const; + // Same as vehicle::part() except with const binding + const vehicle_part &cpart( int part_num ) const; // Forcibly removes a part from this vehicle. Only exists to support faction_camp.cpp void force_erase_part( int part_num ); + // get the parent part of a fake part or return part_num otherwise + int get_non_fake_part( int part_num ); + // Updates active state on all fake_mounts based on whether they can fill a gap + // map.cpp calls this in displace_vehicle + void update_active_fakes(); + // Determines if the given part_num is real or active fake part + bool real_or_active_fake_part( int part_num ) const; + // Determines if this vehicle has any parts + bool has_any_parts() const; + // Number of parts in this vehicle + int num_parts() const; + int num_true_parts() const; + int num_fake_parts() const; + int num_active_fake_parts() const; + // Updates the internal precalculated mount offsets after the vehicle has been displaced // used in map::displace_vehicle() std::set advance_precalc_mounts( const point &new_pos, const tripoint &src, @@ -1886,29 +1954,33 @@ class vehicle // make sure the vehicle is supported across z-levels or on the same z-level bool level_vehicle(); - std::vector alternators; // List of alternator indices NOLINT(cata-serialize) - std::vector engines; // List of engine indices NOLINT(cata-serialize) - std::vector reactors; // List of reactor indices NOLINT(cata-serialize) - std::vector solar_panels; // List of solar panel indices NOLINT(cata-serialize) - std::vector wind_turbines; // List of wind turbine indices NOLINT(cata-serialize) - std::vector water_wheels; // List of water wheel indices NOLINT(cata-serialize) - std::vector sails; // List of sail indices NOLINT(cata-serialize) - std::vector funnels; // List of funnel indices NOLINT(cata-serialize) - std::vector emitters; // List of emitter parts NOLINT(cata-serialize) - std::vector loose_parts; // List of UNMOUNT_ON_MOVE parts NOLINT(cata-serialize) - std::vector wheelcache; // List of wheels NOLINT(cata-serialize) - std::vector rotors; // List of rotors NOLINT(cata-serialize) - std::vector rail_wheelcache; // List of rail wheels NOLINT(cata-serialize) - std::vector steering; // List of STEERABLE parts NOLINT(cata-serialize) - // List of parts that will not be on a vehicle very often, or which only one will be present - std::vector speciality; // NOLINT(cata-serialize) - std::vector floating; // List of parts with buoyancy NOLINT(cata-serialize) - std::vector batteries; // List of batteries NOLINT(cata-serialize) - std::vector fuel_containers; // Parts with non-null ammo_type NOLINT(cata-serialize) - std::vector turret_locations; // List of turret parts NOLINT(cata-serialize) - std::vector mufflers; // List of muffler parts NOLINT(cata-serialize) - std::vector planters; // List of planter parts NOLINT(cata-serialize) - std::vector accessories; // List of power consuming parts NOLINT(cata-serialize) + // These are lists of part numbers used for quick lookup of various kinds of vehicle parts. + // They are rebuilt in vehicle::refresh() + std::vector alternators; // NOLINT(cata-serialize) + std::vector engines; // NOLINT(cata-serialize) + std::vector reactors; // NOLINT(cata-serialize) + std::vector solar_panels; // NOLINT(cata-serialize) + std::vector wind_turbines; // NOLINT(cata-serialize) + std::vector water_wheels; // NOLINT(cata-serialize) + std::vector sails; // NOLINT(cata-serialize) + std::vector funnels; // NOLINT(cata-serialize) + std::vector emitters; // NOLINT(cata-serialize) + // Parts that will fall off the next time the vehicle moves. + std::vector loose_parts; // NOLINT(cata-serialize) + std::vector wheelcache; // NOLINT(cata-serialize) + std::vector rotors; // NOLINT(cata-serialize) + std::vector rail_wheelcache; // NOLINT(cata-serialize) + std::vector steering; // NOLINT(cata-serialize) + // Intended to be a misc list, but currently only security systems. + std::vector speciality; // NOLINT(cata-serialize) + std::vector floating; // NOLINT(cata-serialize) + std::vector batteries; // NOLINT(cata-serialize) + std::vector fuel_containers; // NOLINT(cata-serialize) + std::vector turret_locations; // NOLINT(cata-serialize) + std::vector mufflers; // NOLINT(cata-serialize) + std::vector planters; // NOLINT(cata-serialize) + std::vector accessories; // NOLINT(cata-serialize) + std::vector fake_parts; // NOLINT(cata-serialize) // config values std::string name; // vehicle name @@ -2098,4 +2170,100 @@ class vehicle std::vector> get_debug_overlay_data() const; }; +// For reference what each function is supposed to do, see their implementation in +// @ref DefaultRemovePartHandler. Add compatible code for it into @ref MapgenRemovePartHandler, +// if needed. +class RemovePartHandler +{ + public: + virtual ~RemovePartHandler() = default; + + virtual void unboard( const tripoint &loc ) = 0; + virtual void add_item_or_charges( const tripoint &loc, item it, bool permit_oob ) = 0; + virtual void set_transparency_cache_dirty( int z ) = 0; + virtual void set_floor_cache_dirty( int z ) = 0; + virtual void removed( vehicle &veh, int part ) = 0; + virtual void spawn_animal_from_part( item &base, const tripoint &loc ) = 0; + virtual map &get_map_ref() = 0; +}; + +class DefaultRemovePartHandler : public RemovePartHandler +{ + public: + ~DefaultRemovePartHandler() override = default; + + void unboard( const tripoint &loc ) override { + get_map().unboard_vehicle( loc ); + } + void add_item_or_charges( const tripoint &loc, item it, bool /*permit_oob*/ ) override { + get_map().add_item_or_charges( loc, std::move( it ) ); + } + void set_transparency_cache_dirty( const int z ) override { + map &here = get_map(); + here.set_transparency_cache_dirty( z ); + here.set_seen_cache_dirty( tripoint_zero ); + } + void set_floor_cache_dirty( const int z ) override { + get_map().set_floor_cache_dirty( z ); + } + void removed( vehicle &veh, const int part ) override; + void spawn_animal_from_part( item &base, const tripoint &loc ) override { + base.release_monster( loc, 1 ); + } + map &get_map_ref() override { + return get_map(); + } +}; + +class MapgenRemovePartHandler : public RemovePartHandler +{ + private: + map &m; + + public: + explicit MapgenRemovePartHandler( map &m ) : m( m ) { } + + ~MapgenRemovePartHandler() override = default; + + void unboard( const tripoint &/*loc*/ ) override { + debugmsg( "Tried to unboard during mapgen!" ); + // Ignored. Will almost certainly not be called anyway, because + // there are no creatures that could have been mounted during mapgen. + } + void add_item_or_charges( const tripoint &loc, item it, bool permit_oob ) override { + if( !m.inbounds( loc ) ) { + if( !permit_oob ) { + debugmsg( "Tried to put item %s on invalid tile %s during mapgen!", + it.tname(), loc.to_string() ); + } + tripoint copy = loc; + m.clip_to_bounds( copy ); + cata_assert( m.inbounds( copy ) ); // prevent infinite recursion + add_item_or_charges( copy, std::move( it ), false ); + return; + } + m.add_item_or_charges( loc, std::move( it ) ); + } + void set_transparency_cache_dirty( const int /*z*/ ) override { + // Ignored for now. We don't initialize the transparency cache in mapgen anyway. + } + void set_floor_cache_dirty( const int /*z*/ ) override { + // Ignored for now. We don't initialize the floor cache in mapgen anyway. + } + void removed( vehicle &veh, const int /*part*/ ) override { + // TODO: check if this is necessary, it probably isn't during mapgen + m.dirty_vehicle_list.insert( &veh ); + } + void spawn_animal_from_part( item &/*base*/, const tripoint &/*loc*/ ) override { + debugmsg( "Tried to spawn animal from vehicle part during mapgen!" ); + // Ignored. The base item will not be changed and will spawn as is: + // still containing the animal. + // This should not happend during mapgen anyway. + // TODO: *if* this actually happens: create a spawn point for the animal instead. + } + map &get_map_ref() override { + return m; + } +}; + #endif // CATA_SRC_VEHICLE_H diff --git a/src/vehicle_display.cpp b/src/vehicle_display.cpp index d6c8909f0580b..924c67a58fe4a 100644 --- a/src/vehicle_display.cpp +++ b/src/vehicle_display.cpp @@ -34,13 +34,16 @@ std::string vehicle::disp_name() const return string_format( _( "the %s" ), name ); } -char vehicle::part_sym( const int p, const bool exact ) const +char vehicle::part_sym( const int p, const bool exact, const bool include_fake ) const { if( p < 0 || p >= static_cast( parts.size() ) || parts[p].removed ) { return ' '; } - const int displayed_part = exact ? p : part_displayed_at( parts[p].mount ); + int displayed_part = exact ? p : part_displayed_at( parts[p].mount, include_fake ); + if( displayed_part == -1 ) { + displayed_part = p; + } const vehicle_part &vp = parts.at( displayed_part ); const vpart_info &vp_info = part_info( displayed_part ); @@ -74,7 +77,7 @@ std::string vehicle::part_id_string( const int p, char &part_mod ) const return ""; } - int displayed_part = part_displayed_at( parts[p].mount ); + int displayed_part = part_displayed_at( parts[p].mount, true ); if( displayed_part < 0 || displayed_part >= static_cast( parts.size() ) || parts[ displayed_part ].removed ) { return ""; @@ -93,7 +96,7 @@ std::string vehicle::part_id_string( const int p, char &part_mod ) const return vp.id.str() + ( vp.variant.empty() ? "" : "_" + vp.variant ); } -nc_color vehicle::part_color( const int p, const bool exact ) const +nc_color vehicle::part_color( const int p, const bool exact, const bool include_fake ) const { if( p < 0 || p >= static_cast( parts.size() ) ) { return c_black; @@ -111,7 +114,7 @@ nc_color vehicle::part_color( const int p, const bool exact ) const if( parm >= 0 ) { col = part_info( parm ).color; } else { - const int displayed_part = exact ? p : part_displayed_at( parts[p].mount ); + const int displayed_part = exact ? p : part_displayed_at( parts[p].mount, include_fake ); if( displayed_part < 0 || displayed_part >= static_cast( parts.size() ) ) { return c_black; @@ -161,21 +164,24 @@ nc_color vehicle::part_color( const int p, const bool exact ) const * @param detail Whether or not to show detailed contents for fuel components. */ int vehicle::print_part_list( const catacurses::window &win, int y1, const int max_y, int width, - int p, int hl /*= -1*/, bool detail ) const + int p, int hl /*= -1*/, bool detail, bool include_fakes ) const { if( p < 0 || p >= static_cast( parts.size() ) ) { return y1; } - std::vector pl = this->parts_at_relative( parts[p].mount, true ); + std::vector pl = this->parts_at_relative( parts[p].mount, true, include_fakes ); int y = y1; for( size_t i = 0; i < pl.size(); i++ ) { + const vehicle_part &vp = parts[ pl [ i ] ]; + if( vp.is_fake && !vp.is_active_fake ) { + continue; + } if( y >= max_y ) { mvwprintz( win, point( 1, y ), c_yellow, _( "More parts hereā€¦" ) ); ++y; break; } - const vehicle_part &vp = parts[ pl [ i ] ]; std::string partname = vp.name(); diff --git a/src/vehicle_move.cpp b/src/vehicle_move.cpp index 96d935614a4fe..87ab9cc517912 100644 --- a/src/vehicle_move.cpp +++ b/src/vehicle_move.cpp @@ -98,7 +98,8 @@ int vehicle::slowdown( int at_velocity ) const const double skid_factor = 1 + 24 * std::abs( units::sin( face.dir() - move.dir() ) ); f_total_drag += f_rolling_drag * skid_factor; } - double accel_slowdown = f_total_drag / to_kilogram( total_mass() ); + // check mass to make sure it's not 0 which happens for some reason + double accel_slowdown = total_mass().value() > 0 ? f_total_drag / to_kilogram( total_mass() ) : 0; // converting m/s^2 to vmiph/s int slowdown = mps_to_vmiph( accel_slowdown ); if( is_towing() ) { @@ -561,7 +562,7 @@ void vehicle::thrust( int thd, int z ) if( velocity > mon->get_speed() * 12 ) { add_msg( m_bad, _( "Your %s is not fast enough to keep up with the %s" ), mon->get_name(), name ); int dmg = rng( 0, 10 ); - damage_direct( e, dmg ); + damage_direct( get_map(), e, dmg ); } } } @@ -682,10 +683,14 @@ bool vehicle::collision( std::vector &colls, const int sign_before = sgn( velocity_before ); bool empty = true; map &here = get_map(); - for( int p = 0; static_cast( p ) < parts.size(); p++ ) { + for( int p = 0; p < num_parts(); p++ ) { + if( parts.at( p ).removed || ( parts.at( p ).is_fake && !parts.at( p ).is_active_fake ) ) { + continue; + } + const vpart_info &info = part_info( p ); - if( ( info.location != part_location_structure && info.rotor_diameter() == 0 ) || - parts[ p ].removed ) { + if( !parts.at( p ).is_fake && + info.location != part_location_structure && info.rotor_diameter() == 0 ) { continue; } empty = false; @@ -903,8 +908,8 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, // Calculate mass AFTER checking for collision // because it involves iterating over all cargo // Rotors only use rotor mass in calculation. - const float mass = ( part_info( part ).rotor_diameter() > 0 ) ? - to_kilogram( parts[ part ].base.weight() ) : to_kilogram( total_mass() ); + const float mass = ( part_info( ret.part ).rotor_diameter() > 0 ) ? + to_kilogram( parts[ ret.part ].base.weight() ) : to_kilogram( total_mass() ); //Calculate damage resulting from d_E const itype *type = item::find_type( part_info( ret.part ).base_item ); @@ -1185,7 +1190,7 @@ void vehicle::handle_trap( const tripoint &p, int part ) explosion_handler::explosion( p, veh_data.damage, 0.5f, false, veh_data.shrapnel ); } else { // Hit the wheel directly since it ran right over the trap. - damage_direct( pwh, veh_data.damage ); + damage_direct( here, pwh, veh_data.damage ); } bool still_has_trap = true; if( veh_data.remove_trap || veh_data.do_explosion ) { @@ -2177,3 +2182,41 @@ units::angle map::shake_vehicle( vehicle &veh, const int velocity_before, return coll_turn; } + +bool vehicle::should_enable_fake( const tripoint &fake_precalc, const tripoint &parent_precalc, + const tripoint &neighbor_precalc ) const +{ + // if parent's pos is diagonal to neighbor, but fake isn't, fake can fill a gap opened + tripoint abs_parent_neighbor_diff = get_abs_diff( parent_precalc, neighbor_precalc ); + tripoint abs_fake_neighbor_diff = get_abs_diff( fake_precalc, neighbor_precalc ); + return ( abs_parent_neighbor_diff.x == 1 && abs_parent_neighbor_diff.y == 1 ) && + ( ( abs_fake_neighbor_diff.x == 1 && abs_fake_neighbor_diff.y == 0 ) || + ( abs_fake_neighbor_diff.x == 0 && abs_fake_neighbor_diff.y == 1 ) ); +} + +void vehicle::update_active_fakes() +{ + for( const int fake_index : fake_parts ) { + vehicle_part &part_fake = parts.at( fake_index ); + if( part_fake.removed ) { + continue; + } + const vehicle_part &part_real = parts.at( part_fake.fake_part_to ); + const tripoint &fake_precalc = part_fake.precalc[0]; + const tripoint &real_precalc = part_real.precalc[0]; + const vpart_edge_info &real_edge = edges[part_real.mount]; + const bool is_protrusion = part_real.info().has_flag( "PROTRUSION" ); + + if( real_edge.forward != -1 ) { + const tripoint &forward = parts.at( real_edge.forward ).precalc[0]; + part_fake.is_active_fake = should_enable_fake( fake_precalc, real_precalc, forward ); + } + if( real_edge.back != -1 && ( !part_fake.is_active_fake || real_edge.forward == -1 ) ) { + const tripoint &back = parts.at( real_edge.back ).precalc[0]; + part_fake.is_active_fake = should_enable_fake( fake_precalc, real_precalc, back ); + } + if( is_protrusion && part_fake.fake_protrusion_on >= 0 ) { + part_fake.is_active_fake = parts.at( part_fake.fake_protrusion_on ).is_active_fake; + } + } +} diff --git a/src/vehicle_part.cpp b/src/vehicle_part.cpp index d4579272f130b..5b782711ab08d 100644 --- a/src/vehicle_part.cpp +++ b/src/vehicle_part.cpp @@ -608,7 +608,13 @@ bool vehicle_part::is_seat() const const vpart_info &vehicle_part::info() const { if( !info_cache ) { - info_cache = &id.obj(); + // segmentation fault occurs here during severe vehicle crash + // probably this part is removed/destroyed? + if( !id.is_null() && id.is_valid() ) { + info_cache = &id.obj(); + } else { + info_cache = nullptr; + } } return *info_cache; } diff --git a/src/vehicle_use.cpp b/src/vehicle_use.cpp index 7aeb98b1c0f8b..ac558ddf9ef32 100644 --- a/src/vehicle_use.cpp +++ b/src/vehicle_use.cpp @@ -530,6 +530,7 @@ void vehicle::smash_security_system() } } Character &player_character = get_player_character(); + map &here = get_map(); //controls and security must both be valid if( c >= 0 && s >= 0 ) { ///\EFFECT_MECHANICS reduces chance of damaging controls when smashing security system @@ -539,7 +540,7 @@ void vehicle::smash_security_system() int rand = rng( 1, 100 ); if( percent_controls > rand ) { - damage_direct( c, part_info( c ).durability / 4 ); + damage_direct( here, c, part_info( c ).durability / 4 ); if( parts[ c ].removed || parts[ c ].is_broken() ) { player_character.controlling_vehicle = false; @@ -550,7 +551,7 @@ void vehicle::smash_security_system() } } if( percent_alarm > rand ) { - damage_direct( s, part_info( s ).durability / 5 ); + damage_direct( here, s, part_info( s ).durability / 5 ); // chance to disable alarm immediately, or disable on destruction if( percent_alarm / 4 > rand || parts[ s ].is_broken() ) { is_alarm_on = false; @@ -971,7 +972,7 @@ bool vehicle::fold_up() try { std::ostringstream veh_data; JsonOut json( veh_data ); - json.write( parts ); + json.write( real_parts() ); bicycle.set_var( "folding_bicycle_parts", veh_data.str() ); } catch( const JsonError &e ) { debugmsg( "Error storing vehicle: %s", e.c_str() ); @@ -1418,7 +1419,7 @@ void vehicle::transform_terrain() } else { const int speed = std::abs( velocity ); int v_damage = rng( 3, speed ); - damage( vp.part_index(), v_damage, damage_type::BASH, false ); + damage( here, vp.part_index(), v_damage, damage_type::BASH, false ); sounds::sound( start_pos, v_damage, sounds::sound_t::combat, _( "Clanggggg!" ), false, "smash_success", "hit_vehicle" ); } @@ -1487,7 +1488,7 @@ void vehicle::operate_planter() here.set( loc, t_dirt, f_plant_seed ); } else if( !here.has_flag( ter_furn_flag::TFLAG_PLOWABLE, loc ) ) { //If it isn't plowable terrain, then it will most likely be damaged. - damage( planter_id, rng( 1, 10 ), damage_type::BASH, false ); + damage( here, planter_id, rng( 1, 10 ), damage_type::BASH, false ); sounds::sound( loc, rng( 10, 20 ), sounds::sound_t::combat, _( "Clink" ), false, "smash_success", "hit_vehicle" ); } @@ -1625,19 +1626,34 @@ bool vehicle::is_open( int part_index ) const bool vehicle::can_close( int part_index, Character &who ) { creature_tracker &creatures = get_creature_tracker(); - for( auto const &vec : find_lines_of_parts( part_index, "OPENABLE" ) ) { - for( auto const &partID : vec ) { - const Creature *const mon = creatures.creature_at( global_part_pos3( parts[partID] ) ); - if( mon ) { - if( mon->is_avatar() ) { - who.add_msg_if_player( m_info, _( "There's some buffoon in the way!" ) ); - } else if( mon->is_monster() ) { - // TODO: Houseflies, mosquitoes, etc shouldn't count - who.add_msg_if_player( m_info, _( "The %s is in the way!" ), mon->get_name() ); + part_index = get_non_fake_part( part_index ); + std::vector> openable_parts = find_lines_of_parts( part_index, "OPENABLE" ); + if( openable_parts.empty() ) { + std::vector base_element; + base_element.push_back( part_index ); + openable_parts.emplace_back( base_element ); + } + for( const std::vector &vec : openable_parts ) { + for( int partID : vec ) { + // Check the part for collisions, then if there's a fake part present check that too. + while( partID >= 0 ) { + const Creature *const mon = creatures.creature_at( global_part_pos3( parts[partID] ) ); + if( mon ) { + if( mon->is_avatar() ) { + who.add_msg_if_player( m_info, _( "There's some buffoon in the way!" ) ); + } else if( mon->is_monster() ) { + // TODO: Houseflies, mosquitoes, etc shouldn't count + who.add_msg_if_player( m_info, _( "The %s is in the way!" ), mon->get_name() ); + } else { + who.add_msg_if_player( m_info, _( "%s is in the way!" ), mon->disp_name() ); + } + return false; + } + if( parts[partID].has_fake ) { + partID = parts[partID].fake_part_at; } else { - who.add_msg_if_player( m_info, _( "%s is in the way!" ), mon->disp_name() ); + partID = -1; } - return false; } } } @@ -1646,7 +1662,7 @@ bool vehicle::can_close( int part_index, Character &who ) void vehicle::open_all_at( int p ) { - std::vector parts_here = parts_at_relative( parts[p].mount, true ); + std::vector parts_here = parts_at_relative( parts[p].mount, true, true ); for( auto &elem : parts_here ) { if( part_flag( elem, VPFLAG_OPENABLE ) ) { // Note that this will open multi-square and non-multipart parts in the tile. This @@ -1664,8 +1680,17 @@ void vehicle::open_all_at( int p ) */ void vehicle::open_or_close( const int part_index, const bool opening ) { + const auto part_open_or_close = [&]( const int parti, const bool opening ) { + vehicle_part &prt = parts.at( parti ); + prt.open = opening; + if( prt.is_fake ) { + parts.at( prt.fake_part_to ).open = opening; + } else if( prt.has_fake ) { + parts.at( prt.fake_part_at ).open = opening; + } + }; //find_lines_of_parts() doesn't return the part_index we passed, so we set it on it's own - parts[part_index].open = opening; + part_open_or_close( part_index, opening ); insides_dirty = true; map &here = get_map(); here.set_transparency_cache_dirty( sm_pos.z ); @@ -1676,9 +1701,9 @@ void vehicle::open_or_close( const int part_index, const bool opening ) sfx::play_variant_sound( opening ? "vehicle_open" : "vehicle_close", parts[ part_index ].info().get_id().str(), 100 - dist * 3 ); } - for( auto const &vec : find_lines_of_parts( part_index, "OPENABLE" ) ) { - for( auto const &partID : vec ) { - parts[partID].open = opening; + for( const std::vector &vec : find_lines_of_parts( part_index, "OPENABLE" ) ) { + for( const int &partID : vec ) { + part_open_or_close( partID, opening ); } } diff --git a/src/vpart_range.h b/src/vpart_range.h index 5ad6d807cea29..7873e244b15fe 100644 --- a/src/vpart_range.h +++ b/src/vpart_range.h @@ -101,14 +101,21 @@ class generic_vehicle_part_range { private: std::reference_wrapper<::vehicle> vehicle_; + bool with_fake_; public: - explicit generic_vehicle_part_range( ::vehicle &v ) : vehicle_( v ) { } + explicit generic_vehicle_part_range( ::vehicle &v, bool with_fake = false ) : vehicle_( v ), + with_fake_( with_fake ) { } // Templated because see top of file. template size_t part_count() const { - return static_cast( vehicle_.get() ).part_count(); + if( with_fake_ ) { + return static_cast( vehicle_.get() ).num_parts(); + } else { + return static_cast( vehicle_.get() ).num_true_parts(); + } + } using iterator = vehicle_part_iterator; @@ -144,9 +151,20 @@ class vehicle_part_range : public generic_vehicle_part_range public: explicit vehicle_part_range( ::vehicle &v ) : generic_vehicle_part_range( v ) { } - bool matches( const size_t /*part*/ ) const { - return true; - } + bool matches( size_t part ) const; +}; + +class vehicle_part_with_fakes_range : public + generic_vehicle_part_range +{ + private: + bool with_inactive_fakes_; + public: + vehicle_part_with_fakes_range( ::vehicle &v, bool with_inactive ) : + generic_vehicle_part_range( v, true ), + with_inactive_fakes_( with_inactive ) { } + + bool matches( size_t part ) const; }; /** A range that contains parts that have a given feature and (optionally) are not broken. */ diff --git a/tests/map_helpers.cpp b/tests/map_helpers.cpp index dbeb4a0f37e85..a3c70deb3d784 100644 --- a/tests/map_helpers.cpp +++ b/tests/map_helpers.cpp @@ -27,9 +27,9 @@ #include "type_id.h" // Remove all vehicles from the map -void clear_vehicles() +void clear_vehicles( map *target ) { - map &here = get_map(); + map &here = target ? *target : get_map(); for( wrapped_vehicle &veh : here.get_vehicles() ) { here.destroy_vehicle( veh.v ); } @@ -48,9 +48,9 @@ void clear_radiation() } } -void wipe_map_terrain() +void wipe_map_terrain( map *target ) { - map &here = get_map(); + map &here = target ? *target : get_map(); const int mapsize = here.getmapsize() * SEEX; for( int z = -1; z <= OVERMAP_HEIGHT; ++z ) { ter_id terrain = z == 0 ? t_grass : z < 0 ? t_rock : t_open_air; @@ -61,7 +61,7 @@ void wipe_map_terrain() } } } - clear_vehicles(); + clear_vehicles( target ); here.invalidate_map_cache( 0 ); here.build_map_cache( 0, true ); } @@ -119,6 +119,7 @@ void clear_zones() void clear_map() { + map &here = get_map(); // Clearing all z-levels is rather slow, so just clear the ones I know the // tests use for now. for( int z = -2; z <= 0; ++z ) { @@ -128,7 +129,7 @@ void clear_map() wipe_map_terrain(); clear_npcs(); clear_creatures(); - get_map().clear_traps(); + here.clear_traps(); for( int z = -2; z <= 0; ++z ) { clear_items( z ); } @@ -175,12 +176,23 @@ void player_add_headlamp() you.worn.wear_item( you, headlamp, false, true ); } +void set_time_to_day() +{ + time_point noon = calendar::turn - time_past_midnight( calendar::turn ) + 12_hours; + if( noon < calendar::turn ) { + noon = noon + 1_days; + } + set_time( noon ); +} + // Set current time of day, and refresh map and caches for the new light level void set_time( const time_point &time ) { calendar::turn = time; g->reset_light_level(); - int z = get_player_character().posz(); + Character &you = get_player_character(); + int z = you.posz(); + you.recalc_sight_limits(); map &here = get_map(); here.update_visibility_cache( z ); here.invalidate_map_cache( z ); diff --git a/tests/map_helpers.h b/tests/map_helpers.h index d51d57a670553..e15432d4a423b 100644 --- a/tests/map_helpers.h +++ b/tests/map_helpers.h @@ -7,10 +7,11 @@ #include "calendar.h" #include "type_id.h" +class map; class monster; struct tripoint; -void wipe_map_terrain(); +void wipe_map_terrain( map *target = nullptr ); void clear_creatures(); void clear_npcs(); void clear_fields( int zlevel ); @@ -20,9 +21,10 @@ void clear_map(); void clear_radiation(); void clear_map_and_put_player_underground(); monster &spawn_test_monster( const std::string &monster_type, const tripoint &start ); -void clear_vehicles(); +void clear_vehicles( map *target = nullptr ); void build_test_map( const ter_id &terrain ); void player_add_headlamp(); +void set_time_to_day(); void set_time( const time_point &time ); #endif // CATA_TESTS_MAP_HELPERS_H diff --git a/tests/npc_test.cpp b/tests/npc_test.cpp index 8182a3bb1efa1..b283fb2350a51 100644 --- a/tests/npc_test.cpp +++ b/tests/npc_test.cpp @@ -437,14 +437,7 @@ TEST_CASE( "npc-movement" ) TEST_CASE( "npc_can_target_player" ) { - time_point noon = calendar::turn - time_past_midnight( calendar::turn ) + 12_hours; - if( noon < calendar::turn ) { - noon = noon + 1_days; - } - REQUIRE( time_past_midnight( noon ) == 12_hours ); - REQUIRE( noon >= calendar::turn ); - // Set to daytime for visibiliity - calendar::turn = noon; + set_time_to_day(); g->faction_manager_ptr->create_if_needed(); diff --git a/tests/vehicle_fake_part_test.cpp b/tests/vehicle_fake_part_test.cpp new file mode 100644 index 0000000000000..e8a5705d9ad24 --- /dev/null +++ b/tests/vehicle_fake_part_test.cpp @@ -0,0 +1,420 @@ +#include +#include + +#include "action.h" +#include "avatar.h" +#include "catch/catch.hpp" +#include "damage.h" +#include "enums.h" +#include "game.h" +#include "item.h" +#include "map.h" +#include "map_helpers.h" +#include "optional.h" +#include "player_helpers.h" +#include "point.h" +#include "type_id.h" +#include "vehicle.h" +#include "vpart_position.h" +#include "vpart_range.h" +#include "veh_type.h" + +static const vproto_id vehicle_prototype_bicycle( "bicycle" ); +static const vproto_id vehicle_prototype_schoolbus( "schoolbus" ); +static const vproto_id vehicle_prototype_suv( "suv" ); +static const vproto_id vehicle_prototype_test_van( "test_van" ); + + +static void really_clear_map() +{ + clear_map(); + build_test_map( ter_id( "t_pavement" ) ); +} + +static void validate_part_count( const vehicle &veh, const int target_velocity, + const units::angle &face_dir, const int real_parts, + const int fake_parts, const int active_fakes ) +{ + if( target_velocity > 0 && veh.velocity <= 200 ) { + std::cout << veh.disp_name() << " at dir " << to_degrees( face_dir ); + std::cout << " speed " << veh.velocity << std::endl; + } + if( to_degrees( veh.face.dir() ) != Approx( to_degrees( face_dir ) ).epsilon( 0.1f ) ) { + std::cout << veh.disp_name() << " at dir " << to_degrees( face_dir ); + std::cout << " face " << veh.face.dir() << std::endl; + } + if( veh.num_true_parts() != real_parts ) { + std::cout << veh.disp_name() << " at dir " << to_degrees( face_dir ); + std::cout << " real parts " << veh.num_true_parts() << std::endl; + } + if( veh.num_fake_parts() != fake_parts ) { + std::cout << veh.disp_name() << " at dir " << to_degrees( face_dir ); + std::cout << " fake parts " << veh.num_fake_parts() << std::endl; + } + if( veh.num_active_fake_parts() != active_fakes ) { + std::cout << veh.disp_name() << " at dir " << to_degrees( face_dir ); + std::cout << " active fakes " << veh.num_active_fake_parts() << std::endl; + } + + + if( target_velocity > 0 ) { + REQUIRE( veh.velocity > 200 ); + } + REQUIRE( to_degrees( veh.face.dir() ) == Approx( to_degrees( face_dir ) ).epsilon( 0.1f ) ); + CHECK( veh.num_true_parts() == real_parts ); + CHECK( veh.num_fake_parts() == fake_parts ); + CHECK( veh.num_active_fake_parts() == active_fakes ); +} + +TEST_CASE( "ensure_fake_parts_enable_on_place", "[vehicle] [vehicle_fake]" ) +{ + const int original_parts = 120; + const int fake_parts = 18; + std::vector active_fakes_by_angle = { 0, 2, 5, 15, 7, 2 }; + + GIVEN( "A vehicle with a known number of parts" ) { + const tripoint test_origin( 30, 30, 0 ); + + for( int quadrant = 0; quadrant < 4; quadrant += 1 ) { + for( int sub_angle = 0; sub_angle < 6; sub_angle += 1 ) { + const units::angle angle = quadrant * 90_degrees + sub_angle * 15_degrees; + really_clear_map(); + map &here = get_map(); + + vehicle *veh = here.add_vehicle( vehicle_prototype_test_van, test_origin, angle, 100, 0 ); + REQUIRE( veh != nullptr ); + + /* since we want all the doors closed anyway, go ahead and test that opening + * and closing the real part also changes the fake part + */ + // include inactive fakes since the vehicle isn't rotated + bool tested_a_fake = false; + for( const vpart_reference vp : veh->get_all_parts_with_fakes( true ) ) { + tested_a_fake |= vp.part().is_fake; + } + validate_part_count( *veh, 0, angle, original_parts, fake_parts, + active_fakes_by_angle.at( sub_angle ) ); + } + } + } +} + + + +TEST_CASE( "ensure_fake_parts_enable_on_turn", "[vehicle] [vehicle_fake]" ) +{ + const int original_parts = 120; + const int fake_parts = 18; + std::vector active_fakes_by_angle = { 0, 3, 8, 15, 6, 1 }; + + GIVEN( "A vehicle with a known number of parts" ) { + really_clear_map(); + map &here = get_map(); + const tripoint test_origin( 30, 30, 0 ); + vehicle *veh = here.add_vehicle( vehicle_prototype_test_van, test_origin, 0_degrees, 100, 0 ); + REQUIRE( veh != nullptr ); + + /* since we want all the doors closed anyway, go ahead and test that opening + * and closing the real part also changes the fake part + */ + for( const vpart_reference vp : veh->get_avail_parts( "OPENABLE" ) ) { + REQUIRE( !vp.part().is_fake ); + veh->open( vp.part_index() ); + } + // include inactive fakes since the vehicle isn't rotated + bool tested_a_fake = false; + for( const vpart_reference vp : veh->get_all_parts_with_fakes( true ) ) { + if( vp.info().has_flag( "OPENABLE" ) ) { + tested_a_fake |= vp.part().is_fake; + CHECK( veh->is_open( vp.part_index() ) ); + } + } + REQUIRE( tested_a_fake ); + for( const vpart_reference vp : veh->get_avail_parts( "OPENABLE" ) ) { + veh->close( vp.part_index() ); + } + for( const vpart_reference vp : veh->get_all_parts_with_fakes( true ) ) { + if( vp.info().has_flag( "OPENABLE" ) ) { + CHECK( !veh->is_open( vp.part_index() ) ); + } + } + + veh->tags.insert( "IN_CONTROL_OVERRIDE" ); + veh->engine_on = true; + const int target_velocity = 12 * 100; + veh->cruise_velocity = target_velocity; + veh->velocity = veh->cruise_velocity; + veh->cruise_on = true; + + for( int quadrant = 0; quadrant < 4; quadrant += 1 ) { + for( int sub_angle = 0; sub_angle < 6; sub_angle += 1 ) { + const units::angle angle = quadrant * 90_degrees + sub_angle * 15_degrees; + here.vehmove(); + REQUIRE( veh->cruise_on ); + validate_part_count( *veh, target_velocity, angle, original_parts, fake_parts, + active_fakes_by_angle.at( sub_angle ) ); + veh->turn( 15_degrees ); + veh->velocity = veh->cruise_velocity; + } + } + here.vehmove(); + veh->idle( true ); + validate_part_count( *veh, target_velocity, 0_degrees, original_parts, fake_parts, + active_fakes_by_angle.at( 0 ) ); + } +} + +TEST_CASE( "ensure_vehicle_weight_is_constant", "[vehicle] [vehicle_fake]" ) +{ + really_clear_map(); + const tripoint test_origin( 30, 30, 0 ); + map &here = get_map(); + vehicle *veh = here.add_vehicle( vehicle_prototype_suv, test_origin, 0_degrees, 0, 0 ); + REQUIRE( veh != nullptr ); + + veh->tags.insert( "IN_CONTROL_OVERRIDE" ); + veh->engine_on = true; + const int target_velocity = 10 * 100; + veh->cruise_velocity = target_velocity; + veh->velocity = veh->cruise_velocity; + + GIVEN( "A vehicle with a known weight" ) { + units::mass initial_weight = veh->total_mass(); + WHEN( "The vehicle turns such that it is not perpendicular to a cardinal axis" ) { + veh->turn( 45_degrees ); + here.vehmove(); + THEN( "The vehicle weight is constant" ) { + units::mass turned_weight = veh->total_mass(); + CHECK( initial_weight == turned_weight ); + } + } + } +} + +TEST_CASE( "vehicle_collision_applies_damage_to_fake_parent", "[vehicle] [vehicle_fake]" ) +{ + really_clear_map(); + map &here = get_map(); + GIVEN( "A moving vehicle traveling at a 45 degree angle to the X axis" ) { + const tripoint test_origin( 30, 30, 0 ); + vehicle *veh = here.add_vehicle( vehicle_prototype_suv, test_origin, 0_degrees, 100, 0 ); + REQUIRE( veh != nullptr ); + + veh->tags.insert( "IN_CONTROL_OVERRIDE" ); + veh->engine_on = true; + const int target_velocity = 50 * 100; + veh->cruise_velocity = target_velocity; + veh->velocity = veh->cruise_velocity; + veh->turn( 45_degrees ); + here.vehmove(); + + WHEN( "A bashable object is placed in the vehicle's path such that it will hit a fake part" ) { + // we know the mount point of the front right headlight is 2,2 + // that places it's fake mirror at 2,3 + const point fake_r_hl( 2, 3 ); + tripoint fake_front_right_headlight = veh->mount_to_tripoint( fake_r_hl ); + // we're travelling south east, so placing it SE of the fake headlight mirror + // will impact it on next move + tripoint obstacle_point = fake_front_right_headlight + tripoint_south_east; + here.furn_set( obstacle_point.xy(), furn_id( "f_boulder_large" ) ); + + int part_count = veh->parts_at_relative( point( 2, 2 ), true, false ).size(); + THEN( "The collision damage is applied to the fake's parent" ) { + here.vehmove(); + std::vector damaged_parts; + std::vector damaged_fake_parts; + // hitting the boulder should have slowed the vehicle down + REQUIRE( veh->velocity < target_velocity ); + + std::vector parent_parts = veh->parts_at_relative( point( 2, 2 ), true, false ); + for( int rel : parent_parts ) { + vehicle_part &vp = veh->part( rel ); + if( vp.info().durability > vp.hp() ) { + damaged_parts.push_back( rel ); + } + } + for( int rel : veh->parts_at_relative( point( 2, 3 ), true, false ) ) { + vehicle_part &vp = veh->part( rel ); + if( vp.info().durability > vp.hp() ) { + damaged_fake_parts.push_back( rel ); + } + } + // If a part was smashed, we pass, + if( parent_parts.size() == static_cast( part_count ) ) { + CHECK( !damaged_parts.empty() ); + } + CHECK( damaged_fake_parts.empty() ); + } + } + } +} + +TEST_CASE( "vehicle_to_vehicle_collision", "[vehicle] [vehicle_fake]" ) +{ + really_clear_map(); + map &here = get_map(); + GIVEN( "A moving vehicle traveling at a 30 degree angle to the X axis" ) { + const tripoint test_origin( 30, 30, 0 ); + vehicle *veh = here.add_vehicle( vehicle_prototype_test_van, test_origin, 30_degrees, 100, 0 ); + REQUIRE( veh != nullptr ); + const tripoint global_origin = veh->global_pos3(); + + veh->tags.insert( "IN_CONTROL_OVERRIDE" ); + veh->engine_on = true; + const int target_velocity = 50 * 100; + veh->cruise_velocity = target_velocity; + veh->velocity = veh->cruise_velocity; + here.vehmove(); + const tripoint global_move = veh->global_pos3(); + const tripoint obstacle_point = test_origin + 2 * ( global_move - global_origin ); + vehicle *trg = here.add_vehicle( vehicle_prototype_schoolbus, obstacle_point, 90_degrees, 100, 0 ); + REQUIRE( trg != nullptr ); + trg->name = "crash bus"; + WHEN( "A vehicle is placed in the vehicle's path such that it will hit a true part" ) { + // we're travelling south east, so place another vehicle in the way. + + THEN( "The collision damage is applied to the true part" ) { + std::vector damaged_parts; + std::vector trg_damaged_parts; + + int moves = 0; + while( veh->velocity == target_velocity && moves < 3 ) { + here.vehmove(); + moves += 1; + } + + // hitting the bus should have slowed the vehicle down + REQUIRE( veh->velocity < target_velocity ); + + for( const vpart_reference &vp : veh->get_all_parts() ) { + if( vp.info().durability > vp.part().hp() ) { + damaged_parts.push_back( vp.part_index() ); + } + } + + for( const vpart_reference &vp : trg->get_all_parts() ) { + if( vp.info().durability > vp.part().hp() ) { + trg_damaged_parts.push_back( vp.part_index() ); + } + } + + CHECK( !damaged_parts.empty() ); + CHECK( !trg_damaged_parts.empty() ); + } + } + } +} + +TEST_CASE( "ensure_vehicle_with_no_obstacles_has_no_fake_parts", "[vehicle] [vehicle_fake]" ) +{ + really_clear_map(); + map &here = get_map(); + GIVEN( "A vehicle with no parts that block movement" ) { + const tripoint test_origin( 30, 30, 0 ); + vehicle *veh = here.add_vehicle( vehicle_prototype_bicycle, test_origin, 45_degrees, 100, 0 ); + REQUIRE( veh != nullptr ); + WHEN( "The vehicle is placed in the world" ) { + THEN( "There are no fake parts added" ) { + validate_part_count( *veh, 0, 45_degrees, veh->num_parts(), 0, 0 ); + } + } + } +} + +TEST_CASE( "fake_parts_are_opaque", "[vehicle],[vehicle_fake]" ) +{ + really_clear_map(); + Character &you = get_player_character(); + clear_avatar(); + const tripoint test_origin = you.pos() + point( 6, 2 ); + map &here = get_map(); + set_time_to_day(); + + REQUIRE( you.sees( you.pos() + point( 10, 10 ) ) ); + vehicle *veh = here.add_vehicle( vehicle_prototype_test_van, test_origin, 315_degrees, 100, 0 ); + REQUIRE( veh != nullptr ); + here.set_seen_cache_dirty( 0 ); + here.build_map_cache( 0 ); + CHECK( !you.sees( you.pos() + point( 10, 10 ) ) ); +} + +TEST_CASE( "open_and_close_fake_doors", "[vehicle],[vehicle_fake]" ) +{ + really_clear_map(); + Character &you = get_player_character(); + clear_avatar(); + const tripoint test_origin = you.pos() + point( 3, 0 ); + map &here = get_map(); + + vehicle *veh = here.add_vehicle( vehicle_prototype_test_van, test_origin, 315_degrees, 100, 0 ); + REQUIRE( veh != nullptr ); + + // First get the doors to a known good state. + for( const vpart_reference vp : veh->get_avail_parts( "OPENABLE" ) ) { + REQUIRE( !vp.part().is_fake ); + veh->close( vp.part_index() ); + } + + // Then scan through all the openables including fakes and assert that we can open them. + int fakes_tested = 0; + for( const vpart_reference vp : veh->get_all_parts_with_fakes() ) { + if( vp.info().has_flag( "OPENABLE" ) && vp.part().is_fake ) { + fakes_tested++; + REQUIRE( !veh->is_open( vp.part_index() ) ); + CHECK( can_interact_at( ACTION_OPEN, vp.pos() ) ); + int part_to_open = veh->next_part_to_open( vp.part_index() ); + // This should be the same part for this use case since there are no curtains etc. + REQUIRE( part_to_open == static_cast( vp.part_index() ) ); + // Using open_all_at because it will usually be from outside the vehicle. + veh->open_all_at( part_to_open ); + CHECK( veh->is_open( vp.part_index() ) ); + CHECK( veh->is_open( vp.part().fake_part_to ) ); + } + } + REQUIRE( fakes_tested == 4 ); + + tripoint prev_player_pos = you.pos(); + // Then open them all back up. + for( const vpart_reference vp : veh->get_avail_parts( "OPENABLE" ) ) { + REQUIRE( !vp.part().is_fake ); + veh->open( vp.part_index() ); + REQUIRE( veh->is_open( vp.part_index() ) ); + if( !vp.part().has_fake ) { + continue; + } + vpart_reference fake_door( *veh, vp.part().fake_part_at ); + if( !fake_door.part().is_active_fake ) { + continue; + } + CAPTURE( prev_player_pos ); + CAPTURE( you.pos() ); + REQUIRE( veh->can_close( vp.part_index(), you ) ); + REQUIRE( veh->can_close( fake_door.part_index(), you ) ); + you.setpos( vp.pos() ); + CHECK( !veh->can_close( vp.part_index(), you ) ); + CHECK( !veh->can_close( fake_door.part_index(), you ) ); + // Move to the location of the fake part and repeat the assetion + you.setpos( fake_door.pos() ); + CHECK( !veh->can_close( vp.part_index(), you ) ); + CHECK( !veh->can_close( fake_door.part_index(), you ) ); + you.setpos( prev_player_pos ); + } + + // Then scan through all the openables including fakes and assert that we can close them. + fakes_tested = 0; + for( const vpart_reference vp : veh->get_all_parts_with_fakes() ) { + if( vp.info().has_flag( "OPENABLE" ) && vp.part().is_fake ) { + fakes_tested++; + CHECK( veh->is_open( vp.part_index() ) ); + CHECK( can_interact_at( ACTION_CLOSE, vp.pos() ) ); + int part_to_close = veh->next_part_to_close( vp.part_index() ); + // This should be the same part for this use case since there are no curtains etc. + REQUIRE( part_to_close == static_cast( vp.part_index() ) ); + // Using open_all_at because it will usually be from outside the vehicle. + veh->close( part_to_close ); + CHECK( !veh->is_open( vp.part_index() ) ); + CHECK( !veh->is_open( vp.part().fake_part_to ) ); + } + } + REQUIRE( fakes_tested == 4 ); +} diff --git a/tests/vehicle_split_test.cpp b/tests/vehicle_split_test.cpp index 07a4e8e055fbb..be079868b169c 100644 --- a/tests/vehicle_split_test.cpp +++ b/tests/vehicle_split_test.cpp @@ -4,11 +4,13 @@ #include "cata_catch.h" #include "character.h" #include "map.h" +#include "map_helpers.h" #include "point.h" #include "type_id.h" #include "units.h" #include "vehicle.h" +static const vproto_id vehicle_prototype_car( "car" ); static const vproto_id vehicle_prototype_circle_split_test( "circle_split_test" ); static const vproto_id vehicle_prototype_cross_split_test( "cross_split_test" ); @@ -22,13 +24,10 @@ TEST_CASE( "vehicle_split_section" ) player_character.setpos( test_origin ); tripoint vehicle_origin = tripoint( 10, 10, 0 ); VehicleList vehs = here.get_vehicles(); - vehicle *veh_ptr; - for( auto &vehs_v : vehs ) { - veh_ptr = vehs_v.v; - here.destroy_vehicle( veh_ptr ); - } + clear_vehicles(); REQUIRE( here.get_vehicles().empty() ); - veh_ptr = here.add_vehicle( vehicle_prototype_cross_split_test, vehicle_origin, dir, 0, 0 ); + vehicle *veh_ptr = here.add_vehicle( vehicle_prototype_cross_split_test, + vehicle_origin, dir, 0, 0 ); REQUIRE( veh_ptr != nullptr ); std::set original_points = veh_ptr->get_points( true ); @@ -82,3 +81,52 @@ TEST_CASE( "vehicle_split_section" ) } } } + +TEST_CASE( "conjoined_vehicles", "[vehicle]" ) +{ + map &here = get_map(); + here.add_vehicle( vehicle_prototype_car, { 40, 40 }, 0_degrees ); + here.add_vehicle( vehicle_prototype_car, { 42, 42 }, 0_degrees ); + here.add_vehicle( vehicle_prototype_car, { 44, 44 }, 45_degrees ); + here.add_vehicle( vehicle_prototype_car, { 48, 44 }, 45_degrees ); +} + +TEST_CASE( "crater_crash", "[vehicle]" ) +{ + tinymap here; + tripoint_abs_sm map_location( 60, 60, 0 ); + here.load( map_location, true ); + here.add_vehicle( vehicle_prototype_car, { 14, 11 }, 45_degrees ); + const tripoint end{ 20, 20, 0 }; + for( tripoint cursor = { 14, 4, 0 }; cursor.y < end.y; cursor.y++ ) { + for( cursor.x = 4; cursor.x < end.x; cursor.x++ ) { + here.destroy( cursor, true ); + } + } +} + +TEST_CASE( "split_vehicle_during_mapgen" ) +{ + clear_map(); + map &here = get_map(); + clear_vehicles(); + REQUIRE( here.get_vehicles().empty() ); + + tinymap tm; + // Wherever the main map is, create this tinymap outside its bounds. + tm.load( here.get_abs_sub() + tripoint( 20, 20, 0 ), false ); + wipe_map_terrain( &tm ); + REQUIRE( tm.get_vehicles().empty() ); + tripoint vehicle_origin{ 14, 14, 0 }; + vehicle *veh_ptr = tm.add_vehicle( vehicle_prototype_cross_split_test, + vehicle_origin, 0_degrees, 0, 0 ); + REQUIRE( veh_ptr != nullptr ); + REQUIRE( !tm.get_vehicles().empty() ); + tm.destroy( vehicle_origin ); + CHECK( tm.get_vehicles().size() == 4 ); + for( wrapped_vehicle &veh : here.get_vehicles() ) { + WARN( veh.v->sm_pos ); + } + CHECK( here.get_vehicles().empty() ); + clear_vehicles(); +}