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 7 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"
}
]
87 changes: 65 additions & 22 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,27 @@
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();
float E1_before = 0.5 * m1 * std::pow( ( 0.0044704f * velo_veh1.magnitude() ), 2.0f );
float E2_before = 0.5 * m2 * std::pow( ( 0.0044704f * velo_veh2.magnitude() ), 2.0f );

// Collision_axis
point cof1 = veh .rotated_center_of_mass();
Expand All @@ -878,27 +903,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 = 0.0044704f * collision_axis_y.dot_product( velo_veh1 );
Retagin marked this conversation as resolved.
Show resolved Hide resolved
float vel1_x = 0.0044704f * 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 = 0.0044704f * collision_axis_y.dot_product( velo_veh2 );
float vel2_x = 0.0044704f * 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 ) / 0.0044704f;
Retagin marked this conversation as resolved.
Show resolved Hide resolved
rl_vec2d final2 = ( collision_axis_y * vel2_y_a + collision_axis_x * vel2_x_a ) / 0.0044704f;

veh.move.init( final1.as_point() );
if( final1.dot_product( veh.face_vec() ) < 0 ) {
Expand Down Expand Up @@ -926,20 +954,35 @@
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
float E1_after = 0.5 * m1 * ( velocity_constant * velocity_constant * final1.dot_product(
final1 ) );
float E2_after = 0.5 * m2 * ( velocity_constant * velocity_constant * final1.dot_product(
final1 ) );
float d_E = E1_before + E2_before - E1_after -

Check warning on line 961 in src/map.cpp

View workflow job for this annotation

GitHub Actions / GCC 12, Ubuntu, Curses

unused variable ‘d_E’ [-Wunused-variable]

Check warning on line 961 in src/map.cpp

View workflow job for this annotation

GitHub Actions / GCC 12, Ubuntu, Tiles, Sound, Lua, CMake, Languages

unused variable ‘d_E’ [-Wunused-variable]

Check warning on line 961 in src/map.cpp

View workflow job for this annotation

GitHub Actions / GCC 12, Ubuntu, Tiles, Sound, Lua

unused variable ‘d_E’ [-Wunused-variable]

Check warning on line 961 in src/map.cpp

View workflow job for this annotation

GitHub Actions / build

unused variable 'd_E' [clang-diagnostic-unused-variable]
Retagin marked this conversation as resolved.
Show resolved Hide resolved
E2_after; //Lost energy at collision -> deformation energy

// 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 +6361,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 6364 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 +6513,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 6516 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 +6952,7 @@
}

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

Check warning on line 6955 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 +9076,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 9079 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 +9085,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 9088 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 +9213,14 @@

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

Check warning on line 9216 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 9223 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
33 changes: 27 additions & 6 deletions src/vehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6644,24 +6644,45 @@ 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" ) ) {
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_IMMUNE" ) ) {
continue;
}
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