From 6e6f277c01bcd3d25b7fa87cebf4da7ef87ef352 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 11 Jun 2020 22:48:57 +0200 Subject: [PATCH 01/40] vehicles: cleanup in preparation for adding fake gap filling parts Co-authored-by: Michael Co-authored-by: Mark Langsdorf Various minor clean-ups in the vehicles code, mostly using get_all_parts() instead of iterating through parts() by hand, and making sure that test vehicles don't have OBSTACLE parts because that will make some of the test cases more complicated. --- data/json/vehicles/test.json.rej | 96 ++++ .../mapgen/map_extras/patrol_test.json | 56 +++ data/mods/TEST_DATA/vehicle.json | 74 ++- src/grab.cpp | 5 +- src/map.cpp | 40 +- src/mapgen.cpp | 1 + src/savegame_json.cpp | 2 +- src/vehicle.cpp | 431 ++++++++++++++---- src/vehicle.h | 106 ++++- src/vehicle_display.cpp | 9 +- src/vehicle_move.cpp | 52 ++- src/vehicle_use.cpp | 17 +- src/vpart_range.h | 28 +- tests/vehicle_fake_part_test.cpp | 311 +++++++++++++ 14 files changed, 1083 insertions(+), 145 deletions(-) create mode 100644 data/json/vehicles/test.json.rej create mode 100644 data/mods/Dark-Skies-Above/mapgen/map_extras/patrol_test.json create mode 100644 tests/vehicle_fake_part_test.cpp diff --git a/data/json/vehicles/test.json.rej b/data/json/vehicles/test.json.rej new file mode 100644 index 0000000000000..8eefafc8d30a7 --- /dev/null +++ b/data/json/vehicles/test.json.rej @@ -0,0 +1,96 @@ +--- data/json/vehicles/test.json ++++ data/json/vehicles/test.json +@@ -120,7 +120,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" ] } + ] + }, +@@ -144,9 +144,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" ] } + ] +@@ -209,5 +209,71 @@ + { "x": 1, "y": 0, "parts": [ "xlframe_vertical", "wheel_mount_light_steerable", "wheel_small" ] }, + { "x": -1, "y": 0, "parts": [ "xlframe_vertical", "wheel_mount_light", "wheel_small" ] } + ] ++ }, ++ { ++ "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/data/mods/Dark-Skies-Above/mapgen/map_extras/patrol_test.json b/data/mods/Dark-Skies-Above/mapgen/map_extras/patrol_test.json new file mode 100644 index 0000000000000..de21d2af9b418 --- /dev/null +++ b/data/mods/Dark-Skies-Above/mapgen/map_extras/patrol_test.json @@ -0,0 +1,56 @@ +[ + { + "type": "mapgen", + "method": "json", + "update_mapgen_id": "mx_dsa_patrol_test", + "object": { + "place_monster": [ + { + "monster": "mon_dks_glowdrone", + "x": 12, + "y": 12, + "repeat": [ 2, 3 ], + "chance": 100, + "spawn_data": { "patrol": [ { "x": -5, "y": -5 }, { "x": 5, "y": -5 }, { "x": 5, "y": 5 }, { "x": -5, "y": 5 } ] } + } + ] + } + }, + { + "id": "mx_dsa_patrol_test", + "type": "map_extra", + "name": { "str": "Patrol Test" }, + "description": "Test of the patrol logic.", + "generator": { "generator_method": "update_mapgen", "generator_id": "mx_dsa_patrol_test" }, + "sym": "!", + "color": "red", + "autonote": true + }, + { + "type": "mapgen", + "method": "json", + "update_mapgen_id": "mx_dsa_stray_patrol_test", + "object": { + "place_monster": [ + { + "monster": "dks_mon_stray", + "x": [ 6, 18 ], + "y": [ 6, 18 ], + "repeat": [ 2, 3 ], + "chance": 100, + "spawn_data": { "patrol": [ { "x": -5, "y": -5 }, { "x": 29, "y": -5 }, { "x": 29, "y": 29 }, { "x": -5, "y": 29 } ] } + } + ] + } + }, + { + "id": "mx_dsa_stray_patrol_test", + "type": "map_extra", + "name": { "str": "Stray Patrol Test" }, + "description": "Test of the patrol logic.", + "generator": { "generator_method": "update_mapgen", "generator_id": "mx_dsa_stray_patrol_test" }, + "sym": "!", + "color": "red", + "autonote": true + } +] 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/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/map.cpp b/src/map.cpp index b8a6265aba0c8..eafca40b09324 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,19 +575,17 @@ 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 ); + if( veh.part_info( part_num ).rotor_diameter() > 0 ) { + veh.damage( part_num, coll_dmg, damage_type::BASH, true ); } else { impulse += coll_dmg; - veh.damage( coll.part, coll_dmg, damage_type::BASH ); + veh.damage( part_num, coll_dmg, damage_type::BASH ); veh.damage_all( coll_dmg / 2, coll_dmg, damage_type::BASH, collision_point ); } } @@ -877,21 +875,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 = veh.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( 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( target_parm, dmg2_part, damage_type::BASH ); } epicenter2.x /= coll_parts_cnt; @@ -1260,6 +1260,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; @@ -5754,7 +5756,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; diff --git a/src/mapgen.cpp b/src/mapgen.cpp index ff32d2ee4b2f3..427927452782a 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6580,6 +6580,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 be487feb81602..80494b2a751fb 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -3471,7 +3471,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/vehicle.cpp b/src/vehicle.cpp index 8bf4df567c4fc..fe4d17ade7c3e 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -178,7 +178,6 @@ class DefaultRemovePartHandler : public RemovePartHandler } } // 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() ) { @@ -1263,7 +1262,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 +1590,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 +1602,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 +1666,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) @@ -1960,7 +1962,7 @@ bool vehicle::remove_part( const int p, RemovePartHandler &handler ) return shift_if_needed(); } -void vehicle::part_removal_cleanup() +bool vehicle::__part_removal_actual() { bool changed = false; map &here = get_map(); @@ -1978,6 +1980,13 @@ void vehicle::part_removal_cleanup() ++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(); @@ -1989,7 +1998,7 @@ void vehicle::part_removal_cleanup() } } shift_if_needed(); - refresh(); // Rebuild cached indices + refresh( false ); // Rebuild cached indices coeff_air_dirty = coeff_air_changed; coeff_air_changed = false; } @@ -2262,6 +2271,7 @@ bool vehicle::split_vehicles( const std::vector> &new_vehs, const std::vector> &new_mounts ) { bool did_split = false; + remove_fake_parts(); size_t i = 0; map &here = get_map(); for( i = 0; i < new_vehs.size(); i ++ ) { @@ -2431,10 +2441,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 +2462,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 @@ -2655,20 +2682,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 +2710,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 +2733,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; @@ -2816,7 +2849,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 +2877,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 ); + auto 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 +2894,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 +3064,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 +3072,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 +3094,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 +3142,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 +3163,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 +3180,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 +3397,26 @@ 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 ) { - 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 ); + 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.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 ) ) { + float mass = to_gram( vpr.part().base.only_item().weight() ); + total_energy += vpr.part().base.only_item().specific_energy * mass; + total_mass += mass; } } return total_energy / total_mass; @@ -3472,7 +3522,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 +4037,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; @@ -5675,11 +5725,14 @@ 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; } + if( remove_fakes ) { + remove_fake_parts(); + } alternators.clear(); engines.clear(); @@ -5873,8 +5926,75 @@ 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 ); + 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 ); + parts.push_back( part_fake ); + if( relative_parts.find( part_fake.mount ) == relative_parts.end() ) { + std::vector relative; + relative.push_back( fake_index ); + relative_parts[ part_fake.mount ] = relative; + } else { + relative_parts[ part_fake.mount ].push_back( fake_index ); + } + edges.emplace( real_mount, edge_info ); + } + }; + // 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 ) { + // 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" ); + } + } + // 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(); + map &here = get_map(); + here.add_vehicle_to_cache( this ); + for( const int dirty_z : smzs ) { + here.on_vehicle_moved( dirty_z ); + } check_environmental_effects = true; insides_dirty = true; zones_dirty = true; @@ -5882,6 +6002,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 ) { @@ -6419,6 +6620,7 @@ int vehicle::damage( int p, int dmg, damage_type type, bool aimed ) 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. @@ -6502,6 +6704,7 @@ void vehicle::damage_all( int dmg1, int dmg2, damage_type type, const point &imp return; } + std::cout << "veh " << name << " has " << parts.size() << " parts."; for( const vpart_reference &vp : get_all_parts() ) { const size_t p = vp.part_index(); int distance = 1 + square_dist( vp.mount(), impact ); @@ -7150,7 +7353,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; } @@ -7240,16 +7443,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]; @@ -7257,12 +7479,19 @@ 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 < num_parts() ) { + if( parts.at( part_num ).is_fake ) { + return parts.at( part_num ).fake_part_to; + } else { + return part_num; + } + } + return -1; } void vehicle::force_erase_part( int part_num ) @@ -7270,6 +7499,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 ) @@ -7333,6 +7583,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 ) { @@ -7405,8 +7660,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 ); @@ -7416,9 +7670,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..520acb1720b8b 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; + bool is_active_fake = false; + int fake_part_to = -1; + int fake_protrusion_on = -1; + bool has_fake = false; + int fake_part_at = -1; + }; class turret_data @@ -730,9 +760,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 +826,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] @@ -955,6 +984,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(); @@ -994,6 +1026,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 +1066,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,7 +1160,7 @@ 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 @@ -1156,8 +1191,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; @@ -1861,13 +1898,25 @@ class 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 - * 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 - */ - void validate_carried_vehicles( std::vector> - &carried_vehicles ); + // 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; + // 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 accross the vectors rows removed + */ + void validate_carried_vehicles( std::vector> &carried_vehicles ); + public: // Number of parts contained in this vehicle int part_count() const; @@ -1876,8 +1925,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,11 +1952,14 @@ class vehicle // make sure the vehicle is supported across z-levels or on the same z-level bool level_vehicle(); + + std::vector omt_path; // route for overmap-scale auto-driving + 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 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) @@ -1902,13 +1971,14 @@ class vehicle 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 floating; // List of parts that provide buoyancy to boats 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 fuel_containers; // List 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) + std::vector accessories; // List of accessory (power consuming) parts NOLINT(cata-serialize) + std::vector fake_parts; // List of parts that are fakes to fill gaps NOLINT(cata-serialize) // config values std::string name; // vehicle name diff --git a/src/vehicle_display.cpp b/src/vehicle_display.cpp index d6c8909f0580b..63c2fbd9af604 100644 --- a/src/vehicle_display.cpp +++ b/src/vehicle_display.cpp @@ -40,7 +40,10 @@ char vehicle::part_sym( const int p, const bool exact ) const return ' '; } - const int displayed_part = exact ? p : part_displayed_at( parts[p].mount ); + int displayed_part = exact ? p : part_displayed_at( parts[p].mount, true ); + 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 ""; @@ -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, true ); if( displayed_part < 0 || displayed_part >= static_cast( parts.size() ) ) { return c_black; diff --git a/src/vehicle_move.cpp b/src/vehicle_move.cpp index 96d935614a4fe..fb08ca6a4750f 100644 --- a/src/vehicle_move.cpp +++ b/src/vehicle_move.cpp @@ -682,10 +682,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 +907,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 ); @@ -2177,3 +2181,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_use.cpp b/src/vehicle_use.cpp index 7aeb98b1c0f8b..8e0a5b16f71f0 100644 --- a/src/vehicle_use.cpp +++ b/src/vehicle_use.cpp @@ -1664,8 +1664,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 +1685,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/vehicle_fake_part_test.cpp b/tests/vehicle_fake_part_test.cpp new file mode 100644 index 0000000000000..93357e7a465f8 --- /dev/null +++ b/tests/vehicle_fake_part_test.cpp @@ -0,0 +1,311 @@ +#include +#include + +#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 "point.h" +#include "type_id.h" +#include "vehicle.h" +#include "vpart_position.h" +#include "vpart_range.h" +#include "veh_type.h" + +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( vproto_id( "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( vproto_id( "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( vproto_id( "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( vproto_id( "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" ) ); + + 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 ); + + for( int rel : veh->parts_at_relative( point( 2, 2 ), true, false ) ) { + 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 ); + } + } + 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(); + vproto_id test_van( "test_van" ); + vproto_id school_bus( "schoolbus" ); + 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( 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( school_bus, 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( vproto_id( "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 ); + } + } + } +} From 41b1316692a9a4b1cce3e5ecb59e00f093027bb6 Mon Sep 17 00:00:00 2001 From: robob27 Date: Thu, 23 Dec 2021 03:24:24 -0500 Subject: [PATCH 02/40] Fix segfault for part_num -1 --- src/map.cpp | 15 +++++++++------ src/vehicle.cpp | 3 ++- src/vehicle.h | 20 +++++++++++--------- src/vehicle_move.cpp | 3 ++- src/vehicle_part.cpp | 8 +++++++- tests/vehicle_fake_part_test.cpp | 16 ++++++++-------- 6 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index eafca40b09324..7553ea10ca6e9 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -581,12 +581,15 @@ vehicle *map::move_vehicle( vehicle &veh, const tripoint &dp, const tileray &fac const int coll_dmg = coll.imp; // Shock damage, if the target part is a rotor treat as an aimed hit. - if( veh.part_info( part_num ).rotor_diameter() > 0 ) { - veh.damage( part_num, coll_dmg, damage_type::BASH, true ); - } else { - impulse += coll_dmg; - veh.damage( part_num, 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( part_num, coll_dmg, damage_type::BASH, true ); + } else { + impulse += coll_dmg; + veh.damage( part_num, coll_dmg, damage_type::BASH ); + veh.damage_all( coll_dmg / 2, coll_dmg, damage_type::BASH, collision_point ); + } } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index fe4d17ade7c3e..c2c8a18a998e2 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -7484,13 +7484,14 @@ const vehicle_part &vehicle::part( int part_num ) const int vehicle::get_non_fake_part( const int part_num ) { - if( part_num < num_parts() ) { + 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; } } + std::cout << "Returning -1 for get_non_fake_part."; return -1; } diff --git a/src/vehicle.h b/src/vehicle.h index 520acb1720b8b..161be933382f8 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -1194,7 +1194,7 @@ class vehicle // 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 ); + 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; @@ -1910,12 +1910,12 @@ class vehicle 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 accross the vectors rows removed - */ - void validate_carried_vehicles( std::vector> &carried_vehicles ); + /** + * 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 accross the vectors rows removed + */ + void validate_carried_vehicles( std::vector> &carried_vehicles ); public: // Number of parts contained in this vehicle @@ -1971,14 +1971,16 @@ class vehicle 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 that provide buoyancy to boats NOLINT(cata-serialize) + std::vector + floating; // List of parts that provide buoyancy to boats NOLINT(cata-serialize) std::vector batteries; // List of batteries NOLINT(cata-serialize) std::vector fuel_containers; // List 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 accessory (power consuming) parts NOLINT(cata-serialize) - std::vector fake_parts; // List of parts that are fakes to fill gaps NOLINT(cata-serialize) + std::vector + fake_parts; // List of parts that are fakes to fill gaps NOLINT(cata-serialize) // config values std::string name; // vehicle name diff --git a/src/vehicle_move.cpp b/src/vehicle_move.cpp index fb08ca6a4750f..4afdd4908308c 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() ) { 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/tests/vehicle_fake_part_test.cpp b/tests/vehicle_fake_part_test.cpp index 93357e7a465f8..dffe6872017c8 100644 --- a/tests/vehicle_fake_part_test.cpp +++ b/tests/vehicle_fake_part_test.cpp @@ -25,7 +25,7 @@ static void really_clear_map() 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 ) + 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 ); @@ -67,7 +67,7 @@ TEST_CASE( "ensure_fake_parts_enable_on_place", "[vehicle] [vehicle_fake]" ) 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 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(); @@ -82,7 +82,7 @@ TEST_CASE( "ensure_fake_parts_enable_on_place", "[vehicle] [vehicle_fake]" ) // 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; + tested_a_fake |= vp.part().is_fake; } validate_part_count( *veh, 0, angle, original_parts, fake_parts, active_fakes_by_angle.at( sub_angle ) ); @@ -255,10 +255,10 @@ TEST_CASE( "vehicle_to_vehicle_collision", "[vehicle] [vehicle_fake]" ) 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 ); + const tripoint obstacle_point = test_origin + 2 * ( global_move - global_origin ); vehicle *trg = here.add_vehicle( school_bus, obstacle_point, 90_degrees, 100, 0 ); - REQUIRE( trg != nullptr ); - trg->name = "crash bus"; + 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. @@ -275,13 +275,13 @@ TEST_CASE( "vehicle_to_vehicle_collision", "[vehicle] [vehicle_fake]" ) // hitting the bus should have slowed the vehicle down REQUIRE( veh->velocity < target_velocity ); - for( const vpart_reference &vp: veh->get_all_parts() ) { + 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() ) { + for( const vpart_reference &vp : trg->get_all_parts() ) { if( vp.info().durability > vp.part().hp() ) { trg_damaged_parts.push_back( vp.part_index() ); } From 99f3a69672934ab96283c8b035a55b66e64005dc Mon Sep 17 00:00:00 2001 From: robob27 Date: Thu, 23 Dec 2021 07:17:36 -0500 Subject: [PATCH 03/40] Fixup moving on to this grass is slow --- src/vehicle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index c2c8a18a998e2..bd74670f93328 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2513,7 +2513,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; } From 7265d2c6eb4097e6b1e5c7fa0b98c0eeb7fe417a Mon Sep 17 00:00:00 2001 From: robob27 Date: Thu, 23 Dec 2021 08:36:33 -0500 Subject: [PATCH 04/40] Partial folding fix --- src/iuse.cpp | 2 ++ src/vehicle_use.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/iuse.cpp b/src/iuse.cpp index ce38e7bc16e73..7adf2fb59a240 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -5258,6 +5258,8 @@ cata::optional iuse::unfold_generic( Character *p, item *it, bool, const tr const bool can_float = size( veh->get_avail_parts( "FLOATS" ) ) > 2; const auto invalid_pos = [&here]( const tripoint & pp, bool can_float ) { + std::cout << "VEH HERE: " << here.veh_at( pp ).has_value() << "\n"; + std::cout << "IMPASSABLE: " << here.impassable( pp ) << "\n"; return ( here.has_flag_ter( ter_furn_flag::TFLAG_DEEP_WATER, pp ) && !can_float ) || here.veh_at( pp ) || here.impassable( pp ); }; diff --git a/src/vehicle_use.cpp b/src/vehicle_use.cpp index 8e0a5b16f71f0..e9f9750076506 100644 --- a/src/vehicle_use.cpp +++ b/src/vehicle_use.cpp @@ -971,7 +971,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() ); From 135e02c9ca97fdb33103433e39924cc6cf22f0a4 Mon Sep 17 00:00:00 2001 From: robob27 Date: Thu, 23 Dec 2021 09:21:33 -0500 Subject: [PATCH 05/40] Possible folding fix --- src/iuse.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/iuse.cpp b/src/iuse.cpp index 7adf2fb59a240..7172511b0d529 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -5245,6 +5245,11 @@ cata::optional iuse::unfold_generic( Character *p, item *it, bool, const tr return cata::nullopt; } map &here = get_map(); + + if( here.veh_at( p->pos() ) || here.impassable( p->pos() ) ) { + p->add_msg_if_player( m_info, _( "There's no room to unfold the %s." ), it->tname() ); + return cata::nullopt; + } vehicle *veh = here.add_vehicle( vehicle_prototype_none, p->pos(), 0_degrees, 0, 0, false ); if( veh == nullptr ) { p->add_msg_if_player( m_info, _( "There's no room to unfold the %s." ), it->tname() ); @@ -5258,10 +5263,7 @@ cata::optional iuse::unfold_generic( Character *p, item *it, bool, const tr const bool can_float = size( veh->get_avail_parts( "FLOATS" ) ) > 2; const auto invalid_pos = [&here]( const tripoint & pp, bool can_float ) { - std::cout << "VEH HERE: " << here.veh_at( pp ).has_value() << "\n"; - std::cout << "IMPASSABLE: " << here.impassable( pp ) << "\n"; - return ( here.has_flag_ter( ter_furn_flag::TFLAG_DEEP_WATER, pp ) && !can_float ) || - here.veh_at( pp ) || here.impassable( pp ); + return ( here.has_flag_ter( ter_furn_flag::TFLAG_DEEP_WATER, pp ) && !can_float ); }; for( const vpart_reference &vp : veh->get_all_parts() ) { if( vp.info().location != "structure" ) { From 5cf881dad8023e43bccbc1a8cbf0887e71662c1d Mon Sep 17 00:00:00 2001 From: robob27 Date: Thu, 23 Dec 2021 22:35:02 -0500 Subject: [PATCH 06/40] Real folding fix maybe --- src/iuse.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/iuse.cpp b/src/iuse.cpp index 7172511b0d529..673a7619a1566 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -5245,12 +5245,8 @@ cata::optional iuse::unfold_generic( Character *p, item *it, bool, const tr return cata::nullopt; } map &here = get_map(); - - if( here.veh_at( p->pos() ) || here.impassable( p->pos() ) ) { - p->add_msg_if_player( m_info, _( "There's no room to unfold the %s." ), it->tname() ); - return cata::nullopt; - } 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; @@ -5263,7 +5259,8 @@ cata::optional iuse::unfold_generic( Character *p, item *it, bool, const tr const bool can_float = size( veh->get_avail_parts( "FLOATS" ) ) > 2; const auto invalid_pos = [&here]( const tripoint & pp, bool can_float ) { - return ( here.has_flag_ter( ter_furn_flag::TFLAG_DEEP_WATER, pp ) && !can_float ); + return ( here.has_flag_ter( ter_furn_flag::TFLAG_DEEP_WATER, pp ) && !can_float ) || + here.veh_at( pp ) || here.impassable( pp ); }; for( const vpart_reference &vp : veh->get_all_parts() ) { if( vp.info().location != "structure" ) { @@ -5276,7 +5273,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" ); From 3d264e934ccd6cd766733cb5ac28923ccfb25a01 Mon Sep 17 00:00:00 2001 From: robob27 Date: Thu, 23 Dec 2021 23:10:18 -0500 Subject: [PATCH 07/40] Remove unnecessary file --- data/json/vehicles/test.json.rej | 96 -------------------------------- 1 file changed, 96 deletions(-) delete mode 100644 data/json/vehicles/test.json.rej diff --git a/data/json/vehicles/test.json.rej b/data/json/vehicles/test.json.rej deleted file mode 100644 index 8eefafc8d30a7..0000000000000 --- a/data/json/vehicles/test.json.rej +++ /dev/null @@ -1,96 +0,0 @@ ---- data/json/vehicles/test.json -+++ data/json/vehicles/test.json -@@ -120,7 +120,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" ] } - ] - }, -@@ -144,9 +144,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" ] } - ] -@@ -209,5 +209,71 @@ - { "x": 1, "y": 0, "parts": [ "xlframe_vertical", "wheel_mount_light_steerable", "wheel_small" ] }, - { "x": -1, "y": 0, "parts": [ "xlframe_vertical", "wheel_mount_light", "wheel_small" ] } - ] -+ }, -+ { -+ "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" } -+ ] - } - ] From 9ef9885190438b9ffbc726e122f2c979f9ca6690 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sun, 20 Feb 2022 13:45:12 -0800 Subject: [PATCH 08/40] Handle a rare outcome of part smashing test --- tests/vehicle_fake_part_test.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/vehicle_fake_part_test.cpp b/tests/vehicle_fake_part_test.cpp index dffe6872017c8..62926f5110762 100644 --- a/tests/vehicle_fake_part_test.cpp +++ b/tests/vehicle_fake_part_test.cpp @@ -210,6 +210,7 @@ TEST_CASE( "vehicle_collision_applies_damage_to_fake_parent", "[vehicle] [vehicl 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; @@ -217,7 +218,8 @@ TEST_CASE( "vehicle_collision_applies_damage_to_fake_parent", "[vehicle] [vehicl // hitting the boulder should have slowed the vehicle down REQUIRE( veh->velocity < target_velocity ); - for( int rel : veh->parts_at_relative( point( 2, 2 ), true, false ) ) { + 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 ); @@ -229,7 +231,10 @@ TEST_CASE( "vehicle_collision_applies_damage_to_fake_parent", "[vehicle] [vehicl damaged_fake_parts.push_back( rel ); } } - CHECK( !damaged_parts.empty() ); + // If a part was smashed, we pass, + if( parent_parts.size() == part_count ) { + CHECK( !damaged_parts.empty() ); + } CHECK( damaged_fake_parts.empty() ); } } From ba4a40d4ad1935c851dc474980830c62da62e7d7 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Mon, 14 Mar 2022 13:01:12 -0700 Subject: [PATCH 09/40] Supress fake part generation for split vehicles. --- src/vehicle.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index bd74670f93328..0b5289ab7e852 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2281,6 +2281,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() ) { @@ -2322,6 +2324,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++ ) { @@ -5730,7 +5733,8 @@ void vehicle::refresh( const bool remove_fakes ) if( no_refresh ) { return; } - if( remove_fakes ) { + bool wreck = has_tag( "wreckage" ); + if( remove_fakes || wreck ) { remove_fake_parts(); } @@ -5974,7 +5978,7 @@ void vehicle::refresh( const bool remove_fakes ) }; // 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 ) { + if( remove_fakes && !wreck ) { // add all the obstacles first for( const std::pair > &rp : relative_parts ) { add_fake_part( rp.first, "OBSTACLE" ); From 844dd9bde19ef85d1e05a8eb068fe9b7d59f2387 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Fri, 4 Mar 2022 18:23:57 -0800 Subject: [PATCH 10/40] Hoist RemovePartHandlers to vehicle.h --- src/vehicle.cpp | 127 ++++++++---------------------------------------- src/vehicle.h | 89 +++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 106 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 0b5289ab7e852..5bb36d4b5bbd3 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -128,118 +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 ) { } + } - ~MapgenRemovePartHandler() override = default; + here.dirty_vehicle_list.insert( &veh ); +} - 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 ) diff --git a/src/vehicle.h b/src/vehicle.h index 161be933382f8..a70a6e1b27e6c 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -2170,4 +2170,93 @@ 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; +}; + +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 ); + } +}; + +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. + } +}; + #endif // CATA_SRC_VEHICLE_H From b8d4a352a1ef38a23332a3e5291b2b743d158320 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Mon, 14 Mar 2022 13:48:57 -0700 Subject: [PATCH 11/40] Add some more vehicle splitting and skewing tests --- tests/vehicle_split_test.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/vehicle_split_test.cpp b/tests/vehicle_split_test.cpp index 07a4e8e055fbb..4ac4782cb5e43 100644 --- a/tests/vehicle_split_test.cpp +++ b/tests/vehicle_split_test.cpp @@ -9,6 +9,7 @@ #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" ); @@ -82,3 +83,26 @@ 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 = { 4, 4, 0 }; cursor.y < end.y; cursor.y++ ) { + for( cursor.x = 4; cursor.x < end.x; cursor.x++ ) { + here.destroy( cursor, true ); + } + } +} From 2c89784d14944e0f612e02d7bc6dbc8b2eb0137a Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 26 Mar 2022 17:36:57 -0700 Subject: [PATCH 12/40] Fix typo in vehicle-vehicle collision handling --- src/map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/map.cpp b/src/map.cpp index 7553ea10ca6e9..1e4f29962b359 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -879,7 +879,7 @@ float map::vehicle_vehicle_collision( vehicle &veh, vehicle &veh2, continue; } int coll_part = veh.get_non_fake_part( veh_veh_coll.part ); - int target_part = veh.get_non_fake_part( veh_veh_coll.target_part ); + int target_part = veh2.get_non_fake_part( veh_veh_coll.target_part ); int coll_parm = veh.part_with_feature( coll_part, VPFLAG_ARMOR, true ); if( coll_parm < 0 ) { From 18ff522b9f7bba4038f9038cba25355a6bb5bcad Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 26 Mar 2022 17:39:47 -0700 Subject: [PATCH 13/40] Debug message cleanup --- src/mapgen.cpp | 1 - src/vehicle.cpp | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 427927452782a..08fe09b954915 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6455,7 +6455,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; } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 5bb36d4b5bbd3..48df6e0690df8 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -6623,7 +6623,6 @@ void vehicle::damage_all( int dmg1, int dmg2, damage_type type, const point &imp return; } - std::cout << "veh " << name << " has " << parts.size() << " parts."; for( const vpart_reference &vp : get_all_parts() ) { const size_t p = vp.part_index(); int distance = 1 + square_dist( vp.mount(), impact ); @@ -7410,7 +7409,7 @@ int vehicle::get_non_fake_part( const int part_num ) return part_num; } } - std::cout << "Returning -1 for get_non_fake_part."; + debugmsg( "Returning -1 for get_non_fake_part on part_num %d on %s.", part_num, disp_name() ); return -1; } From c3fae57275afbf3ea1566ada6d46bcbec677ca90 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 26 Mar 2022 17:41:18 -0700 Subject: [PATCH 14/40] Add precalc_mounts() invocation for freshly split vehicles this cause some kind of cache desynchronization and massive vehicle lookup errors. --- src/vehicle.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 48df6e0690df8..62257c4cff448 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2332,6 +2332,7 @@ bool vehicle::split_vehicles( const std::vector> &new_vehs, } // 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() ); From ea6cd2e1a15f756d1fb4e55486a1f627c1696acb Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 26 Mar 2022 17:42:44 -0700 Subject: [PATCH 15/40] Mark fake parts as removed when their parents are removed --- src/vehicle.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 62257c4cff448..495a8475810de 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1849,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 ); From 5757d0974ff5eb7fef6df944d8655bb5f731915f Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 26 Mar 2022 19:11:55 -0700 Subject: [PATCH 16/40] Invoke special part handler in add_vehicle_to_map() --- src/mapgen.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 08fe09b954915..cd19ca3ef0ed3 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6519,7 +6519,7 @@ std::unique_ptr map::add_vehicle_to_map( * * the overlap span is still a mess, though. */ - + std::unique_ptr handler_ptr; 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 ); @@ -6534,11 +6534,23 @@ std::unique_ptr map::add_vehicle_to_map( 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 ); From 83dab06cf8aeef36286a6dc739f7c54388789e51 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 26 Mar 2022 21:21:31 -0700 Subject: [PATCH 17/40] Ignore fake parts when handling vehicle placement overlaps --- src/mapgen.cpp | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/mapgen.cpp b/src/mapgen.cpp index cd19ca3ef0ed3..718f78ee9cb08 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6484,6 +6484,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; @@ -6520,14 +6523,21 @@ 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 @@ -6557,19 +6567,18 @@ std::unique_ptr map::add_vehicle_to_map( } } - // 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; From c4338d574232a4f197c94bbb6d1f49fa79e9a139 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 26 Mar 2022 21:22:25 -0700 Subject: [PATCH 18/40] Fixup for wreckage handling --- src/vehicle.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 495a8475810de..c9193df080850 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2189,7 +2189,6 @@ bool vehicle::split_vehicles( const std::vector> &new_vehs, const std::vector> &new_mounts ) { bool did_split = false; - remove_fake_parts(); size_t i = 0; map &here = get_map(); for( i = 0; i < new_vehs.size(); i ++ ) { From 1d7a61cf7a0a76bd9c203f2a8cbd21f77f96fe52 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 26 Mar 2022 21:23:03 -0700 Subject: [PATCH 19/40] Very misc fixups --- src/vehicle.cpp | 4 ++-- tests/vehicle_fake_part_test.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index c9193df080850..b613519de8cc6 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2798,7 +2798,7 @@ 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; - auto all_parts = get_all_parts(); + 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() ); @@ -3321,7 +3321,7 @@ int vehicle::fuel_capacity( const itype_id &ftype ) const 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; + cata::value_ptr a_val = item::find_type( ftype )->ammo; return lhs + ( rhs.part().ammo_current() == ftype ? rhs.part().ammo_capacity( !!a_val ? a_val->type : ammotype::NULL_ID() ) : 0 ); diff --git a/tests/vehicle_fake_part_test.cpp b/tests/vehicle_fake_part_test.cpp index 62926f5110762..4e4c8b064ac91 100644 --- a/tests/vehicle_fake_part_test.cpp +++ b/tests/vehicle_fake_part_test.cpp @@ -232,7 +232,7 @@ TEST_CASE( "vehicle_collision_applies_damage_to_fake_parent", "[vehicle] [vehicl } } // If a part was smashed, we pass, - if( parent_parts.size() == part_count ) { + if( parent_parts.size() == static_cast( part_count ) ) { CHECK( !damaged_parts.empty() ); } CHECK( damaged_fake_parts.empty() ); From cc664a3287958201692cc904e64e26119b5a460c Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sun, 3 Apr 2022 23:25:28 -0700 Subject: [PATCH 20/40] Propogate current map instance reference through vehicle damage code Prevents destruction of vehicles in mapgen from spawning vehicles on the main map --- src/explosion.cpp | 6 ++-- src/game.cpp | 2 +- src/map.cpp | 14 ++++---- src/map_field.cpp | 2 +- src/mapgen.cpp | 2 +- src/vehicle.cpp | 86 +++++++++++++++++++++++++------------------- src/vehicle.h | 24 ++++++++----- src/vehicle_move.cpp | 4 +-- src/vehicle_use.cpp | 9 ++--- 9 files changed, 86 insertions(+), 63 deletions(-) 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 f382e292865ab..9b77f421da243 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5035,7 +5035,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/map.cpp b/src/map.cpp index 1e4f29962b359..1588c790cfc9b 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -584,10 +584,10 @@ vehicle *map::move_vehicle( vehicle &veh, const tripoint &dp, const tileray &fac // 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( part_num, coll_dmg, damage_type::BASH, true ); + veh.damage( *this, part_num, coll_dmg, damage_type::BASH, true ); } else { impulse += coll_dmg; - veh.damage( part_num, coll_dmg, damage_type::BASH ); + veh.damage( *this, part_num, coll_dmg, damage_type::BASH ); veh.damage_all( coll_dmg / 2, coll_dmg, damage_type::BASH, collision_point ); } } @@ -891,10 +891,10 @@ float map::vehicle_vehicle_collision( vehicle &veh, vehicle &veh2, } epicenter1 += veh.part( coll_parm ).mount; - veh.damage( coll_parm, dmg1_part, damage_type::BASH ); + veh.damage( *this, coll_parm, dmg1_part, damage_type::BASH ); epicenter2 += veh2.part( target_parm ).mount; - veh2.damage( target_parm, dmg2_part, damage_type::BASH ); + veh2.damage( *this, target_parm, dmg2_part, damage_type::BASH ); } epicenter2.x /= coll_parts_cnt; @@ -3712,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" ); @@ -3818,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 ); } } @@ -3847,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 ) { 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 718f78ee9cb08..677f224c160d6 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6563,7 +6563,7 @@ std::unique_ptr map::add_vehicle_to_map( } 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 ); } } diff --git a/src/vehicle.cpp b/src/vehicle.cpp index b613519de8cc6..cadb67e418e6b 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1877,7 +1877,7 @@ bool vehicle::remove_part( const int p, RemovePartHandler &handler ) } refresh(); coeff_air_changed = true; - return shift_if_needed(); + return shift_if_needed( handler.get_map_ref() ); } bool vehicle::__part_removal_actual() @@ -1915,7 +1915,7 @@ void vehicle::part_removal_cleanup() here.add_vehicle_to_cache( this ); } } - shift_if_needed(); + shift_if_needed( here ); refresh( false ); // Rebuild cached indices coeff_air_dirty = coeff_air_changed; coeff_air_changed = false; @@ -2046,7 +2046,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 ); @@ -2071,16 +2071,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; @@ -2151,10 +2151,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; } } @@ -2184,13 +2184,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() ) { @@ -2330,7 +2330,7 @@ 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 @@ -2345,14 +2345,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 ) @@ -5166,7 +5166,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 { @@ -6536,7 +6536,7 @@ 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; @@ -6592,7 +6592,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 )]; @@ -6605,11 +6605,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 ); } } @@ -6638,7 +6638,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 ); } } } @@ -6650,7 +6650,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 ); @@ -6673,7 +6673,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(); } /** @@ -6681,7 +6681,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 ) { @@ -6693,7 +6693,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; } @@ -6701,7 +6701,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; } @@ -6710,6 +6710,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 @@ -6717,7 +6722,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() ) { @@ -6732,6 +6736,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 ); @@ -6755,20 +6765,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() ) { @@ -6789,7 +6799,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 ); } } } @@ -6827,7 +6837,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 ) { @@ -6837,10 +6847,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 ); @@ -6890,7 +6899,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 ); + } } } diff --git a/src/vehicle.h b/src/vehicle.h index a70a6e1b27e6c..874c1be85df1e 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -741,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 @@ -996,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 ); @@ -1628,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(); @@ -2184,6 +2185,7 @@ class RemovePartHandler 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 @@ -2209,6 +2211,9 @@ class DefaultRemovePartHandler : public RemovePartHandler 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 @@ -2257,6 +2262,9 @@ class MapgenRemovePartHandler : public RemovePartHandler // 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_move.cpp b/src/vehicle_move.cpp index 4afdd4908308c..87ab9cc517912 100644 --- a/src/vehicle_move.cpp +++ b/src/vehicle_move.cpp @@ -562,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 ); } } } @@ -1190,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 ) { diff --git a/src/vehicle_use.cpp b/src/vehicle_use.cpp index e9f9750076506..08c17c726d629 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; @@ -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" ); } From 4998022591bdfca5fcbbb5cb9858abdc5f5905d3 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sun, 3 Apr 2022 23:30:01 -0700 Subject: [PATCH 21/40] Extend map clearing helpers to work on tinymaps --- tests/map_helpers.cpp | 13 +++++++------ tests/map_helpers.h | 5 +++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/map_helpers.cpp b/tests/map_helpers.cpp index dbeb4a0f37e85..9a3620943706e 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 ); } diff --git a/tests/map_helpers.h b/tests/map_helpers.h index d51d57a670553..928009cb998f0 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,7 +21,7 @@ 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( const time_point &time ); From cb58750a055a53d99e5daac97fff2cddfdaff145 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sun, 3 Apr 2022 23:33:39 -0700 Subject: [PATCH 22/40] Add test to exercise the case when a vehicle is split during mapgen. --- tests/vehicle_split_test.cpp | 38 +++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/vehicle_split_test.cpp b/tests/vehicle_split_test.cpp index 4ac4782cb5e43..be079868b169c 100644 --- a/tests/vehicle_split_test.cpp +++ b/tests/vehicle_split_test.cpp @@ -4,6 +4,7 @@ #include "cata_catch.h" #include "character.h" #include "map.h" +#include "map_helpers.h" #include "point.h" #include "type_id.h" #include "units.h" @@ -23,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 ); @@ -100,9 +98,35 @@ TEST_CASE( "crater_crash", "[vehicle]" ) here.load( map_location, true ); here.add_vehicle( vehicle_prototype_car, { 14, 11 }, 45_degrees ); const tripoint end{ 20, 20, 0 }; - for( tripoint cursor = { 4, 4, 0 }; cursor.y < end.y; cursor.y++ ) { + 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(); +} From 2fae2db0011fbeebe03e275011ae87e1701e10c7 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sun, 3 Apr 2022 23:46:24 -0700 Subject: [PATCH 23/40] Don't remove fake parts during refresh This avoids invalidating part indexes --- src/vehicle.cpp | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index cadb67e418e6b..f6eb9d60043e0 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1875,7 +1875,7 @@ 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( handler.get_map_ref() ); } @@ -1884,18 +1884,26 @@ 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() ); + 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() ); + } } 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; @@ -5651,10 +5659,6 @@ void vehicle::refresh( const bool remove_fakes ) if( no_refresh ) { return; } - bool wreck = has_tag( "wreckage" ); - if( remove_fakes || wreck ) { - remove_fake_parts(); - } alternators.clear(); engines.clear(); @@ -5867,6 +5871,10 @@ void vehicle::refresh( const bool remove_fakes ) 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 == true && + static_cast( part_real.fake_part_at ) < parts.size() ) { + return; + } vehicle_part part_fake( parts.at( real_index ) ); part_real.has_fake = true; part_fake.is_fake = true; @@ -5896,7 +5904,7 @@ void vehicle::refresh( const bool remove_fakes ) }; // 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 && !wreck ) { + if( remove_fakes && !has_tag( "wreckage" ) ) { // add all the obstacles first for( const std::pair > &rp : relative_parts ) { add_fake_part( rp.first, "OBSTACLE" ); From f1beb542df66fcd23e533563429005a58e281859 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sun, 3 Apr 2022 23:54:16 -0700 Subject: [PATCH 24/40] Very misc fixes --- src/vehicle.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index f6eb9d60043e0..1276aca3be0a1 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -3343,9 +3343,8 @@ float vehicle::fuel_specific_energy( const itype_id &ftype ) const 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 ) ) { - float mass = to_gram( vpr.part().base.only_item().weight() ); - total_energy += vpr.part().base.only_item().specific_energy * mass; - total_mass += mass; + 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; @@ -5871,7 +5870,7 @@ void vehicle::refresh( const bool remove_fakes ) 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 == true && + if( part_real.has_fake && static_cast( part_real.fake_part_at ) < parts.size() ) { return; } @@ -5920,11 +5919,6 @@ void vehicle::refresh( const bool remove_fakes ) std::set smzs = precalc_mounts( 0, pivot_rotation[0], pivot_anchor[0] ); // update the fakes, and then repopulate the cache update_active_fakes(); - map &here = get_map(); - here.add_vehicle_to_cache( this ); - for( const int dirty_z : smzs ) { - here.on_vehicle_moved( dirty_z ); - } check_environmental_effects = true; insides_dirty = true; zones_dirty = true; @@ -7434,7 +7428,8 @@ int vehicle::get_non_fake_part( const int part_num ) return part_num; } } - debugmsg( "Returning -1 for get_non_fake_part on part_num %d on %s.", part_num, disp_name() ); + 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; } From 30574603e73b47ae14d348d3340c1cb38a893af5 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 20 Apr 2022 11:13:14 -0700 Subject: [PATCH 25/40] Silence serialization warnings --- src/vehicle.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vehicle.h b/src/vehicle.h index 874c1be85df1e..0cea3e838d976 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -524,12 +524,12 @@ struct vehicle_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; - bool is_active_fake = false; - int fake_part_to = -1; - int fake_protrusion_on = -1; - bool has_fake = false; - int fake_part_at = -1; + 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) }; From c3311437371f5a268ad450e043bacc943108dabc Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 20 Apr 2022 11:13:47 -0700 Subject: [PATCH 26/40] Use global ids in test --- tests/vehicle_fake_part_test.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/vehicle_fake_part_test.cpp b/tests/vehicle_fake_part_test.cpp index 4e4c8b064ac91..03e35fe45019c 100644 --- a/tests/vehicle_fake_part_test.cpp +++ b/tests/vehicle_fake_part_test.cpp @@ -17,6 +17,12 @@ #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(); @@ -73,7 +79,7 @@ TEST_CASE( "ensure_fake_parts_enable_on_place", "[vehicle] [vehicle_fake]" ) really_clear_map(); map &here = get_map(); - vehicle *veh = here.add_vehicle( vproto_id( "test_van" ), test_origin, angle, 100, 0 ); + 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 @@ -103,7 +109,7 @@ TEST_CASE( "ensure_fake_parts_enable_on_turn", "[vehicle] [vehicle_fake]" ) really_clear_map(); map &here = get_map(); const tripoint test_origin( 30, 30, 0 ); - vehicle *veh = here.add_vehicle( vproto_id( "test_van" ), test_origin, 0_degrees, 100, 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 @@ -161,7 +167,7 @@ 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( vproto_id( "suv" ), test_origin, 0_degrees, 0, 0 ); + vehicle *veh = here.add_vehicle( vehicle_prototype_suv, test_origin, 0_degrees, 0, 0 ); REQUIRE( veh != nullptr ); veh->tags.insert( "IN_CONTROL_OVERRIDE" ); @@ -189,7 +195,7 @@ TEST_CASE( "vehicle_collision_applies_damage_to_fake_parent", "[vehicle] [vehicl 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( vproto_id( "suv" ), test_origin, 0_degrees, 100, 0 ); + vehicle *veh = here.add_vehicle( vehicle_prototype_suv, test_origin, 0_degrees, 100, 0 ); REQUIRE( veh != nullptr ); veh->tags.insert( "IN_CONTROL_OVERRIDE" ); @@ -245,11 +251,9 @@ TEST_CASE( "vehicle_to_vehicle_collision", "[vehicle] [vehicle_fake]" ) { really_clear_map(); map &here = get_map(); - vproto_id test_van( "test_van" ); - vproto_id school_bus( "schoolbus" ); 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( test_van, test_origin, 30_degrees, 100, 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(); @@ -261,7 +265,7 @@ TEST_CASE( "vehicle_to_vehicle_collision", "[vehicle] [vehicle_fake]" ) 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( school_bus, obstacle_point, 90_degrees, 100, 0 ); + 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" ) { @@ -305,7 +309,7 @@ TEST_CASE( "ensure_vehicle_with_no_obstacles_has_no_fake_parts", "[vehicle] [veh 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( vproto_id( "bicycle" ), test_origin, 45_degrees, 100, 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" ) { From af3c4636720bcea4852b984956314feddee63f8c Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Fri, 22 Apr 2022 22:40:30 -0700 Subject: [PATCH 27/40] Insure we always refresh relative_parts with fake parts --- src/vehicle.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 1276aca3be0a1..bb8bb2487da98 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -5872,6 +5872,8 @@ void vehicle::refresh( const bool remove_fakes ) 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 ) ); @@ -5891,13 +5893,7 @@ void vehicle::refresh( const bool remove_fakes ) part_real.fake_part_at = fake_index; fake_parts.push_back( fake_index ); parts.push_back( part_fake ); - if( relative_parts.find( part_fake.mount ) == relative_parts.end() ) { - std::vector relative; - relative.push_back( fake_index ); - relative_parts[ part_fake.mount ] = relative; - } else { - relative_parts[ part_fake.mount ].push_back( fake_index ); - } + relative_parts[ part_fake.mount ].push_back( fake_index ); edges.emplace( real_mount, edge_info ); } }; @@ -5909,10 +5905,20 @@ void vehicle::refresh( const bool remove_fakes ) 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! From 407c5acb5b2353b38c610a428f228a659cd634d9 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 27 Apr 2022 16:54:33 -0700 Subject: [PATCH 28/40] Cache transparency of fake parts --- src/map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/map.cpp b/src/map.cpp index 1588c790cfc9b..27a1baaaa54f5 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -8353,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; From b66585ca7a80397e2844ef26a8b6322d650f47b6 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 27 Apr 2022 16:54:49 -0700 Subject: [PATCH 29/40] Apply scent blocking for fake parts --- src/map.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index 27a1baaaa54f5..5c893403fabec 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -8763,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; From 8e5b470a972833300e044099631c22c49209c511 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 27 Apr 2022 16:55:18 -0700 Subject: [PATCH 30/40] Show fake parts list in look_around --- src/vehicle_display.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vehicle_display.cpp b/src/vehicle_display.cpp index 63c2fbd9af604..e098e5659225b 100644 --- a/src/vehicle_display.cpp +++ b/src/vehicle_display.cpp @@ -169,7 +169,7 @@ int vehicle::print_part_list( const catacurses::window &win, int y1, const int m 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, true ); int y = y1; for( size_t i = 0; i < pl.size(); i++ ) { if( y >= max_y ) { From 1f53fa5698131b9dee8ff59a142fdc100d5956bb Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Mon, 2 May 2022 13:18:11 -0700 Subject: [PATCH 31/40] Extract code to insure that it is noon in tests --- tests/map_helpers.cpp | 13 ++++++++++++- tests/map_helpers.h | 1 + tests/npc_test.cpp | 9 +-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/map_helpers.cpp b/tests/map_helpers.cpp index 9a3620943706e..a3c70deb3d784 100644 --- a/tests/map_helpers.cpp +++ b/tests/map_helpers.cpp @@ -176,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 928009cb998f0..e15432d4a423b 100644 --- a/tests/map_helpers.h +++ b/tests/map_helpers.h @@ -24,6 +24,7 @@ monster &spawn_test_monster( const std::string &monster_type, const tripoint &st 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(); From d6d94fc76dce4acaf3685372c2fb669ed9fb0de1 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Thu, 28 Apr 2022 22:24:41 -0700 Subject: [PATCH 32/40] Add test for fake part transparency handling. --- tests/vehicle_fake_part_test.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/vehicle_fake_part_test.cpp b/tests/vehicle_fake_part_test.cpp index 03e35fe45019c..7a894537963cc 100644 --- a/tests/vehicle_fake_part_test.cpp +++ b/tests/vehicle_fake_part_test.cpp @@ -10,6 +10,7 @@ #include "map.h" #include "map_helpers.h" #include "optional.h" +#include "player_helpers.h" #include "point.h" #include "type_id.h" #include "vehicle.h" @@ -318,3 +319,20 @@ TEST_CASE( "ensure_vehicle_with_no_obstacles_has_no_fake_parts", "[vehicle] [veh } } } + +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 ) ) ); +} From 87c3a0e1ae9bc9a8690906f1a53261845ef85627 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Tue, 3 May 2022 11:23:22 -0700 Subject: [PATCH 33/40] Create a test exercising opening and closing fake vehicle parts --- tests/vehicle_fake_part_test.cpp | 82 ++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/vehicle_fake_part_test.cpp b/tests/vehicle_fake_part_test.cpp index 7a894537963cc..e8a5705d9ad24 100644 --- a/tests/vehicle_fake_part_test.cpp +++ b/tests/vehicle_fake_part_test.cpp @@ -1,6 +1,7 @@ #include #include +#include "action.h" #include "avatar.h" #include "catch/catch.hpp" #include "damage.h" @@ -336,3 +337,84 @@ TEST_CASE( "fake_parts_are_opaque", "[vehicle],[vehicle_fake]" ) 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 ); +} From 978a259c2809fbbb7827ed24cdd3cbbcadaa9868 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Tue, 3 May 2022 11:23:41 -0700 Subject: [PATCH 34/40] Fix opening and closing fake vehicle parts --- src/vehicle.cpp | 4 ++-- src/vehicle_use.cpp | 39 +++++++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index bb8bb2487da98..9888772e5dca6 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -2704,7 +2704,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(); @@ -2723,7 +2723,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 ) { diff --git a/src/vehicle_use.cpp b/src/vehicle_use.cpp index 08c17c726d629..ac558ddf9ef32 100644 --- a/src/vehicle_use.cpp +++ b/src/vehicle_use.cpp @@ -1626,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; } } } @@ -1647,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 From 3de047bffb2ae52db8cb0f8051ab98acfa180d14 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Tue, 3 May 2022 21:05:01 -0700 Subject: [PATCH 35/40] Only clear cache entries for real parts and active fakes. --- src/vehicle.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 9888772e5dca6..63ecb1c936ec6 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1900,8 +1900,10 @@ bool vehicle::__part_removal_actual() items.erase( items.begin() ); } } - const tripoint pt = global_part_pos3( *it ); - here.clear_vehicle_point_from_cache( this, pt ); + 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; } @@ -7498,7 +7500,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]; From 939b01d7a060d6db62090895322e4ad884724651 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 4 May 2022 09:37:46 -0700 Subject: [PATCH 36/40] Silence clang-tidy warnings and clean up part list cache comments. --- src/vehicle.h | 61 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/vehicle.h b/src/vehicle.h index 0cea3e838d976..2bba9e9671371 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -1898,11 +1898,12 @@ class vehicle // Cached points occupied by the vehicle mutable std::set occupied_points; // NOLINT(cata-serialize) - std::vector parts; // Parts which occupy different tiles + // 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; + 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; @@ -1953,35 +1954,33 @@ class vehicle // make sure the vehicle is supported across z-levels or on the same z-level bool level_vehicle(); - - std::vector omt_path; // route for overmap-scale auto-driving - - 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 that provide buoyancy to boats NOLINT(cata-serialize) - std::vector batteries; // List of batteries NOLINT(cata-serialize) - std::vector fuel_containers; // List 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 accessory (power consuming) parts NOLINT(cata-serialize) - std::vector - fake_parts; // List of parts that are fakes to fill gaps 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 From 5a1900c427485e00fb38979af98e46aa5bca663d Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 28 May 2022 16:46:30 -0700 Subject: [PATCH 37/40] revert weird DSA addition. --- .../mapgen/map_extras/patrol_test.json | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 data/mods/Dark-Skies-Above/mapgen/map_extras/patrol_test.json diff --git a/data/mods/Dark-Skies-Above/mapgen/map_extras/patrol_test.json b/data/mods/Dark-Skies-Above/mapgen/map_extras/patrol_test.json deleted file mode 100644 index de21d2af9b418..0000000000000 --- a/data/mods/Dark-Skies-Above/mapgen/map_extras/patrol_test.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "type": "mapgen", - "method": "json", - "update_mapgen_id": "mx_dsa_patrol_test", - "object": { - "place_monster": [ - { - "monster": "mon_dks_glowdrone", - "x": 12, - "y": 12, - "repeat": [ 2, 3 ], - "chance": 100, - "spawn_data": { "patrol": [ { "x": -5, "y": -5 }, { "x": 5, "y": -5 }, { "x": 5, "y": 5 }, { "x": -5, "y": 5 } ] } - } - ] - } - }, - { - "id": "mx_dsa_patrol_test", - "type": "map_extra", - "name": { "str": "Patrol Test" }, - "description": "Test of the patrol logic.", - "generator": { "generator_method": "update_mapgen", "generator_id": "mx_dsa_patrol_test" }, - "sym": "!", - "color": "red", - "autonote": true - }, - { - "type": "mapgen", - "method": "json", - "update_mapgen_id": "mx_dsa_stray_patrol_test", - "object": { - "place_monster": [ - { - "monster": "dks_mon_stray", - "x": [ 6, 18 ], - "y": [ 6, 18 ], - "repeat": [ 2, 3 ], - "chance": 100, - "spawn_data": { "patrol": [ { "x": -5, "y": -5 }, { "x": 29, "y": -5 }, { "x": 29, "y": 29 }, { "x": -5, "y": 29 } ] } - } - ] - } - }, - { - "id": "mx_dsa_stray_patrol_test", - "type": "map_extra", - "name": { "str": "Stray Patrol Test" }, - "description": "Test of the patrol logic.", - "generator": { "generator_method": "update_mapgen", "generator_id": "mx_dsa_stray_patrol_test" }, - "sym": "!", - "color": "red", - "autonote": true - } -] From 1595f0c126ccdf0022e156333873c32a1cc70364 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 28 May 2022 19:25:20 -0700 Subject: [PATCH 38/40] Filter out inactive fakes in look around --- src/vehicle_display.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vehicle_display.cpp b/src/vehicle_display.cpp index e098e5659225b..af8f0cd8084b3 100644 --- a/src/vehicle_display.cpp +++ b/src/vehicle_display.cpp @@ -172,13 +172,16 @@ int vehicle::print_part_list( const catacurses::window &win, int y1, const int m std::vector pl = this->parts_at_relative( parts[p].mount, true, true ); 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(); From c840e67df1a814f9a12dbeee5879d96325b46b57 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Sat, 28 May 2022 23:19:24 -0700 Subject: [PATCH 39/40] Prevent displaying fake parts in vehicle interactions --- src/veh_interact.cpp | 10 +++++----- src/vehicle.h | 6 +++--- src/vehicle_display.cpp | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) 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.h b/src/vehicle.h index 2bba9e9671371..f4496307ba549 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -861,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, @@ -1168,11 +1168,11 @@ class 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; diff --git a/src/vehicle_display.cpp b/src/vehicle_display.cpp index af8f0cd8084b3..924c67a58fe4a 100644 --- a/src/vehicle_display.cpp +++ b/src/vehicle_display.cpp @@ -34,13 +34,13 @@ 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 ' '; } - int displayed_part = exact ? p : part_displayed_at( parts[p].mount, true ); + int displayed_part = exact ? p : part_displayed_at( parts[p].mount, include_fake ); if( displayed_part == -1 ) { displayed_part = p; } @@ -96,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; @@ -114,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, true ); + 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; @@ -164,12 +164,12 @@ 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, 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 ] ]; From 62fd404f72f63789b4770330c35d6cc8387d548f Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Wed, 1 Jun 2022 21:02:23 -0700 Subject: [PATCH 40/40] Move insertion into parts vector to end of lambda to avoid dereferencing real_mount after it has been invalidated. --- src/vehicle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 63ecb1c936ec6..26c5b2bc62a07 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -5894,9 +5894,9 @@ void vehicle::refresh( const bool remove_fakes ) int fake_index = parts.size(); part_real.fake_part_at = fake_index; fake_parts.push_back( fake_index ); - parts.push_back( part_fake ); 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