diff --git a/data/json/vehicleparts/battery.json b/data/json/vehicleparts/battery.json index 348b64e06a34..cf11262faee1 100644 --- a/data/json/vehicleparts/battery.json +++ b/data/json/vehicleparts/battery.json @@ -25,7 +25,7 @@ "using": [ [ "soldering_standard", 5 ], [ "vehicle_repair_electronics", 1 ], [ "plastics", 1 ] ] } }, - "flags": [ "FOLDABLE" ] + "flags": [ "FOLDABLE", "SHOCK_RESISTANT" ] }, { "id": "battery_motorbike", diff --git a/data/json/vehicleparts/vehicle_parts.json b/data/json/vehicleparts/vehicle_parts.json index c25332310c4e..0fa0f315d0e1 100644 --- a/data/json/vehicleparts/vehicle_parts.json +++ b/data/json/vehicleparts/vehicle_parts.json @@ -116,7 +116,7 @@ "removal": { "skills": [ [ "mechanics", 2 ] ], "time": "15 m", "using": [ [ "vehicle_bolt", 1 ] ] }, "repair": { "skills": [ [ "mechanics", 2 ] ], "time": "30 m", "using": [ [ "welding_standard", 5 ], [ "steel_tiny", 1 ] ] } }, - "flags": [ "SEAT", "BOARDABLE", "CARGO", "BELTABLE" ], + "flags": [ "SEAT", "BOARDABLE", "CARGO", "BELTABLE", "SHOCK_RESISTANT" ], "breaks_into": "ig_vp_seat", "damage_reduction": { "all": 2 } }, @@ -151,7 +151,7 @@ "removal": { "skills": [ [ "mechanics", 2 ] ], "time": "15 m", "using": [ [ "vehicle_bolt", 1 ] ] }, "repair": { "skills": [ [ "mechanics", 2 ] ], "time": "30 m", "using": [ [ "welding_standard", 5 ], [ "steel_tiny", 1 ] ] } }, - "flags": [ "BED", "SEAT", "BOARDABLE", "BELTABLE", "CARGO" ], + "flags": [ "BED", "SEAT", "BOARDABLE", "BELTABLE", "CARGO", "SHOCK_RESISTANT" ], "breaks_into": "ig_vp_seat", "damage_reduction": { "all": 3 } }, @@ -680,7 +680,7 @@ "color": "light_gray", "broken_symbol": "*", "broken_color": "dark_gray", - "flags": [ "NO_REPAIR" ], + "flags": [ "NO_REPAIR", "SHOCK_IMMUNE" ], "requirements": { "install": { "skills": [ [ "mechanics", 2 ] ], "qualities": [ { "id": "SCREW", "level": 3 }, { "id": "WRENCH", "level": 3 } ] }, "removal": { "skills": [ [ "mechanics", 2 ] ], "using": [ [ "vehicle_screw", 1 ] ] } @@ -1641,7 +1641,7 @@ "removal": { "skills": [ [ "mechanics", 1 ] ], "time": "150 s", "using": [ [ "vehicle_screw", 1 ] ] }, "repair": { "skills": [ [ "mechanics", 1 ] ], "time": "20 s", "using": [ [ "adhesive", 1 ], [ "fabric_standard", 1 ] ] } }, - "flags": [ "SEATBELT", "FOLDABLE" ], + "flags": [ "SEATBELT", "FOLDABLE", "SHOCK_IMMUNE" ], "breaks_into": [ { "item": "nylon", "count": [ 0, 3 ] } ] }, { @@ -1695,7 +1695,7 @@ "removal": { "skills": [ [ "mechanics", 2 ] ], "time": "5 m", "using": [ [ "vehicle_screw", 1 ] ] }, "repair": { "skills": [ [ "mechanics", 2 ] ], "time": "20 s", "using": [ [ "adhesive", 1 ], [ "fabric_standard", 1 ] ] } }, - "flags": [ "SEATBELT", "FOLDABLE" ], + "flags": [ "SEATBELT", "FOLDABLE", "SHOCK_IMMUNE" ], "breaks_into": [ { "item": "seatbelt", "count": [ 0, 3 ] } ] }, { @@ -2916,7 +2916,7 @@ "using": [ [ "adhesive", 1 ], [ "vehicle_repair_electronics", 1 ] ] } }, - "flags": [ "WATCH", "ALARMCLOCK", "FOLDABLE" ], + "flags": [ "WATCH", "ALARMCLOCK", "FOLDABLE", "SHOCK_RESISTANT" ], "breaks_into": [ { "item": "scrap", "prob": 50 } ] }, { diff --git a/data/json/vehicleparts/vp_flags.json b/data/json/vehicleparts/vp_flags.json index 812766f64022..bfb704280a94 100644 --- a/data/json/vehicleparts/vp_flags.json +++ b/data/json/vehicleparts/vp_flags.json @@ -256,5 +256,15 @@ "context": [ "vehicle_part" ], "type": "json_flag", "requires_flag": "WHEEL_MOUNT_SKATEBOARD" + }, + { + "id": "SHOCK_IMMUNE", + "context": [ "vehicle_part" ], + "type": "json_flag" + }, + { + "id": "SHOCK_RESISTANT", + "context": [ "vehicle_part" ], + "type": "json_flag" } ] diff --git a/src/map.cpp b/src/map.cpp index ac12b6746329..ef78a3760b44 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -135,6 +135,9 @@ static const efftype_id effect_crushed( "crushed" ); static const ter_str_id t_rock_floor_no_roof( "t_rock_floor_no_roof" ); +// Conversion constant for 100ths of miles per hour to meters per second +constexpr float velocity_constant = 0.0044704; + #define dbg(x) DebugLog((x),DC::Map) static location_vector nulitems( new @@ -297,6 +300,7 @@ maptile map::maptile_at_internal( const tripoint &p ) // Vehicle functions + VehicleList map::get_vehicles() { if( last_full_vehicle_list_dirty ) { @@ -682,7 +686,19 @@ vehicle *map::move_vehicle( vehicle &veh, const tripoint &dp, const tileray &fac } else { impulse += coll_dmg; veh.damage( coll.part, coll_dmg, DT_BASH ); - veh.damage_all( coll_dmg / 2, coll_dmg, DT_BASH, collision_point ); + // Upper bound of shock damage + int shock_max = coll_dmg; + // Lower bound of shock damage + int shock_min = coll_dmg / 2; + float coll_part_bash_resist = veh.part_info( coll.part ).damage_reduction.type_resist( + DT_BASH ); + // Reduce shock damage by collision part DR to prevent bushes from damaging car batteries + shock_min = std::max( 0, shock_min - coll_part_bash_resist ); + shock_max = std::max( 0, shock_max - coll_part_bash_resist ); + // Shock damage decays exponentially, we only want to track shock damage that would cause meaningful damage. + if( shock_min >= 20 ) { + veh.damage_all( shock_min, shock_max, DT_BASH, collision_point ); + } } } @@ -849,18 +865,23 @@ float map::vehicle_vehicle_collision( vehicle &veh, vehicle &veh2, point epicenter1; point epicenter2; - float dmg; + float veh1_impulse = 0; + float veh2_impulse = 0; + float delta_vel = 0; + // A constant to tune how many Ns of impulse are equivalent to 1 point of damage, look in vehicle_move.cpp for the impulse to damage function. + const float dmg_adjust = impulse_to_damage( 1 ); + float dmg_veh1 = 0; + float dmg_veh2 = 0; // Vertical collisions will be simpler for a while (1D) if( !vertical ) { // For reference, a cargo truck weighs ~25300, a bicycle 690, // and 38mph is 3800 'velocity' + // Converting away from 100*mph, because mixing unit systems is bad. + // 1 mph = 0.44704m/s = 100 "velocity". For velocity to m/s, *0.0044704 rl_vec2d velo_veh1 = veh.velo_vec(); rl_vec2d velo_veh2 = veh2.velo_vec(); const float m1 = to_kilogram( veh.total_mass() ); const float m2 = to_kilogram( veh2.total_mass() ); - //Energy of vehicle1 and vehicle2 before collision - float E = 0.5 * m1 * velo_veh1.magnitude() * velo_veh1.magnitude() + - 0.5 * m2 * velo_veh2.magnitude() * velo_veh2.magnitude(); // Collision_axis point cof1 = veh .rotated_center_of_mass(); @@ -878,15 +899,18 @@ float map::vehicle_vehicle_collision( vehicle &veh, vehicle &veh2, // imp? & delta? & final? reworked: // newvel1 =( vel1 * ( mass1 - mass2 ) + ( 2 * mass2 * vel2 ) ) / ( mass1 + mass2 ) // as per http://en.wikipedia.org/wiki/Elastic_collision - //velocity of veh1 before collision in the direction of collision_axis_y - float vel1_y = collision_axis_y.dot_product( velo_veh1 ); - float vel1_x = collision_axis_x.dot_product( velo_veh1 ); - //velocity of veh2 before collision in the direction of collision_axis_y - float vel2_y = collision_axis_y.dot_product( velo_veh2 ); - float vel2_x = collision_axis_x.dot_product( velo_veh2 ); + //velocity of veh1 before collision in the direction of collision_axis_y, converting to m/s + float vel1_y = velocity_constant * collision_axis_y.dot_product( velo_veh1 ); + float vel1_x = velocity_constant * collision_axis_x.dot_product( velo_veh1 ); + //velocity of veh2 before collision in the direction of collision_axis_y, converting to m/s + float vel2_y = velocity_constant * collision_axis_y.dot_product( velo_veh2 ); + float vel2_x = velocity_constant * collision_axis_x.dot_product( velo_veh2 ); + delta_vel = std::abs( vel1_y - vel2_y ); + // Keep in mind get_collision_factor is looking for m/s, not m/h. // e = 0 -> inelastic collision // e = 1 -> elastic collision - float e = get_collision_factor( vel1_y / 100 - vel2_y / 100 ); + float e = get_collision_factor( vel1_y - vel2_y ); + add_msg( m_debug, "Requested collision factor, received %.2f", e ); // Velocity after collision // vel1_x_a = vel1_x, because in x-direction we have no transmission of force @@ -894,11 +918,11 @@ float map::vehicle_vehicle_collision( vehicle &veh, vehicle &veh2, float vel2_x_a = vel2_x; // Transmission of force only in direction of collision_axix_y // Equation: partially elastic collision - float vel1_y_a = ( m2 * vel2_y * ( 1 + e ) + vel1_y * ( m1 - m2 * e ) ) / ( m1 + m2 ); - float vel2_y_a = ( m1 * vel1_y * ( 1 + e ) + vel2_y * ( m2 - m1 * e ) ) / ( m1 + m2 ); + float vel1_y_a = ( ( m2 * vel2_y * ( 1 + e ) + vel1_y * ( m1 - m2 * e ) ) / ( m1 + m2 ) ); + float vel2_y_a = ( ( m1 * vel1_y * ( 1 + e ) + vel2_y * ( m2 - m1 * e ) ) / ( m1 + m2 ) ); // Add both components; Note: collision_axis is normalized - rl_vec2d final1 = collision_axis_y * vel1_y_a + collision_axis_x * vel1_x_a; - rl_vec2d final2 = collision_axis_y * vel2_y_a + collision_axis_x * vel2_x_a; + rl_vec2d final1 = ( collision_axis_y * vel1_y_a + collision_axis_x * vel1_x_a ) / velocity_constant; + rl_vec2d final2 = ( collision_axis_y * vel2_y_a + collision_axis_x * vel2_x_a ) / velocity_constant; veh.move.init( final1.as_point() ); if( final1.dot_product( veh.face_vec() ) < 0 ) { @@ -925,21 +949,28 @@ float map::vehicle_vehicle_collision( vehicle &veh, vehicle &veh2, veh.of_turn = avg_of_turn * .9; veh2.of_turn = avg_of_turn * 1.1; - //Energy after collision - float E_a = 0.5 * m1 * final1.magnitude() * final1.magnitude() + - 0.5 * m2 * final2.magnitude() * final2.magnitude(); - float d_E = E - E_a; //Lost energy at collision -> deformation energy - dmg = std::abs( d_E / 1000 / 2000 ); //adjust to balance damage + // Remember that the impulse on vehicle 1 is techncally negative, slowing it + veh1_impulse = std::abs( m1 * ( vel1_y_a - vel1_y ) ); + veh2_impulse = std::abs( m2 * ( vel2_y_a - vel2_y ) ); } else { const float m1 = to_kilogram( veh.total_mass() ); // Collision is perfectly inelastic for simplicity // Assume veh2 is standing still - dmg = std::abs( veh.vertical_velocity / 100 ) * m1 / 10; + dmg_veh1 = ( std::abs( vmiph_to_mps( veh.vertical_velocity ) ) * ( m1 / 10 ) ) / 2; + dmg_veh2 = dmg_veh1; veh.vertical_velocity = 0; } - float dmg_veh1 = dmg * 0.5; - float dmg_veh2 = dmg * 0.5; + // To facilitate pushing vehicles, because the simulation pretends cars are ping pong balls that get all their velocity in zero starting distance to slam into eachother while touching. + // Stay under 6 m/s to push cars without damaging them + if( delta_vel >= 6.0f ) { + dmg_veh1 = veh1_impulse * dmg_adjust; + dmg_veh2 = veh2_impulse * dmg_adjust; + } else { + dmg_veh1 = 0; + dmg_veh2 = 0; + } + int coll_parts_cnt = 0; //quantity of colliding parts between veh1 and veh2 for( const auto &veh_veh_coll : collisions ) { diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 2636cc39df84..0969f8f99e88 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -6644,24 +6644,46 @@ void vehicle::damage_all( int dmg1, int dmg2, damage_type type, point impact ) if( dmg2 < dmg1 ) { std::swap( dmg1, dmg2 ); } - if( dmg1 < 1 ) { return; } - + const float damage_min = std::abs( dmg1 ); + const float damage_max = std::abs( dmg2 ); + add_msg( m_debug, "Shock damage to vehicle of %.2f to %.2f", damage_min, damage_max ); for( const vpart_reference &vp : get_all_parts() ) { const size_t p = vp.part_index(); + const vpart_info &shockpart = part_info( p ); int distance = 1 + square_dist( vp.mount(), impact ); if( distance > 1 ) { int net_dmg = rng( dmg1, dmg2 ) / ( distance * distance ); - if( part_info( p ).location != part_location_structure || - !part_info( p ).has_flag( "PROTRUSION" ) ) { + if( shockpart.location != part_location_structure || + !shockpart.has_flag( "PROTRUSION" ) ) { + if( shockpart.has_flag( "SHOCK_IMMUNE" ) ) { + net_dmg = 0; + continue; + } int shock_absorber = part_with_feature( p, "SHOCK_ABSORBER", true ); if( shock_absorber >= 0 ) { - net_dmg = std::max( 0, net_dmg - parts[ shock_absorber ].info().bonus ); + net_dmg = std::max( 0.0f, net_dmg - ( parts[ shock_absorber ].info().bonus ) - + shockpart.damage_reduction.type_resist( type ) ); + } + if( shockpart.has_flag( "SHOCK_RESISTANT" ) ) { + float damage_resist = 0; + for( const int elem : all_parts_at_location( shockpart.location ) ) { + //Original intent was to find the frame that the part was mounted on and grab that objects resistance, but instead we will go with half the largest damage resist in the stack. + damage_resist = std::max( damage_resist, part_info( elem ).damage_reduction.type_resist( type ) ); + } + damage_resist = damage_resist / 2; + + add_msg( m_debug, "%1s inherited %.1f damage resistance!", shockpart.name(), damage_resist ); + net_dmg = std::max( 0.0f, net_dmg - damage_resist ); } } - damage_direct( p, net_dmg, type ); + if( net_dmg > part_info( p ).damage_reduction.type_resist( type ) ) { + damage_direct( p, net_dmg, type ); + add_msg( m_debug, _( "%1s took %.1f damage from shock." ), part_info( p ).name(), 1.0f * net_dmg ); + } + } } } diff --git a/src/vehicle.h b/src/vehicle.h index 279aa75c3257..126ec31340cb 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -70,7 +70,7 @@ struct rider_data { int prt = -1; bool moved = false; }; -//collision factor for vehicle-vehicle collision; delta_v in mph +//collision factor for vehicle-vehicle collision; delta_v in m/s float get_collision_factor( float delta_v ); //How far to scatter parts from a vehicle when the part is destroyed (+/-) @@ -112,7 +112,7 @@ struct veh_collision { //int veh? int part = 0; veh_coll_type type = veh_coll_nothing; - // impulse + // Impulse, in Ns. Call impulse_to_damage or damage_to_impulse from vehicle_move.cpp for conversion to damage. int imp = 0; //vehicle void *target = nullptr; @@ -183,6 +183,8 @@ int mps_to_vmiph( double mps ); double vmiph_to_mps( int vmiph ); int cmps_to_vmiph( int cmps ); int vmiph_to_cmps( int vmiph ); +float impulse_to_damage( float impulse ); +float damage_to_impulse( float damage ); class turret_data { diff --git a/src/vehicle_move.cpp b/src/vehicle_move.cpp index 94130752e1f8..9ed790affe29 100644 --- a/src/vehicle_move.cpp +++ b/src/vehicle_move.cpp @@ -67,6 +67,12 @@ static const float tile_height = 4; static const int mi_to_vmi = 100; // meters per second to miles per hour static const float mps_to_miph = 2.23694f; +// Conversion constant for Impulse Ns to damage for vehicle collisions. Fine tune to desired damage. +static const float imp_conv_const = 0.1; +// Inverse conversion constant for impulse to damage +static const float imp_conv_const_inv = 1 / imp_conv_const; +// Conversion constant for 100ths of miles per hour to meters per second +constexpr float velocity_constant = 0.0044704; // convert m/s to vehicle 100ths of a mile per hour int mps_to_vmiph( double mps ) @@ -77,7 +83,7 @@ int mps_to_vmiph( double mps ) // convert vehicle 100ths of a mile per hour to m/s double vmiph_to_mps( int vmiph ) { - return vmiph / mps_to_miph / mi_to_vmi; + return vmiph * velocity_constant; } int cmps_to_vmiph( int cmps ) @@ -89,13 +95,24 @@ int vmiph_to_cmps( int vmiph ) { return vmiph / mps_to_miph; } +// Conversion of impulse Ns to damage for vehicle collision purposes. +float impulse_to_damage( float impulse ) +{ + return impulse * imp_conv_const; +} + +// Convert damage back to impulse Ns +float damage_to_impulse( float damage ) +{ + return damage * imp_conv_const_inv; +} int vehicle::slowdown( int at_velocity ) const { double mps = vmiph_to_mps( std::abs( at_velocity ) ); // slowdown due to air resistance is proportional to square of speed - double f_total_drag = coeff_air_drag() * mps * mps; + double f_total_drag = std::abs( coeff_air_drag() * mps * mps ); if( is_watercraft() ) { // same with water resistance f_total_drag += coeff_water_drag() * mps * mps; @@ -112,7 +129,7 @@ int vehicle::slowdown( int at_velocity ) const } double accel_slowdown = f_total_drag / to_kilogram( total_mass() ); // converting m/s^2 to vmiph/s - int slowdown = mps_to_vmiph( accel_slowdown ); + float slowdown = mps_to_vmiph( accel_slowdown ); if( is_towing() ) { vehicle *other_veh = tow_data.get_towed(); if( other_veh ) { @@ -122,14 +139,14 @@ int vehicle::slowdown( int at_velocity ) const if( slowdown < 0 ) { debugmsg( "vehicle %s has negative drag slowdown %d\n", name, slowdown ); } - add_msg( m_debug, "%s at %d vimph, f_drag %3.2f, drag accel %d vmiph - extra drag %d", + add_msg( m_debug, "%s at %d vimph, f_drag %3.2f, drag accel %.1f vmiph - extra drag %d", name, at_velocity, f_total_drag, slowdown, static_drag() ); // plows slow rolling vehicles, but not falling or floating vehicles if( !( is_falling || is_floating || is_flying ) ) { slowdown -= static_drag(); } - return std::max( 1, slowdown ); + return std::max( 1.0f, slowdown ); } void vehicle::thrust( int thd, int z ) @@ -554,13 +571,18 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, if( armor_part >= 0 ) { ret.part = armor_part; } - - int dmg_mod = part_info( ret.part ).dmg_mod; + // Damage modifier, pre-divided by 100. 1 is full collision damage, 1.5 is 50% bonus, etc. + int dmg_mod = ( part_info( ret.part ).dmg_mod ) / 100; + // Failsafe incase of wierdness. + if( dmg_mod == 0 ) { + dmg_mod = 1; + } // Let's calculate type of collision & mass of object we hit float mass2 = 0; // e = 0 -> plastic collision - float e = 0.3; // e = 1 -> inelastic collision + float e = 0.3; + //part density float part_dens = 0; @@ -611,7 +633,10 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, const float mass = ( part_info( part ).rotor_diameter() > 0 ) ? to_kilogram( parts[ part ].base->weight() ) : to_kilogram( total_mass() ); - //Calculate damage resulting from d_E + // No longer calculating damage based on deformation energy. + // Damage to each object is based upon force applied from change in momentum + + //Finds vehicle part density using part material const material_id_list &mats = part_info( ret.part ).item->materials; float vpart_dens = 0; if( !mats.empty() ) { @@ -622,25 +647,33 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, vpart_dens /= mats.size(); } - //k=100 -> 100% damage on part - //k=0 -> 100% damage on obj - float material_factor = ( part_dens - vpart_dens ) * 0.5; - material_factor = std::max( -25.0f, std::min( 25.0f, material_factor ) ); + //Calculates density factor. Used as a bad stand in to determine deformation distance. + //Ranges from 0.1 -> 100, measured in cm. A density difference of 100 is needed for the full value. + float density_factor = std::abs( part_dens - vpart_dens ); + density_factor = clamp( density_factor, 0.1f, 100.0f ); + + //Deformation distance of the collision, measured in meters. A bad approximation for a value we dont have that would take intensive simulation to determine. + // 0.001 -> 1 meter. Left modifiable so that armor or other parts can affect it. + float deformation_distance = density_factor / 100; + + //Calculates mass factor. Depreciated, maintained for stats. // factor = -25 if mass is much greater than mass2 // factor = +25 if mass2 is much greater than mass const float weight_factor = mass >= mass2 ? -25 * ( std::log( mass ) - std::log( mass2 ) ) / std::log( mass ) : 25 * ( std::log( mass2 ) - std::log( mass ) ) / std::log( mass2 ); - float k = 50 + material_factor + weight_factor; - k = std::max( 10.0f, std::min( 90.0f, k ) ); - bool smashed = true; const std::string snd = _( "smash!" ); - float dmg = 0.0f; - float part_dmg = 0.0f; - // Calculate Impulse of car + float part_dmg = 0; + float obj_dmg = 0; + // Calculate stun time of car time_duration time_stunned = 0_turns; + float vel1_a = 0; + float vel2_a = 0; + float impulse_veh = 0; + float impulse_obj = 0; + int critter_health = 0; const int prev_velocity = coll_velocity; const int vel_sign = sgn( coll_velocity ); @@ -650,24 +683,28 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, float vel2 = 0.0f; do { smashed = false; - // Impulse of vehicle - const float vel1 = coll_velocity / 100.0f; + // Velocity of vehicle for calculations + // Changed from mph to m/s, because mixing unit systems is a nono + const float vel1 = vmiph_to_mps( coll_velocity ); // Velocity of car after collision - const float vel1_a = ( mass * vel1 + mass2 * vel2 + e * mass2 * ( vel2 - vel1 ) ) / - ( mass + mass2 ); - // Velocity of object after collision - const float vel2_a = ( mass * vel1 + mass2 * vel2 + e * mass * ( vel1 - vel2 ) ) / ( mass + mass2 ); - // Lost energy at collision -> deformation energy -> damage - const float E_before = 0.5f * ( mass * vel1 * vel1 ) + 0.5f * ( mass2 * vel2 * vel2 ); - const float E_after = 0.5f * ( mass * vel1_a * vel1_a ) + 0.5f * ( mass2 * vel2_a * vel2_a ); - const float d_E = E_before - E_after; - if( d_E <= 0 ) { - // Deformation energy is signed - // If it's negative, it means something went wrong - // But it still does happen sometimes... + vel1_a = ( mass * vel1 + mass2 * vel2 + e * mass2 * ( vel2 - vel1 ) ) / + ( mass + mass2 ); + // Velocity of object 2 after collision + vel2_a = ( mass * vel1 + mass2 * vel2 + e * mass * ( vel1 - vel2 ) ) / ( mass + mass2 ); + + // Impulse of vehicle part from collision. Measured in newton seconds (Ns) + // Calculated as the change in momentum due to the collision + impulse_veh = std::abs( mass * ( vel1_a - vel1 ) ); + // Impulse of the impacted object from collision. Measured in newton seconds (Ns) + // Calculated as the change in momentum due to the collision. + impulse_obj = std::abs( mass2 * ( vel2_a - vel2 ) ); + // Due to conservation of momentum, both of these values should be equal. If not, something odd has happened and physics will be wonky. Small threshold for rounding errors. + if( std::abs( impulse_obj - impulse_veh ) > 5 ) { + add_msg( m_debug, + "Conservation of momentum violated, impulse values between object and vehicle are not equal! " ); if( std::fabs( vel1_a ) < std::fabs( vel1 ) ) { // Lower vehicle's speed to prevent infinite loops - coll_velocity = vel1_a * 90; + coll_velocity = mps_to_vmiph( vel1_a ) * 0.9; } if( std::fabs( vel2_a ) > std::fabs( vel2 ) ) { vel2 = vel2_a; @@ -679,19 +716,35 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, continue; } - add_msg( m_debug, "Deformation energy: %.2f", d_E ); // Damage calculation - // Damage dealt overall - dmg += d_E / 400; + // Maximum damage for vehicle part to take + const float bash_max = here.bash_strength( p, bash_floor ); + + // Damage for vehicle-part // Always if no critters, otherwise if critter is real if( critter == nullptr || !critter->is_hallucination() ) { - part_dmg = dmg * k / 100; - add_msg( m_debug, "Part collision damage: %.2f", part_dmg ); - } - // Damage for object - const float obj_dmg = dmg * ( 100 - k ) / 100; + part_dmg = impulse_to_damage( impulse_veh ); + if( bash_max != 0 ) { + part_dmg = std::min( bash_max / dmg_mod, part_dmg ); + } + + //add_msg( m_debug, "Part collision damage: %.2f", part_dmg ); + } else { + part_dmg = 0; + add_msg( m_debug, "Part damage 0, critter assumed to be hallucination" ); + } + // Damage for object. + obj_dmg = impulse_to_damage( impulse_obj ) * dmg_mod; + ret.target_name = here.disp_name( p ); + add_msg( m_debug, _( "%1s collided with %2s!" ), name, ret.target_name ); + add_msg( m_debug, + "Vehicle mass of %.2f Kg with a Pre-Collision Velocity of %d vmph, collision object mass of %.2f Kg, with a Velocity of %.2f mph. predicted deformation distance is %.2f meters.", + mass, prev_velocity, mass2, vel2, deformation_distance ); + add_msg( m_debug, + "Vehicle impulse of %.2f Ns resulted in Part collision damage %.2f of a maximum of %.2f, Object impulse of %.2f resulted in damage of %.2f (dmg mod %0.2i)", + impulse_veh, part_dmg, bash_max, impulse_obj, obj_dmg, dmg_mod ); if( ret.type == veh_coll_bashable ) { // Something bashable -- use map::bash to determine outcome // NOTE: Floor bashing disabled for balance reasons @@ -700,6 +753,17 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, here.bash_resistance( p, bash_floor ) <= obj_dmg && here.bash( p, obj_dmg, false, false, false, this ).success; if( smashed ) { + //Experimental: only use as much energy as was required to destroy the obstacle, and recalc impulse and momentum off of that. + //Recalculate vel1_a from new impulse. Remember that the impulse from a collision is technically negative, reducing speed. + float old_veh_imp = impulse_veh; + impulse_veh = damage_to_impulse( std::min( part_dmg, bash_max / dmg_mod ) ); + add_msg( m_debug, "Terrain collision impulse recovery of %.2f Ns", old_veh_imp - impulse_veh ); + if( ( vel1 - ( impulse_veh / mass ) ) < vel1 ) { + vel1_a = ( vel1 - ( impulse_veh / mass ) ); + add_msg( m_debug, "Post collision velocity recovered to %.2f m/s", vel1_a ); + } + add_msg( m_debug, _( "%1s smashed %2s!" ), name, ret.target_name ); + if( here.is_bashable_ter_furn( p, bash_floor ) ) { // There's new terrain there to smash smashed = false; @@ -716,7 +780,6 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, } } } else if( ret.type == veh_coll_body ) { - int dam = obj_dmg * dmg_mod / 100; // We know critter is set for this type. Assert to inform static // analysis. @@ -724,29 +787,48 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, // No blood from hallucinations if( !critter->is_hallucination() ) { + // Get critter health for determining max damage to apply + critter_health = critter->get_hp_max(); if( part_flag( ret.part, "SHARP" ) ) { - parts[ret.part].blood += ( 20 + dam ) * 5; - } else if( dam > rng( 10, 30 ) ) { - parts[ret.part].blood += ( 10 + dam / 2 ) * 5; + parts[ret.part].blood += ( 20 + obj_dmg ) * 5; + } else if( obj_dmg > rng( 10, 30 ) ) { + parts[ret.part].blood += ( 10 + obj_dmg / 2 ) * 5; } check_environmental_effects = true; } - time_stunned = time_duration::from_turns( ( rng( 0, dam ) > 10 ) + ( rng( 0, dam ) > 40 ) ); + time_stunned = time_duration::from_turns( ( rng( 0, obj_dmg ) > 10 ) + ( rng( 0, obj_dmg ) > 40 ) ); if( time_stunned > 0_turns ) { critter->add_effect( effect_stunned, time_stunned ); } if( ph != nullptr ) { - ph->hitall( dam, 40, driver ); + ph->hitall( obj_dmg, 40, driver ); } else { const int armor = part_flag( ret.part, "SHARP" ) ? critter->get_armor_cut( bodypart_id( "torso" ) ) : critter->get_armor_bash( bodypart_id( "torso" ) ); - dam = std::max( 0, dam - armor ); - critter->apply_damage( driver, bodypart_id( "torso" ), dam ); - add_msg( m_debug, "Critter collision damage: %d", dam ); + obj_dmg = std::max( 0.0f, obj_dmg - armor ); + critter->apply_damage( driver, bodypart_id( "torso" ), obj_dmg ); + + // Limit vehicle damage to the max health of the critter, attenuated by the damage modifier. + part_dmg = std::min( ( critter_health * 1.0f ) / dmg_mod, part_dmg ); + + add_msg( m_debug, "Critter collision! %1s was hit by %2s", critter->disp_name(), name ); + add_msg( m_debug, "Vehicle of %.2f Kg at %2.f vmph impacted Critter of %.2f Kg at %.2f vmph", mass, + vel1, mass2, vel2 ); + add_msg( m_debug, + "Vehicle received impulse of %.2f Nm dealing %.2f damage of maximum %.2f, Critter received impulse of %.2f Nm dealing %.2f damage. ", + impulse_veh, part_dmg, impulse_obj, obj_dmg ); + //attempt to recover unspent collision energy + // Remember that the impulse from a collision is technically negative, reducing speed. + impulse_veh = damage_to_impulse( part_dmg ); + if( ( vel1 - ( impulse_veh / mass ) ) < vel1 ) { + vel1_a = ( vel1 - ( impulse_veh / mass ) ); + add_msg( m_debug, "Post collision velocity recovered to %.2f m/s", vel1_a ); + } + } // Don't fling if vertical - critter got smashed into the ground @@ -780,7 +862,7 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, } if( critter == nullptr || !critter->is_hallucination() ) { - coll_velocity = vel1_a * ( smashed ? 100 : 90 ); + coll_velocity = mps_to_vmiph( vel1_a * ( smashed ? 1 : 0.9 ) ); } // Stop processing when sign inverts, not when we reach 0 } while( !smashed && sgn( coll_velocity ) == vel_sign ); @@ -844,7 +926,7 @@ veh_collision vehicle::part_collision( int part, const tripoint &p, } } - ret.imp = part_dmg; + ret.imp = impulse_to_damage( impulse_veh ); return ret; } @@ -1256,13 +1338,14 @@ rl_vec2d vehicle::dir_vec() const { return angle_to_vec( turn_dir ); } - +// Takes delta_v in m/s, returns collision factor. Ranges from 1 at 0m/s to 0.3 at approx ~60mph. +// Changed from e min of 0.1 as this is a nearly perfectly plastic collision, which is not common outside of vehicles with engineered crumple zones. Cata vehicles dont have crumple zones. float get_collision_factor( const float delta_v ) { - if( std::abs( delta_v ) <= 31 ) { - return ( 1 - ( 0.9 * std::abs( delta_v ) ) / 31 ); + if( std::abs( delta_v ) <= 26.8224 ) { + return ( 1 - ( 0.7 * std::abs( delta_v ) ) / 26.8224 ); } else { - return 0.1; + return 0.3; } }