diff --git a/data/json/materials.json b/data/json/materials.json index 824b4fc006753..31815edf29f14 100644 --- a/data/json/materials.json +++ b/data/json/materials.json @@ -218,6 +218,7 @@ "fire_resist": 1, "elec_resist": 2, "chip_resist": 8, + "wind_resist": 90, "repaired_with": "bone", "salvaged_into": "skewer_bone", "dmg_adj": [ "scratched", "cut", "cracked", "shattered" ], @@ -360,6 +361,7 @@ "fire_resist": 2, "elec_resist": 2, "chip_resist": 10, + "wind_resist": 90, "repaired_with": "chitin_piece", "salvaged_into": "chitin_piece", "dmg_adj": [ "scratched", "cut", "cracked", "shattered" ], @@ -429,6 +431,7 @@ "fire_resist": 0, "elec_resist": 2, "chip_resist": 6, + "wind_resist": 70, "repaired_with": "cotton_patchwork", "salvaged_into": "cotton_patchwork", "dmg_adj": [ "ripped", "torn", "shredded", "tattered" ], @@ -1077,6 +1080,7 @@ "fire_resist": 2, "elec_resist": 2, "chip_resist": 10, + "wind_resist": 90, "repaired_with": "leather", "salvaged_into": "leather", "dmg_adj": [ "scratched", "cut", "shredded", "tattered" ], @@ -1187,6 +1191,7 @@ "fire_resist": 20, "elec_resist": 4, "chip_resist": 8, + "wind_resist": 90, "repaired_with": "nomex", "salvaged_into": "nomex", "dmg_adj": [ "ripped", "torn", "shredded", "tattered" ], @@ -1325,6 +1330,7 @@ "fire_resist": 1, "elec_resist": 2, "chip_resist": 6, + "wind_resist": 90, "repaired_with": "plastic_chunk", "salvaged_into": "plastic_chunk", "dmg_adj": [ "scratched", "cut", "cracked", "shattered" ], @@ -1955,6 +1961,7 @@ "fire_resist": 0, "elec_resist": 2, "chip_resist": 6, + "wind_resist": 60, "repaired_with": "felt_patch", "salvaged_into": "felt_patch", "dmg_adj": [ "ripped", "torn", "shredded", "tattered" ], diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index dd92cd48dec61..2b1068e57261b 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -842,6 +842,7 @@ When you sort your inventory by category, these are the categories that are disp | `dmg_adj` | Description added to damaged item in ascending severity. | `dmg_adj` | Adjectives used to describe damage states of a material. | `density` | Affects vehicle collision damage, with denser parts having the advantage over less-dense parts. +| `wind_resist` | Percentage 0-100. How effective this material is at stopping wind from getting through. Higher values are better. If none of the materials an item is made of specify a value, a default of 99 is assumed. | `vitamins` | Vitamins in a material. Usually overridden by item specific values. An integer percentage of ideal daily value. | `specific_heat_liquid` | Specific heat of a material when not frozen (J/(g K)). Default 4.186 - water. | `specific_heat_solid` | Specific heat of a material when frozen (J/(g K)). Default 2.108 - water. diff --git a/src/character.cpp b/src/character.cpp index 807ca8504242f..b316527c66bda 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -4824,7 +4824,7 @@ bool Character::in_climate_control() if( w.active && w.is_power_armor() ) { return true; } - if( worn_with_flag( flag_CLIMATE_CONTROL ) ) { + if( w.has_flag( flag_CLIMATE_CONTROL ) ) { return true; } } @@ -4850,41 +4850,42 @@ bool Character::in_climate_control() return regulated_area; } -int Character::get_wind_resistance( const bodypart_id &bp ) const +std::map Character::get_wind_resistance( const std::map > &clothing_map ) const { - int coverage = 0; - float totalExposed = 1.0f; - int totalCoverage = 0; - int penalty = 100; - for( const item &i : worn ) { - if( i.covers( bp ) ) { - if( i.made_of( material_id( "leather" ) ) || i.made_of( material_id( "plastic" ) ) || - i.made_of( material_id( "bone" ) ) || - i.made_of( material_id( "chitin" ) ) || i.made_of( material_id( "nomex" ) ) ) { - penalty = 10; // 90% effective - } else if( i.made_of( material_id( "cotton" ) ) ) { - penalty = 30; - } else if( i.made_of( material_id( "wool" ) ) ) { - penalty = 40; - } else { - penalty = 1; // 99% effective - } - - coverage = std::max( 0, i.get_coverage( bp ) - penalty ); - totalExposed *= ( 1.0 - coverage / 100.0 ); // Coverage is between 0 and 1? - } + std::map ret; + for( const bodypart_id &bp : get_all_body_parts() ) { + ret.emplace( bp, 0 ); } // Your shell provides complete wind protection if you're inside it if( has_active_mutation( trait_SHELL2 ) ) { - totalCoverage = 100; - return totalCoverage; + for( std::pair &this_bp : ret ) { + this_bp.second = 100; + } + return ret; } - totalCoverage = 100 - totalExposed * 100; + for( const std::pair> &on_bp : clothing_map ) { + const bodypart_id &bp = on_bp.first; + + int coverage = 0; + float totalExposed = 1.0f; + int totalCoverage = 0; + int penalty = 100; - return totalCoverage; + for( const item *it : on_bp.second ) { + const item &i = *it; + penalty = 100 - i.wind_resist(); + coverage = std::max( 0, i.get_coverage( bp ) - penalty ); + totalExposed *= ( 1.0 - coverage / 100.0 ); // Coverage is between 0 and 1? + } + + ret[bp] = totalCoverage = 100 - totalExposed * 100; + } + + return ret; } void layer_details::reset() @@ -6785,6 +6786,23 @@ void Character::update_bodytemp() const int mutation_heat_bonus = mutation_heat_high - mutation_heat_low; const int h_radiation = get_heat_radiation( pos(), false ); + + std::map> clothing_map; + for( const bodypart_id &bp : get_all_body_parts() ) { + clothing_map.emplace( bp, std::vector() ); + } + for( const item &it : worn ) { + for( const bodypart_str_id &covered : it.get_covered_body_parts() ) { + clothing_map[covered.id()].emplace_back( &it ); + } + } + + std::map warmth_per_bp = warmth( clothing_map ); + std::map bonus_warmth_per_bp = bonus_item_warmth( clothing_map ); + std::map wind_res_per_bp = get_wind_resistance( clothing_map ); + // We might not use this at all, so leave it empty + // If we do need to use it, we'll initialize it (once) there + std::map fire_armor_per_bp; // Current temperature and converging temperature calculations for( const bodypart_id &bp : get_all_body_parts() ) { @@ -6802,13 +6820,13 @@ void Character::update_bodytemp() get_part_temp_cur( bp ) ); // Produces a smooth curve between 30.0 and 60.0. double homeostasis_adjustment = 30.0 * ( 1.0 + scaled_temperature ); - int clothing_warmth_adjustment = static_cast( homeostasis_adjustment * warmth( bp ) ); - int clothing_warmth_adjusted_bonus = static_cast( homeostasis_adjustment * bonus_item_warmth( - bp ) ); + int clothing_warmth_adjustment = static_cast( homeostasis_adjustment * warmth_per_bp[bp] ); + int clothing_warmth_adjusted_bonus = static_cast( homeostasis_adjustment * + bonus_warmth_per_bp[bp] ); // WINDCHILL - bp_windpower = static_cast( static_cast( bp_windpower ) * ( 1 - get_wind_resistance( - bp ) / 100.0 ) ); + bp_windpower = static_cast( static_cast( bp_windpower ) * + ( 1 - wind_res_per_bp[bp] / 100.0 ) ); // Calculate windchill int windchill = get_local_windchill( player_local_temp, get_local_humidity( weather.humidity, get_weather().weather_id, sheltered ), @@ -6854,7 +6872,13 @@ void Character::update_bodytemp() // BLISTERS : Skin gets blisters from intense heat exposure. // Fire protection protects from blisters. // Heatsinks give near-immunity. - if( blister_count - get_armor_fire( bp ) - ( has_heatsink ? 20 : 0 ) > 0 ) { + if( has_heatsink ) { + blister_count -= 20; + } + if( fire_armor_per_bp.empty() && blister_count > 0 ) { + fire_armor_per_bp = get_armor_fire( clothing_map ); + } + if( blister_count - fire_armor_per_bp[bp] > 0 ) { add_effect( effect_blisters, 1_turns, bp ); if( pyromania ) { add_morale( MORALE_PYROMANIA_NEARFIRE, 10, 10, 1_hours, @@ -6998,7 +7022,7 @@ void Character::update_bodytemp() remove_effect( effect_hot_speed, bp ); } - update_frostbite( bp, bp_windpower ); + update_frostbite( bp, bp_windpower, warmth_per_bp ); // Warn the player if condition worsens if( temp_before > BODYTEMP_FREEZING && temp_after < BODYTEMP_FREEZING ) { @@ -7060,7 +7084,8 @@ void Character::update_bodytemp() } } -void Character::update_frostbite( const bodypart_id &bp, const int FBwindPower ) +void Character::update_frostbite( const bodypart_id &bp, const int FBwindPower, + const std::map &warmth_per_bp ) { // FROSTBITE - only occurs to hands, feet, face /** @@ -7099,7 +7124,7 @@ void Character::update_frostbite( const bodypart_id &bp, const int FBwindPower ) int wetness_percentage = 100 * get_part_wetness_percentage( bp ); // 0 - 100 // Warmth gives a slight buff to temperature resistance // Wetness gives a heavy nerf to temperature resistance - double adjusted_warmth = warmth( bp ) - wetness_percentage; + double adjusted_warmth = warmth_per_bp.at( bp ) - wetness_percentage; int Ftemperature = static_cast( player_local_temp + 0.2 * adjusted_warmth ); int intense = get_effect_int( effect_frostbite, bp ); @@ -8669,6 +8694,7 @@ int Character::get_armor_bullet( bodypart_id bp ) const return get_armor_bullet_base( bp ) + armor_bullet_bonus; } +// TODO: Reduce duplication with below function int Character::get_armor_type( damage_type dt, bodypart_id bp ) const { switch( dt ) { @@ -8707,6 +8733,56 @@ int Character::get_armor_type( damage_type dt, bodypart_id bp ) const return 0; } +std::map Character::get_all_armor_type( damage_type dt, + const std::map> &clothing_map ) const +{ + std::map ret; + for( const bodypart_id &bp : get_all_body_parts() ) { + ret.emplace( bp, 0 ); + } + + for( std::pair &per_bp : ret ) { + const bodypart_id &bp = per_bp.first; + switch( dt ) { + case damage_type::PURE: + case damage_type::BIOLOGICAL: + // Characters cannot resist this + return ret; + /* BASH, CUT, STAB, and BULLET don't benefit from the clothing_map optimization */ + // TODO: Fix that + case damage_type::BASH: + per_bp.second += get_armor_bash( bp ); + break; + case damage_type::CUT: + per_bp.second += get_armor_cut( bp ); + break; + case damage_type::STAB: + per_bp.second += get_armor_cut( bp ) * 0.8f; + break; + case damage_type::BULLET: + per_bp.second += get_armor_bullet( bp ); + break; + case damage_type::ACID: + case damage_type::HEAT: + case damage_type::COLD: + case damage_type::ELECTRIC: { + for( const item *it : clothing_map.at( bp ) ) { + per_bp.second += it->damage_resist( dt ); + } + + per_bp.second += mutation_armor( bp, dt ); + break; + } + case damage_type::NONE: + case damage_type::NUM: + debugmsg( "Invalid damage type: %d", dt ); + return ret; + } + } + + return ret; +} + int Character::get_armor_bash_base( bodypart_id bp ) const { float ret = 0; @@ -10002,9 +10078,10 @@ float Character::bionic_armor_bonus( const bodypart_id &bp, damage_type dt ) con return result; } -int Character::get_armor_fire( const bodypart_id &bp ) const +std::map Character::get_armor_fire( const + std::map> &clothing_map ) const { - return get_armor_type( damage_type::HEAT, bp ); + return get_all_armor_type( damage_type::HEAT, clothing_map ); } void Character::did_hit( Creature &target ) @@ -11327,55 +11404,69 @@ std::string Character::is_snuggling() const return "nothing"; } -int Character::warmth( const bodypart_id &bp ) const +std::map Character::warmth( const std::map> + &clothing_map ) const { - int ret = 0; - double warmth = 0.0; + std::map ret; + for( const bodypart_id &bp : get_all_body_parts() ) { + ret.emplace( bp, 0 ); + } - for( const item &i : worn ) { - if( i.covers( bp ) ) { - warmth = i.get_warmth(); + for( const std::pair> &on_bp : clothing_map ) { + const bodypart_id &bp = on_bp.first; + + double warmth = 0.0; + const int wetness_pct = get_part_wetness_percentage( bp ); + + for( const item *it : on_bp.second ) { + warmth = it->get_warmth(); // Wool items do not lose their warmth due to being wet. // Warmth is reduced by 0 - 66% based on wetness. - if( !i.made_of( material_id( "wool" ) ) ) { - warmth *= 1.0 - 0.66 * get_part_wetness_percentage( bp ); + if( !it->made_of( material_id( "wool" ) ) ) { + warmth *= 1.0 - 0.66 * wetness_pct; } - ret += std::round( warmth ); + ret[bp] += std::round( warmth ); } + ret[bp] += get_effect_int( effect_heating_bionic, bp ); } - ret += get_effect_int( effect_heating_bionic, bp ); return ret; } -static int bestwarmth( const std::list< item > &its, const flag_id &flag ) +static int bestwarmth( const std::vector &its, const flag_id &flag ) { int best = 0; - for( const item &w : its ) { - if( w.has_flag( flag ) && w.get_warmth() > best ) { - best = w.get_warmth(); + for( const item *w : its ) { + if( w->has_flag( flag ) && w->get_warmth() > best ) { + best = w->get_warmth(); } } return best; } -int Character::bonus_item_warmth( const bodypart_id &bp ) const +std::map Character::bonus_item_warmth( const + std::map> &clothing_map ) const { - int ret = 0; - - // If the player is not wielding anything big, check if hands can be put in pockets - if( ( bp == body_part_hand_l || bp == body_part_hand_r ) && - weapon.volume() < 500_ml ) { - ret += bestwarmth( worn, flag_POCKETS ); + std::map ret; + for( const bodypart_id &bp : get_all_body_parts() ) { + ret.emplace( bp, 0 ); } + for( const std::pair> &on_bp : clothing_map ) { + const bodypart_id &bp = on_bp.first; + // If the player is not wielding anything big, check if hands can be put in pockets + if( ( bp == body_part_hand_l || bp == body_part_hand_r ) && + weapon.volume() < 500_ml ) { + ret[bp] += bestwarmth( on_bp.second, flag_POCKETS ); + } - // If the player's head is not encumbered, check if hood can be put up - if( bp == body_part_head && encumb( body_part_head ) < 10 ) { - ret += bestwarmth( worn, flag_HOOD ); - } + // If the player's head is not encumbered, check if hood can be put up + if( bp == body_part_head && encumb( body_part_head ) < 10 ) { + ret[bp] += bestwarmth( on_bp.second, flag_HOOD ); + } - // If the player's mouth is not encumbered, check if collar can be put up - if( bp == body_part_mouth && encumb( body_part_mouth ) < 10 ) { - ret += bestwarmth( worn, flag_COLLAR ); + // If the player's mouth is not encumbered, check if collar can be put up + if( bp == body_part_mouth && encumb( body_part_mouth ) < 10 ) { + ret[bp] += bestwarmth( on_bp.second, flag_COLLAR ); + } } return ret; diff --git a/src/character.h b/src/character.h index f9b221df4c724..f3d72f3eaf0a9 100644 --- a/src/character.h +++ b/src/character.h @@ -680,7 +680,8 @@ class Character : public Creature, public visitable bool is_hibernating() const; /** Maintains body temperature */ void update_bodytemp(); - void update_frostbite( const bodypart_id &bp, int FBwindPower ); + void update_frostbite( const bodypart_id &bp, int FBwindPower, + const std::map &warmth_per_bp ); /** Equalizes heat between body parts */ void temp_equalizer( const bodypart_id &bp1, const bodypart_id &bp2 ); @@ -719,7 +720,8 @@ class Character : public Creature, public visitable bool in_climate_control(); /** Returns wind resistance provided by armor, etc **/ - int get_wind_resistance( const bodypart_id &bp ) const; + std::map get_wind_resistance( const + std::map> &clothing_map ) const; /** Returns true if the player isn't able to see */ bool is_blind() const; @@ -972,8 +974,9 @@ class Character : public Creature, public visitable float bionic_armor_bonus( const bodypart_id &bp, damage_type dt ) const; /** Returns the armor bonus against given type from martial arts buffs */ int mabuff_armor_bonus( damage_type type ) const; - /** Returns overall fire resistance for the body part */ - int get_armor_fire( const bodypart_id &bp ) const; + /** Returns overall fire resistance */ + std::map get_armor_fire( const std::map> + &clothing_map ) const; // --------------- Mutation Stuff --------------- // In newcharacter.cpp /** Returns the id of a random starting trait that costs >= 0 points */ @@ -2363,6 +2366,8 @@ class Character : public Creature, public visitable int get_armor_acid( bodypart_id bp ) const; /** Returns overall resistance to given type on the bod part */ int get_armor_type( damage_type dt, bodypart_id bp ) const override; + std::map get_all_armor_type( damage_type dt, + const std::map> &clothing_map ) const; int get_stim() const; void set_stim( int new_stim ); @@ -2524,9 +2529,11 @@ class Character : public Creature, public visitable void set_destination_activity( const player_activity &new_destination_activity ); void clear_destination_activity(); /** Returns warmth provided by armor, etc. */ - int warmth( const bodypart_id &bp ) const; + std::map warmth( const std::map> + &clothing_map ) const; /** Returns warmth provided by an armor's bonus, like hoods, pockets, etc. */ - int bonus_item_warmth( const bodypart_id &bp ) const; + std::map bonus_item_warmth( const std::map> + &clothing_map ) const; /** Can the player lie down and cover self with blankets etc. **/ bool can_use_floor_warmth() const; /** diff --git a/src/game.cpp b/src/game.cpp index 53a9eabcd61ab..b429f02954b53 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1273,16 +1273,18 @@ int get_heat_radiation( const tripoint &location, bool direct ) int best_fire = 0; Character &player_character = get_player_character(); map &here = get_map(); + // Convert it to an int id once, instead of 139 times per turn + const field_type_id fd_fire_int = fd_fire.id(); for( const tripoint &dest : here.points_in_radius( location, 6 ) ) { int heat_intensity = 0; maptile mt = here.maptile_at( dest ); - int ffire = maptile_field_intensity( mt, fd_fire ); + int ffire = maptile_field_intensity( mt, fd_fire_int ); if( ffire > 0 ) { heat_intensity = ffire; } else { - heat_intensity = here.ter( dest )->heat_radiation; + heat_intensity = mt.get_ter()->heat_radiation; } if( heat_intensity == 0 ) { // No heat source here diff --git a/src/item.cpp b/src/item.cpp index cfddaced4a4b9..cd29e3e5d99e1 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -7450,6 +7450,30 @@ bool item::is_broken() const return has_flag( flag_ITEM_BROKEN ); } +int item::wind_resist() const +{ + std::vector materials = made_of_types(); + if( materials.empty() ) { + debugmsg( "Called item::wind_resist on an item (%s) made of nothing!", tname() ); + return 99; + } + + int best = -1; + for( const material_type *mat : materials ) { + cata::optional resistance = mat->wind_resist(); + if( resistance && *resistance > best ) { + best = *resistance; + } + } + + // Default to 99% effective + if( best == -1 ) { + return 99; + } + + return best; +} + std::set item::faults_potential() const { std::set res; diff --git a/src/item.h b/src/item.h index d585fa5d63ce6..7a8b45a80a729 100644 --- a/src/item.h +++ b/src/item.h @@ -1302,6 +1302,9 @@ class item : public visitable void set_last_temp_check( const time_point &pt ); + /** How resistant clothes made of this material are to wind (0-100) */ + int wind_resist() const; + /** What faults can potentially occur with this item? */ std::set faults_potential() const; diff --git a/src/material.cpp b/src/material.cpp index 132731241f61b..7fadc100dd0af 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -65,6 +65,7 @@ void material_type::load( const JsonObject &jsobj, const std::string & ) mandatory( jsobj, was_loaded, "chip_resist", _chip_resist ); mandatory( jsobj, was_loaded, "density", _density ); + optional( jsobj, was_loaded, "wind_resist", _wind_resist ); optional( jsobj, was_loaded, "specific_heat_liquid", _specific_heat_liquid ); optional( jsobj, was_loaded, "specific_heat_solid", _specific_heat_solid ); optional( jsobj, was_loaded, "latent_heat", _latent_heat ); @@ -119,6 +120,11 @@ void material_type::check() const if( !item::type_is_defined( _repaired_with ) ) { debugmsg( "invalid \"repaired_with\" %s for %s.", _repaired_with.c_str(), id.c_str() ); } + + if( _wind_resist && ( *_wind_resist > 100 || *_wind_resist < 0 ) ) { + debugmsg( "Wind resistance outside of range (100%% to 0%%, is %d%%) for %s.", *_wind_resist, + id.str() ); + } } material_id material_type::ident() const @@ -222,6 +228,11 @@ int material_type::density() const return _density; } +cata::optional material_type::wind_resist() const +{ + return _wind_resist; +} + bool material_type::edible() const { return _edible; diff --git a/src/material.h b/src/material.h index 13cb439dbea4f..77d1913103eb4 100644 --- a/src/material.h +++ b/src/material.h @@ -71,6 +71,8 @@ class material_type int _bullet_resist = 0; int _chip_resist = 0; // Resistance to physical damage of the item itself int _density = 1; // relative to "powder", which is 1 + // How resistant this material is to wind as a percentage - 0 to 100 + cata::optional _wind_resist; float _specific_heat_liquid = 4.186f; float _specific_heat_solid = 2.108f; float _latent_heat = 334.0f; @@ -125,6 +127,7 @@ class material_type float latent_heat() const; float freeze_point() const; int density() const; + cata::optional wind_resist() const; bool edible() const; bool rotting() const; bool soft() const; diff --git a/src/weather.cpp b/src/weather.cpp index fe0c40edcf6bd..a1c5a978b3a8d 100644 --- a/src/weather.cpp +++ b/src/weather.cpp @@ -412,8 +412,18 @@ void wet_character( Character &target, int amount ) if( !calendar::once_every( 6_seconds ) ) { return; } - const int warmth_delay = target.warmth( body_part_torso ) * 4 / 5 + target.warmth( - body_part_head ) / 5; + std::map> clothing_map; + for( const bodypart_id &bp : target.get_all_body_parts() ) { + clothing_map.emplace( bp, std::vector() ); + } + for( const item &it : target.worn ) { + for( const bodypart_str_id &covered : it.get_covered_body_parts() ) { + clothing_map[covered.id()].emplace_back( &it ); + } + } + std::map warmth_bp = target.warmth( clothing_map ); + const int warmth_delay = warmth_bp[body_part_torso] * 0.8 + + warmth_bp[body_part_head] * 0.2; if( rng( 0, 100 - amount + warmth_delay ) > 10 ) { // Thick clothing slows down (but doesn't cap) soaking return;