Skip to content
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
18 changes: 18 additions & 0 deletions src/rng.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ int djb2_hash( const unsigned char *input )
return hash;
}

std::vector<int> rng_sequence( size_t count, int lo, int hi, int seed )
{
if( lo > hi ) {
std::swap( lo, hi );
}
std::vector<int> result;
result.reserve( count );

// NOLINTNEXTLINE(cata-determinism)
cata_default_random_engine eng( seed );
std::uniform_int_distribution<int> rng_int_dist;
const std::uniform_int_distribution<int>::param_type param( lo, hi );
for( size_t i = 0; i < count; i++ ) {
result.push_back( rng_int_dist( eng, param ) );
}
return result;
}

double rng_normal( double lo, double hi )
{
if( lo > hi ) {
Expand Down
9 changes: 9 additions & 0 deletions src/rng.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ inline int roll_remainder( float value )

int djb2_hash( const unsigned char *input );

// Generates a deterministic sequence of uniform ints.
// Note that this doesn't use or modify the global rng state but uses the seed given as parameter.
// @param count length of sequence to generate
// @param lo minimum value in sequence
// @param hi maximum value in sequence
// @param seed seed to use
// @returns deterministic vector of uniform ints
std::vector<int> rng_sequence( size_t count, int lo, int hi, int seed = 42 );

double rng_normal( double lo, double hi );

inline double rng_normal( double hi )
Expand Down
36 changes: 32 additions & 4 deletions src/vehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7119,17 +7119,30 @@ item vehicle::get_folded_item() const
} catch( const JsonError &e ) {
debugmsg( "Error storing vehicle: %s", e.c_str() );
}
const units::volume folded_volume = std::accumulate( parts.cbegin(), parts.cend(), 0_ml,
[]( const units::volume v, const vehicle_part & vp ) {
return v + ( vp.removed ? 0_ml : vp.info().folded_volume );
} );

units::volume folded_volume = 0_ml;
double sum_of_damage = 0;
int num_of_parts = 0;
for( const vehicle_part &vp : parts ) {
if( vp.removed ) {
continue;
}
folded_volume += vp.info().folded_volume;
sum_of_damage += vp.damage_percent();
num_of_parts++;
}

// snapshot average damage of parts into both item's hp and item variable
const int avg_part_damage = static_cast<int>( sum_of_damage / num_of_parts * folded.max_damage() );

folded.set_var( "tracking", tracking_on ? 1 : 0 );
folded.set_var( "weight", to_milligram( total_mass() ) );
folded.set_var( "volume", folded_volume / units::legacy_volume_factor );
folded.set_var( "name", string_format( _( "folded %s" ), name ) );
folded.set_var( "vehicle_name", name );
folded.set_var( "unfolding_time", to_moves<int>( unfolding_time() ) );
folded.set_var( "avg_part_damage", avg_part_damage );
folded.set_damage( avg_part_damage );
// TODO: a better description?
std::string desc = string_format( _( "A folded %s." ), name )
.append( "\n\n" )
Expand All @@ -7153,6 +7166,21 @@ bool vehicle::restore_folded_parts( const item &it )
debugmsg( "Error restoring folded vehicle parts: %s", e.c_str() );
return false;
}

// item should have snapshot of average part damage in item var. take difference of current
// item's damage and snapshotted damage, then randomly apply to parts in chunks to roughly match.
constexpr double damage_chunk = 0.25;
const double damage_diff = it.damage() - static_cast<int>( it.get_var( "avg_part_damage", 0.0 ) );
const int count = damage_diff / it.max_damage() * real_parts().size() / damage_chunk;
const int seed = static_cast<int>( damage_diff );
for( int part_idx : rng_sequence( count, 0, parts.size() - 1, seed ) ) {
vehicle_part &pt = parts[part_idx];
if( pt.removed || pt.is_fake ) {
continue;
}
pt.base.mod_damage( damage_chunk * pt.base.max_damage() );
}

refresh();
face.init( 0_degrees );
turn_dir = 0_degrees;
Expand Down
6 changes: 3 additions & 3 deletions tests/ranged_balance_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,9 +582,9 @@ std::map<T, float> hit_distribution( const targeting_graph<T, W> &graph,
for( int i = 0; i < iters; ++i ) {
typename std::map<T, float>::iterator it;
if( guess ) {
it = hits.emplace( graph.select( 0.0, 1.0, *guess ), 0 ).first;
it = hits.emplace( graph.select( 0.0, 1.0, *guess ), 0.f ).first;
} else {
it = hits.emplace( graph.select( 0.0, 1.0, rng_float( 0, 1 ) ), 0 ).first;
it = hits.emplace( graph.select( 0.0, 1.0, rng_float( 0, 1 ) ), 0.f ).first;
}
++it->second;
}
Expand Down Expand Up @@ -1160,7 +1160,7 @@ TEST_CASE( "Default_anatomy_body_part_hit_chances", "[targeting_graph][anatomy][
const int total_hits = 1000000;
for( int i = 0; i < total_hits; ++i ) {
auto it = hits.emplace( tested->select_body_part_projectile_attack( 0, 1, rng_float( 0, 1 ) ),
0 ).first;
0.f ).first;
++it->second;
}

Expand Down
123 changes: 123 additions & 0 deletions tests/vehicle_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,129 @@ TEST_CASE( "Unfolding vehicle parts and testing degradation", "[item][degradatio
clear_vehicles( &get_map() );
}

struct folded_item_damage_preset {
itype_id folded_vehicle_item;
int item_damage_first_fold;
int item_damage_second_fold;
int part_damage_second_unfold; // sum of damage over all parts
int part_damage_third_unfold; // sum of damage over all parts
};

static void check_folded_item_to_parts_damage_transfer( const folded_item_damage_preset &preset )
{
CAPTURE( preset.folded_vehicle_item.str(),
preset.item_damage_first_fold, preset.item_damage_second_fold,
preset.part_damage_second_unfold, preset.part_damage_third_unfold );

// exact damage numbers are checked against, there should be almost no rng,
// only the part damage is pseudo-random spread, while total damage should
// round trip well in integers
clear_avatar();
clear_map();

map &m = get_map();
Character &u = get_player_character();

u.worn.wear_item( u, item( "debug_backpack" ), false, false );

item veh_item( preset.folded_vehicle_item );

// unfold fresh item factory item
complete_activity( u, vehicle_unfolding_activity_actor( veh_item ) );

optional_vpart_position ovp = m.veh_at( u.get_location() );
REQUIRE( ovp.has_value() );

// don't actually need point_north but damage_all filters out direct damage
// do some damage so it is transferred when folding
ovp->vehicle().damage_all( 100, 100, damage_type::PURE, ovp->mount() + point_north );

// fold vehicle into an item
complete_activity( u, vehicle_folding_activity_actor( ovp->vehicle() ) );

ovp = m.veh_at( u.get_location() );
REQUIRE( !ovp.has_value() );

// copy the player-folded vehicle item and delete it from the map
map_stack map_items = m.i_at( u.pos_bub() );
REQUIRE( map_items.size() == 1 );
item player_folded_veh = map_items.only_item();
map_items.clear();

// check the damage was transferred from parts to folded item
CHECK( player_folded_veh.damage() == preset.item_damage_first_fold );
CHECK( player_folded_veh.get_var( "avg_part_damage", 0.0 ) == preset.item_damage_first_fold );

complete_activity( u, vehicle_unfolding_activity_actor( player_folded_veh ) );

ovp = m.veh_at( u.get_location() );
REQUIRE( ovp.has_value() );

int part_damage_before = 0;
for( const vpart_reference &vpr : ovp->vehicle().get_all_parts() ) {
part_damage_before += vpr.part().damage();
}

// check damage correctly transferred from item to vehicle parts
CHECK( part_damage_before == preset.part_damage_second_unfold );

complete_activity( u, vehicle_folding_activity_actor( ovp->vehicle() ) );

ovp = m.veh_at( u.get_location() );
REQUIRE( !ovp.has_value() );
map_items = m.i_at( u.pos_bub() );
REQUIRE( map_items.size() == 1 );
player_folded_veh = map_items.only_item();
map_items.clear();

// check that we don't add extra item damage after folding
CHECK( player_folded_veh.damage() == preset.item_damage_first_fold );
CHECK( player_folded_veh.get_var( "avg_part_damage", 0.0 ) == preset.item_damage_first_fold );

// add some more damage to the item
player_folded_veh.mod_damage( 300 );

// unfold and check extra damage gets distributed into vehicleparts
complete_activity( u, vehicle_unfolding_activity_actor( player_folded_veh ) );
ovp = m.veh_at( u.get_location() );
REQUIRE( ovp.has_value() );

// add up damage on all parts
int part_damage_after = 0;
for( const vpart_reference &vpr : ovp->vehicle().get_all_parts() ) {
part_damage_after += vpr.part().damage();
}

{
INFO( "Checking extra item damage gets distributed to vehicle parts." );
CHECK( part_damage_after > part_damage_before );
CHECK( part_damage_after == preset.part_damage_third_unfold );
}

complete_activity( u, vehicle_folding_activity_actor( ovp->vehicle() ) );

REQUIRE( !m.veh_at( u.get_location() ) );
map_items = m.i_at( u.pos_bub() );
REQUIRE( map_items.size() == 1 );
player_folded_veh = map_items.only_item();
map_items.clear();

CHECK( player_folded_veh.damage() == preset.item_damage_second_fold );
CHECK( player_folded_veh.get_var( "avg_part_damage", 0.0 ) == preset.item_damage_second_fold );
}

TEST_CASE( "Check folded item damage transfers to parts and vice versa", "[item][vehicle]" )
{
std::vector<folded_item_damage_preset> presets {
{ itype_folded_wheelchair_generic, 2111, 2277, 12666, 13666 },
{ itype_folded_bicycle, 1689, 1961, 18582, 21582 },
};

for( const folded_item_damage_preset &preset : presets ) {
check_folded_item_to_parts_damage_transfer( preset );
}
}

// Basically a copy of vehicle::connect() that uses an arbitrary cord type
static void connect_power_line( const tripoint &src_pos, const tripoint &dst_pos,
const itype_id &itm )
Expand Down