double convert_weight( const units::mass &weight ) { double ret = to_gram( weight ); if( get_option<std::string>( "USE_METRIC_WEIGHTS" ) == "kg" ) { ret /= 1000; } else { ret /= 453.6; } return ret; }
double funnel_charges_per_turn( const double surface_area_mm2, const double rain_depth_mm_per_hour ) { // 1mm rain on 1m^2 == 1 liter water == 1000ml // 1 liter == 4 volume // 1 volume == 250ml: containers // 1 volume == 200ml: water // How many charges of water can we collect in a turn (usually <1.0)? if( rain_depth_mm_per_hour == 0.0 ) { return 0.0; } // Calculate once, because that part is expensive static const item water("water", 0); static const double charge_ml = ( double ) to_gram( water.weight() ) / water.charges; // 250ml const double vol_mm3_per_hour = surface_area_mm2 * rain_depth_mm_per_hour; const double vol_mm3_per_turn = vol_mm3_per_hour / HOURS(1); const double ml_to_mm3 = 1000; const double charges_per_turn = vol_mm3_per_turn / (charge_ml * ml_to_mm3); return charges_per_turn; }
int mtype::get_meat_chunks_count() const { const float ch = to_gram( weight ) * ( 0.40f - 0.02f * log10f( to_gram( weight ) ) ); const itype *chunk = item::find_type( get_meat_itype() ); return static_cast<int>( ch / to_gram( chunk->weight ) ); }
void mdeath::splatter( monster &z ) { const bool gibbable = !z.type->has_flag( MF_NOGIB ); const int max_hp = std::max( z.get_hp_max(), 1 ); const float overflow_damage = std::max( -z.get_hp(), 0 ); const float corpse_damage = 2.5 * overflow_damage / max_hp; bool pulverized = corpse_damage > 5 && overflow_damage > z.get_hp_max(); // make sure that full splatter happens when this is a set death function, not part of normal for( const auto &deathfunction : z.type->dies ) { if( deathfunction == mdeath::splatter ) { pulverized = true; } } const field_id type_blood = z.bloodType(); const field_id type_gib = z.gibType(); if( gibbable ) { const auto area = g->m.points_in_radius( z.pos(), 1 ); int number_of_gibs = std::min( std::floor( corpse_damage ) - 1, 1 + max_hp / 5.0f ); if( pulverized && z.type->size >= MS_MEDIUM ) { number_of_gibs += rng( 1, 6 ); sfx::play_variant_sound( "mon_death", "zombie_gibbed", sfx::get_heard_volume( z.pos() ) ); } for( int i = 0; i < number_of_gibs; ++i ) { g->m.add_splatter( type_gib, random_entry( area ), rng( 1, i + 1 ) ); g->m.add_splatter( type_blood, random_entry( area ) ); } } // 1% of the weight of the monster is the base, with overflow damage as a multiplier int gibbed_weight = rng( 0, round( to_gram( z.get_weight() ) / 100 * ( overflow_damage / max_hp + 1 ) ) ); // limit gibbing to 15% gibbed_weight = std::min( gibbed_weight, to_gram( z.get_weight() ) * 15 / 100 ); if( pulverized && gibbable ) { float overflow_ratio = overflow_damage / max_hp + 1; int gib_distance = round( rng( 2, 4 ) ); for( const auto &entry : *z.type->harvest ) { // only flesh and bones survive. if( entry.type == "flesh" || entry.type == "bone" ) { // the larger the overflow damage, the less you get const int chunk_amt = entry.mass_ratio / overflow_ratio / 10 * to_gram( z.get_weight() ) / to_gram( ( item::find_type( entry.drop ) )->weight ); scatter_chunks( entry.drop, chunk_amt, z, gib_distance, chunk_amt / ( gib_distance - 1 ) ); gibbed_weight -= entry.mass_ratio / overflow_ratio / 20 * to_gram( z.get_weight() ); } } if( gibbed_weight > 0 ) { scatter_chunks( "ruined_chunks", gibbed_weight / to_gram( ( item::find_type( "ruined_chunks" ) ) ->weight ), z, gib_distance, gibbed_weight / to_gram( ( item::find_type( "ruined_chunks" ) )->weight ) / ( gib_distance + 1 ) ); } // add corpse with gib flag item corpse = item::make_corpse( z.type->id, calendar::turn, z.unique_name ); // Set corpse to damage that aligns with being pulped corpse.set_damage( 4000 ); corpse.set_flag( "GIBBED" ); if( z.has_effect( effect_no_ammo ) ) { corpse.set_var( "no_ammo", "no_ammo" ); } g->m.add_item_or_charges( z.pos(), corpse ); } }
// Algorithm goes as follows: // Clear map // Spawn a vehicle // Set its fuel up to some percentage - remember exact fuel counts that were set here // Drive it for a while, always moving it back to start point every turn to avoid it going off the bubble // When moving back, record the sum of the tiles moved so far // Repeat that for a set number of turns or until all fuel is drained // Compare saved percentage (set before) to current percentage // Rescale the recorded number of tiles based on fuel percentage left // (ie. 0% fuel left means no scaling, 50% fuel left means double the effective distance) // Return the rescaled number long test_efficiency( const vproto_id &veh_id, int &expected_mass, const ter_id &terrain, const int reset_velocity_turn, const long target_distance, const bool smooth_stops = false, const bool test_mass = true ) { long min_dist = target_distance * 0.99; long max_dist = target_distance * 1.01; clear_game( terrain ); const tripoint map_starting_point( 60, 60, 0 ); vehicle *veh_ptr = g->m.add_vehicle( veh_id, map_starting_point, -90, 0, 0 ); REQUIRE( veh_ptr != nullptr ); if( veh_ptr == nullptr ) { return 0; } vehicle &veh = *veh_ptr; // Remove all items from cargo to normalize weight. for( const vpart_reference vp : veh.get_all_parts() ) { while( veh.remove_item( vp.part_index(), 0 ) ); vp.part().ammo_consume( vp.part().ammo_remaining(), vp.pos() ); } for( const vpart_reference vp : veh.get_avail_parts( "OPENABLE" ) ) { veh.close( vp.part_index() ); } veh.refresh_insides(); if( test_mass ) { CHECK( to_gram( veh.total_mass() ) == expected_mass ); } expected_mass = to_gram( veh.total_mass() ); veh.check_falling_or_floating(); REQUIRE( !veh.is_in_water() ); const auto &starting_fuel = set_vehicle_fuel( veh, fuel_level ); // This is ugly, but improves accuracy: compare the result of fuel approx function // rather than the amount of fuel we actually requested const float starting_fuel_per = fuel_percentage_left( veh, starting_fuel ); REQUIRE( std::abs( starting_fuel_per - 1.0f ) < 0.001f ); const tripoint starting_point = veh.global_pos3(); veh.tags.insert( "IN_CONTROL_OVERRIDE" ); veh.engine_on = true; const int target_velocity = std::min( 70 * 100, veh.safe_ground_velocity( false ) ); veh.cruise_velocity = target_velocity; // If we aren't testing repeated cold starts, start the vehicle at cruising velocity. // Otherwise changing the amount of fuel in the tank perturbs the test results. if( reset_velocity_turn == -1 ) { veh.velocity = target_velocity; } int reset_counter = 0; long tiles_travelled = 0; int cycles_left = cycle_limit; bool accelerating = true; CHECK( veh.safe_velocity() > 0 ); while( veh.engine_on && veh.safe_velocity() > 0 && cycles_left > 0 ) { cycles_left--; g->m.vehmove(); veh.idle( true ); // If the vehicle starts skidding, the effects become random and test is RUINED REQUIRE( !veh.skidding ); for( const tripoint &pos : veh.get_points() ) { REQUIRE( g->m.ter( pos ) ); } // How much it moved tiles_travelled += square_dist( starting_point, veh.global_pos3() ); // Bring it back to starting point to prevent it from leaving the map const tripoint displacement = starting_point - veh.global_pos3(); tripoint veh_pos = veh.global_pos3(); g->m.displace_vehicle( veh_pos, displacement ); if( reset_velocity_turn < 0 ) { continue; } reset_counter++; if( reset_counter > reset_velocity_turn ) { if( smooth_stops ) { accelerating = !accelerating; veh.cruise_velocity = accelerating ? target_velocity : 0; } else { veh.velocity = 0; veh.last_turn = 0; veh.of_turn_carry = 0; } reset_counter = 0; } } float fuel_left = fuel_percentage_left( veh, starting_fuel ); REQUIRE( starting_fuel_per - fuel_left > 0.0001f ); const float fuel_percentage_used = fuel_level * ( starting_fuel_per - fuel_left ); long adjusted_tiles_travelled = tiles_travelled / fuel_percentage_used; if( target_distance >= 0 ) { CHECK( adjusted_tiles_travelled >= min_dist ); CHECK( adjusted_tiles_travelled <= max_dist ); } return adjusted_tiles_travelled; }
bool game::dump_stats( const std::string &what, dump_mode mode, const std::vector<std::string> &opts ) { try { loading_ui ui( false ); load_core_data( ui ); load_packs( _( "Loading content packs" ), { mod_id( "dda" ) }, ui ); DynamicDataLoader::get_instance().finalize_loaded_data( ui ); } catch( const std::exception &err ) { std::cerr << "Error loading data from json: " << err.what() << std::endl; return false; } std::vector<std::string> header; std::vector<std::vector<std::string>> rows; int scol = 0; // sorting column std::map<std::string, standard_npc> test_npcs; test_npcs[ "S1" ] = standard_npc( "S1", { "gloves_survivor", "mask_lsurvivor" }, 4, 8, 10, 8, 10 /* DEX 10, PER 10 */ ); test_npcs[ "S2" ] = standard_npc( "S2", { "gloves_fingerless", "sunglasses" }, 4, 8, 8, 8, 10 /* PER 10 */ ); test_npcs[ "S3" ] = standard_npc( "S3", { "gloves_plate", "helmet_plate" }, 4, 10, 8, 8, 8 /* STAT 10 */ ); test_npcs[ "S4" ] = standard_npc( "S4", {}, 0, 8, 10, 8, 10 /* DEX 10, PER 10 */ ); test_npcs[ "S5" ] = standard_npc( "S5", {}, 4, 8, 10, 8, 10 /* DEX 10, PER 10 */ ); test_npcs[ "S6" ] = standard_npc( "S6", { "gloves_hsurvivor", "mask_hsurvivor" }, 4, 8, 10, 8, 10 /* DEX 10, PER 10 */ ); std::map<std::string, item> test_items; test_items[ "G1" ] = item( "glock_19" ).ammo_set( "9mm" ); test_items[ "G2" ] = item( "hk_mp5" ).ammo_set( "9mm" ); test_items[ "G3" ] = item( "ar15" ).ammo_set( "223" ); test_items[ "G4" ] = item( "remington_700" ).ammo_set( "270" ); test_items[ "G4" ].emplace_back( "rifle_scope" ); if( what == "AMMO" ) { header = { "Name", "Ammo", "Volume", "Weight", "Stack", "Range", "Dispersion", "Recoil", "Damage", "Pierce" }; auto dump = [&rows]( const item & obj ) { // a common task is comparing ammo by type so ammo has multiple repeat the entry for( const auto &e : obj.type->ammo->type ) { std::vector<std::string> r; r.push_back( obj.tname( 1, false ) ); r.push_back( e.str() ); r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.type->stack_size ) ); r.push_back( to_string( obj.type->ammo->range ) ); r.push_back( to_string( obj.type->ammo->dispersion ) ); r.push_back( to_string( obj.type->ammo->recoil ) ); damage_instance damage = obj.type->ammo->damage; r.push_back( to_string( damage.total_damage() ) ); r.push_back( to_string( damage.empty() ? 0 : ( *damage.begin() ).res_pen ) ); rows.push_back( r ); } }; for( const itype *e : item_controller->all() ) { if( e->ammo ) { dump( item( e, calendar::turn, item::solitary_tag {} ) ); } } } else if( what == "ARMOR" ) { header = { "Name", "Encumber (fit)", "Warmth", "Weight", "Storage", "Coverage", "Bash", "Cut", "Acid", "Fire" }; auto dump = [&rows]( const item & obj ) { std::vector<std::string> r; r.push_back( obj.tname( 1, false ) ); r.push_back( to_string( obj.get_encumber() ) ); r.push_back( to_string( obj.get_warmth() ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.get_storage() / units::legacy_volume_factor ) ); r.push_back( to_string( obj.get_coverage() ) ); r.push_back( to_string( obj.bash_resist() ) ); r.push_back( to_string( obj.cut_resist() ) ); r.push_back( to_string( obj.acid_resist() ) ); r.push_back( to_string( obj.fire_resist() ) ); rows.push_back( r ); }; body_part bp = opts.empty() ? num_bp : get_body_part_token( opts.front() ); for( const itype *e : item_controller->all() ) { if( e->armor ) { item obj( e ); if( bp == num_bp || obj.covers( bp ) ) { if( obj.has_flag( "VARSIZE" ) ) { obj.item_tags.insert( "FIT" ); } dump( obj ); } } } } else if( what == "EDIBLE" ) { header = { "Name", "Volume", "Weight", "Stack", "Calories", "Quench", "Healthy" }; for( const auto &v : vitamin::all() ) { header.push_back( v.second.name() ); } auto dump = [&rows]( const item & obj ) { std::vector<std::string> r; r.push_back( obj.tname( false ) ); r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.type->stack_size ) ); r.push_back( to_string( obj.type->comestible->get_calories() ) ); r.push_back( to_string( obj.type->comestible->quench ) ); r.push_back( to_string( obj.type->comestible->healthy ) ); auto vits = g->u.vitamins_from( obj ); for( const auto &v : vitamin::all() ) { r.push_back( to_string( vits[ v.first ] ) ); } rows.push_back( r ); }; for( const itype *e : item_controller->all() ) { item food( e, calendar::turn, item::solitary_tag {} ); if( food.is_food() && g->u.can_eat( food ).success() ) { dump( food ); } } } else if( what == "GUN" ) { header = { "Name", "Ammo", "Volume", "Weight", "Capacity", "Range", "Dispersion", "Effective recoil", "Damage", "Pierce", "Aim time", "Effective range", "Snapshot range", "Max range" }; std::set<std::string> locations; for( const itype *e : item_controller->all() ) { if( e->gun ) { std::transform( e->gun->valid_mod_locations.begin(), e->gun->valid_mod_locations.end(), std::inserter( locations, locations.begin() ), []( const std::pair<gunmod_location, int> &q ) { return q.first.name(); } ); } } for( const auto &e : locations ) { header.push_back( e ); } auto dump = [&rows, &locations]( const standard_npc & who, const item & obj ) { std::vector<std::string> r; r.push_back( obj.tname( 1, false ) ); r.push_back( obj.ammo_type() ? obj.ammo_type().str() : "" ); r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.ammo_capacity() ) ); r.push_back( to_string( obj.gun_range() ) ); r.push_back( to_string( obj.gun_dispersion() ) ); r.push_back( to_string( obj.gun_recoil( who ) ) ); damage_instance damage = obj.gun_damage(); r.push_back( to_string( damage.total_damage() ) ); r.push_back( to_string( damage.empty() ? 0 : ( *damage.begin() ).res_pen ) ); r.push_back( to_string( who.gun_engagement_moves( obj ) ) ); for( const auto &e : locations ) { const auto &vml = obj.type->gun->valid_mod_locations; const auto iter = vml.find( e ); r.push_back( to_string( iter != vml.end() ? iter->second : 0 ) ); } rows.push_back( r ); }; for( const itype *e : item_controller->all() ) { if( e->gun ) { item gun( e ); if( !gun.magazine_integral() ) { gun.emplace_back( gun.magazine_default() ); } gun.ammo_set( gun.ammo_type()->default_ammotype(), gun.ammo_capacity() ); dump( test_npcs[ "S1" ], gun ); if( gun.type->gun->barrel_length > 0 ) { gun.emplace_back( "barrel_small" ); dump( test_npcs[ "S1" ], gun ); } } } } else if( what == "RECIPE" ) { // optionally filter recipes to include only those using specified skills recipe_subset dict; for( const auto &r : recipe_dict ) { if( opts.empty() || std::any_of( opts.begin(), opts.end(), [&r]( const std::string & s ) { if( r.second.skill_used == skill_id( s ) && r.second.difficulty > 0 ) { return true; } auto iter = r.second.required_skills.find( skill_id( s ) ); return iter != r.second.required_skills.end() && iter->second > 0; } ) ) { dict.include( &r.second ); } } // only consider skills that are required by at least one recipe std::vector<Skill> sk; std::copy_if( Skill::skills.begin(), Skill::skills.end(), std::back_inserter( sk ), [&dict]( const Skill & s ) { return std::any_of( dict.begin(), dict.end(), [&s]( const recipe * r ) { return r->skill_used == s.ident() || r->required_skills.find( s.ident() ) != r->required_skills.end(); } ); } ); header = { "Result" }; for( const auto &e : sk ) { header.push_back( e.ident().str() ); } for( const recipe *e : dict ) { std::vector<std::string> r; r.push_back( e->result_name() ); for( const auto &s : sk ) { if( e->skill_used == s.ident() ) { r.push_back( to_string( e->difficulty ) ); } else { auto iter = e->required_skills.find( s.ident() ); r.push_back( to_string( iter != e->required_skills.end() ? iter->second : 0 ) ); } } rows.push_back( r ); } } else if( what == "VEHICLE" ) { header = { "Name", "Weight (empty)", "Weight (fueled)", "Max velocity (mph)", "Safe velocity (mph)", "Acceleration (mph/turn)", "Mass coeff %", "Aerodynamics coeff %", "Friction coeff %", "Traction coeff % (grass)" }; auto dump = [&rows]( const vproto_id & obj ) { auto veh_empty = vehicle( obj, 0, 0 ); auto veh_fueled = vehicle( obj, 100, 0 ); std::vector<std::string> r; r.push_back( veh_empty.name ); r.push_back( to_string( to_kilogram( veh_empty.total_mass() ) ) ); r.push_back( to_string( to_kilogram( veh_fueled.total_mass() ) ) ); r.push_back( to_string( veh_fueled.max_velocity() / 100 ) ); r.push_back( to_string( veh_fueled.safe_velocity() / 100 ) ); r.push_back( to_string( veh_fueled.acceleration() / 100 ) ); r.push_back( to_string( ( int )( 100 * veh_fueled.k_mass() ) ) ); r.push_back( to_string( ( int )( 100 * veh_fueled.k_aerodynamics() ) ) ); r.push_back( to_string( ( int )( 100 * veh_fueled.k_friction() ) ) ); r.push_back( to_string( ( int )( 100 * veh_fueled.k_traction( veh_fueled.wheel_area( false ) / 2.0f ) ) ) ); rows.push_back( r ); }; for( auto &e : vehicle_prototype::get_all() ) { dump( e ); } } else if( what == "VPART" ) { header = { "Name", "Location", "Weight", "Size" }; auto dump = [&rows]( const vpart_info & obj ) { std::vector<std::string> r; r.push_back( obj.name() ); r.push_back( obj.location ); r.push_back( to_string( int( ceil( to_gram( item( obj.item ).weight() ) / 1000.0 ) ) ) ); r.push_back( to_string( obj.size / units::legacy_volume_factor ) ); rows.push_back( r ); }; for( const auto &e : vpart_info::all() ) { dump( e.second ); } } else { std::cerr << "unknown argument: " << what << std::endl; return false; } rows.erase( std::remove_if( rows.begin(), rows.end(), []( const std::vector<std::string> &e ) { return e.empty(); } ), rows.end() ); if( scol >= 0 ) { std::sort( rows.begin(), rows.end(), [&scol]( const std::vector<std::string> &lhs, const std::vector<std::string> &rhs ) { return lhs[ scol ] < rhs[ scol ]; } ); } rows.erase( std::unique( rows.begin(), rows.end() ), rows.end() ); switch( mode ) { case dump_mode::TSV: rows.insert( rows.begin(), header ); for( const auto &r : rows ) { std::copy( r.begin(), r.end() - 1, std::ostream_iterator<std::string>( std::cout, "\t" ) ); std::cout << r.back() << "\n"; } break; case dump_mode::HTML: std::cout << "<table>"; std::cout << "<thead>"; std::cout << "<tr>"; for( const auto &col : header ) { std::cout << "<th>" << col << "</th>"; } std::cout << "</tr>"; std::cout << "</thead>"; std::cout << "<tdata>"; for( const auto &r : rows ) { std::cout << "<tr>"; for( const auto &col : r ) { std::cout << "<td>" << col << "</td>"; } std::cout << "</tr>"; } std::cout << "</tdata>"; std::cout << "</table>"; break; } return true; }