Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(content,balance): Vehicle collision damage logic update, new vehicle part flags #5926

Merged
merged 14 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/json/vehicleparts/battery.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"using": [ [ "soldering_standard", 5 ], [ "vehicle_repair_electronics", 1 ], [ "plastics", 1 ] ]
}
},
"flags": [ "FOLDABLE" ]
"flags": [ "FOLDABLE", "SHOCK_RESISTANT" ]
},
{
"id": "battery_motorbike",
Expand Down
12 changes: 6 additions & 6 deletions data/json/vehicleparts/vehicle_parts.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
},
Expand Down Expand Up @@ -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 }
},
Expand Down Expand Up @@ -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 ] ] }
Expand Down Expand Up @@ -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 ] } ]
},
{
Expand Down Expand Up @@ -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 ] } ]
},
{
Expand Down Expand Up @@ -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 } ]
},
{
Expand Down
10 changes: 10 additions & 0 deletions data/json/vehicleparts/vp_flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
79 changes: 55 additions & 24 deletions src/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@

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<item> nulitems( new
Expand Down Expand Up @@ -297,6 +300,7 @@

// Vehicle functions


VehicleList map::get_vehicles()
{
if( last_full_vehicle_list_dirty ) {
Expand Down Expand Up @@ -682,7 +686,19 @@
} 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<int>( 0, shock_min - coll_part_bash_resist );
shock_max = std::max<int>( 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 );
}
}
}

Expand Down Expand Up @@ -849,18 +865,23 @@
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
Retagin marked this conversation as resolved.
Show resolved Hide resolved
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();
Expand All @@ -878,27 +899,30 @@
// 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
float vel1_x_a = vel1_x;
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 ) {
Expand All @@ -925,21 +949,28 @@
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 ) {
Expand Down Expand Up @@ -6318,7 +6349,7 @@
} else if( curr_furn.movecost < 0 ) {
sym = '.';
tercol = curr_furn.color();
} else if( ( veh = veh_at_internal( p, part_below ) ) != nullptr ) {

Check warning on line 6352 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build

an assignment within an 'if' condition is bug-prone [bugprone-assignment-in-if-condition]
const int roof = veh->roof_at_part( part_below );
const int displayed_part = roof >= 0 ? roof : part_below;
sym = special_symbol( veh->face.dir_symbol( veh->part_sym( displayed_part, true ) ) );
Expand Down Expand Up @@ -6470,7 +6501,7 @@
const point a( std::abs( loc1.x - loc2.x ) * 2, std::abs( loc1.y - loc2.y ) * 2 );
int offset = std::min( a.x, a.y ) - ( std::max( a.x, a.y ) / 2 );
tripoint obstaclepos;
bresenham( loc2, loc1, offset, 0, [&obstaclepos]( const tripoint & new_point ) {

Check warning on line 6504 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build

1st argument 'loc2' (passed to 'loc1') looks like it might be swapped with the 2nd, 'loc1' (passed to 'loc2') [readability-suspicious-call-argument]
// Only adjacent tile between you and enemy is checked for cover.
obstaclepos = new_point;
return false;
Expand Down Expand Up @@ -6909,7 +6940,7 @@
}

template<int SIZE, int MULTIPLIER>
void shift_bitset_cache( std::bitset<SIZE *SIZE> &cache, point s )

Check warning on line 6943 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build

performing an implicit widening conversion to type 'size_t' (aka 'unsigned long') of a multiplication performed in type 'int' [bugprone-implicit-widening-of-multiplication-result]
{
// sx shifts by MULTIPLIER rows, sy shifts by MULTIPLIER columns.
int shift_amount = s.x * MULTIPLIER + s.y * SIZE * MULTIPLIER;
Expand Down Expand Up @@ -9033,7 +9064,7 @@
const std::string &flag )
{
// Some stupid code to get to the corner
const point omt_diff = ( ms_to_omt_copy( getabs( point( ( p.x + SEEX ),

Check warning on line 9067 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build

Construction of 'point' can be simplified using overloaded arithmetic operators. [cata-use-point-arithmetic]
( p.y + SEEY ) ) ) ) ) - ( ms_to_omt_copy( getabs( p.xy() ) ) );
const point omt_p = omt_to_ms_copy( ( ms_to_omt_copy( p.xy() ) ) ) ;
const tripoint omt_o = tripoint( omt_p.x + ( 1 - omt_diff.x ) * SEEX,
Expand All @@ -9042,7 +9073,7 @@

std::vector<tripoint> furn_locs;
for( const auto &furn_loc : points_in_rectangle( omt_o,
tripoint( omt_o.x + 2 * SEEX - 1, omt_o.y + 2 * SEEY - 1, p.z ) ) ) {

Check warning on line 9076 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build

Construction of 'tripoint' can be simplified using overloaded arithmetic operators. [cata-use-point-arithmetic]
if( has_flag_furn( flag, furn_loc ) ) {
furn_locs.push_back( furn_loc );
}
Expand Down Expand Up @@ -9170,14 +9201,14 @@

bool map::check_seen_cache( const tripoint &p ) const
{
std::bitset<MAPSIZE_X *MAPSIZE_Y> &memory_seen_cache =

Check warning on line 9204 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build

performing an implicit widening conversion to type 'size_t' (aka 'unsigned long') of a multiplication performed in type 'int' [bugprone-implicit-widening-of-multiplication-result]
get_cache( p.z ).map_memory_seen_cache;
return !memory_seen_cache[ static_cast<size_t>( p.x + p.y * MAPSIZE_Y ) ];
}

bool map::check_and_set_seen_cache( const tripoint &p ) const
{
std::bitset<MAPSIZE_X *MAPSIZE_Y> &memory_seen_cache =

Check warning on line 9211 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build

performing an implicit widening conversion to type 'size_t' (aka 'unsigned long') of a multiplication performed in type 'int' [bugprone-implicit-widening-of-multiplication-result]
get_cache( p.z ).map_memory_seen_cache;
if( !memory_seen_cache[ static_cast<size_t>( p.x + p.y * MAPSIZE_Y ) ] ) {
memory_seen_cache.set( static_cast<size_t>( p.x + p.y * MAPSIZE_Y ) );
Expand Down
34 changes: 28 additions & 6 deletions src/vehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Retagin marked this conversation as resolved.
Show resolved Hide resolved
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 );
}

}
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/vehicle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 (+/-)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down
Loading
Loading