diff --git a/data/json/body_parts.json b/data/json/body_parts.json index bfdc1bf8ea987..b4cd57c38d675 100644 --- a/data/json/body_parts.json +++ b/data/json/body_parts.json @@ -62,7 +62,16 @@ "smash_efficiency": 0.25, "bionic_slots": 18, "flags": [ "LIMB_UPPER" ], - "sub_parts": [ "head_forehead", "head_crown", "head_nape", "head_throat", "head_ear_r", "head_ear_l" ] + "sub_parts": [ "head_forehead", "head_crown", "head_nape", "head_throat", "head_ear_r", "head_ear_l" ], + "encumbrance_per_weight": [ + { "weight": "1 g", "encumbrance": 1 }, + { "weight": "300 g", "encumbrance": 4 }, + { "weight": "800 g", "encumbrance": 8 }, + { "weight": "1300 g", "encumbrance": 20 }, + { "weight": "1600 g", "encumbrance": 30 }, + { "weight": "2200 g", "encumbrance": 50 }, + { "weight": "5600 g", "encumbrance": 80 } + ] }, { "id": "eyes", diff --git a/data/json/items/armor/helmets.json b/data/json/items/armor/helmets.json index 5bff933aaf5a0..2b74898d026e0 100644 --- a/data/json/items/armor/helmets.json +++ b/data/json/items/armor/helmets.json @@ -62,7 +62,7 @@ "material_thickness": 9, "techniques": [ "WBLOCK_1" ], "flags": [ "VARSIZE", "WATERPROOF", "STURDY", "PADDED" ], - "armor": [ { "encumbrance": 20, "coverage": 85, "covers": [ "head" ] } ] + "armor": [ { "encumbrance_modifiers": [ "WELL_SUPPORTED" ], "coverage": 85, "covers": [ "head" ] } ] }, { "id": "tac_fullhelmet", @@ -554,7 +554,7 @@ ], "covers": [ "head" ], "coverage": 100, - "encumbrance": 25 + "encumbrance_modifiers": [ "RESTRICTS_NECK", "WELL_SUPPORTED" ] }, { "material": [ diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index 343b82476fc45..75d87b0d116e3 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -2936,6 +2936,9 @@ Encumbrance and coverage can be defined on a piece of armor as such: The value of this field (or, if it is an array, the first value in the array) is the base encumbrance (unfitted) of this item. When specified as an array, the second value is the max encumbrance - when the pockets of this armor are completely full of items, the encumbrance of a non-rigid item will be set to this. Otherwise it'll be between the first value and the second value following this the equation: first value + (second value - first value) * non-rigid volume / non-rigid capacity. By default, the max encumbrance is the encumbrance + (non-rigid volume / 250ml). +##### Encumbrance_modifiers +Experimental feature for having an items encumbrance be generated by weight instead of a fixed number. Takes an array of "DESCRIPTORS" described in the code. If you don't need any descriptors put "NONE". This overrides encumbrance putting it as well will make it be ignored. Currently only works for head armor. + ##### Coverage (integer) What percentage of time this piece of armor will be hit (and thus used as armor) when an attack hits the body parts in `covers`. diff --git a/src/bodypart.cpp b/src/bodypart.cpp index 4d8170eddaa74..2953a2ed5ccde 100644 --- a/src/bodypart.cpp +++ b/src/bodypart.cpp @@ -441,6 +441,19 @@ void body_part_type::load( const JsonObject &jo, const std::string & ) mandatory( jo, was_loaded, "side", part_side ); optional( jo, was_loaded, "sub_parts", sub_parts ); + + if( jo.has_array( "encumbrance_per_weight" ) ) { + const JsonArray &jarr = jo.get_array( "encumbrance_per_weight" ); + for( const JsonObject jval : jarr ) { + units::mass weight = 0_gram; + int encumbrance = 0; + + assign( jval, "weight", weight, true ); + mandatory( jval, was_loaded, "encumbrance", encumbrance ); + + encumbrance_per_weight.insert( std::pair( weight, encumbrance ) ); + } + } } void body_part_type::reset() diff --git a/src/bodypart.h b/src/bodypart.h index 60b09e93f63f6..71131c6b82a03 100644 --- a/src/bodypart.h +++ b/src/bodypart.h @@ -179,6 +179,8 @@ struct body_part_type { */ std::vector sub_parts; + std::map encumbrance_per_weight; + cata::flat_set flags; cata::flat_set conditional_flags; diff --git a/src/item_factory.cpp b/src/item_factory.cpp index 23e7275b3f9fe..b2da9ad16de1d 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -723,6 +723,10 @@ void Item_factory::finalize_post( itype &obj ) it.encumber += sub_armor.encumber; it.max_encumber += sub_armor.max_encumber; + for( const encumbrance_modifier &en : sub_armor.encumber_modifiers ) { + it.encumber_modifiers.push_back( en ); + } + // get the amount of the limb that is covered with sublocations // for overall coverage we need to scale coverage by that float scale = sub_armor.max_coverage( bp ) / 100.0; @@ -834,6 +838,30 @@ void Item_factory::finalize_post( itype &obj ) } + // calculate encumbrance data per limb if done by description + for( armor_portion_data &data : obj.armor->data ) { + if( !data.encumber_modifiers.empty() ) { + // we know that the data entry covers a single bp + data.encumber = data.calc_encumbrance( obj.weight, *data.covers.value().begin() ); + + // need to account for varsize stuff here and double encumbrance if so + if( obj.has_flag( flag_VARSIZE ) ) { + data.encumber *= 2; + } + + // Recalc max encumber as well + units::volume total_nonrigid_volume = 0_ml; + for( const pocket_data &pocket : obj.pockets ) { + if( !pocket.rigid ) { + // include the modifier for each individual pocket + total_nonrigid_volume += pocket.max_contains_volume() * pocket.volume_encumber_modifier; + } + } + data.max_encumber = data.encumber + total_nonrigid_volume * data.volume_encumber_modifier / + data.volume_per_encumbrance; + } + } + // need to scale amalgamized portion data based on total coverage. // 3% of 48% needs to be scaled to 6% of 100% for( armor_portion_data &it : obj.armor->data ) { @@ -2657,7 +2685,10 @@ void armor_portion_data::deserialize( const JsonObject &jo ) optional( jo, false, "rigid_layer_only", rigid_layer_only, false ); - if( jo.has_array( "encumbrance" ) ) { + if( jo.has_array( "encumbrance_modifiers" ) ) { + // instead of reading encumbrance calculate it by weight + optional( jo, false, "encumbrance_modifiers", encumber_modifiers ); + } else if( jo.has_array( "encumbrance" ) ) { encumber = jo.get_array( "encumbrance" ).get_int( 0 ); max_encumber = jo.get_array( "encumbrance" ).get_int( 1 ); } else { @@ -3533,6 +3564,27 @@ struct enum_traits { static constexpr balance_val last = balance_val::LAST; }; +namespace io +{ +template<> +std::string enum_to_string( encumbrance_modifier data ) +{ + switch( data ) { + case encumbrance_modifier::IMBALANCED: + return "IMBALANCED"; + case encumbrance_modifier::RESTRICTS_NECK: + return "RESTRICTS_NECK"; + case encumbrance_modifier::WELL_SUPPORTED: + return "WELL_SUPPORTED"; + case encumbrance_modifier::NONE: + return "NONE"; + case encumbrance_modifier::last: + break; + } + cata_fatal( "Invalid encumbrance descriptor" ); +} +} // namespace io + namespace io { // *INDENT-OFF* diff --git a/src/itype.cpp b/src/itype.cpp index bda0b394f4b5c..fa89bd35404ed 100644 --- a/src/itype.cpp +++ b/src/itype.cpp @@ -252,3 +252,56 @@ int armor_portion_data::max_coverage( bodypart_str_id bp ) const // return the max of primary or hanging sublocations (this only matters for hanging items on chest) return std::max( primary_max_coverage, secondary_max_coverage ); } + +int armor_portion_data::calc_encumbrance( units::mass weight, bodypart_id bp ) const +{ + // this function takes some fixed points for mass to encumbrance and interpolates them to get results for head encumbrance + // TODO: Generalize this for other body parts (either with a modifier or seperated point graphs) + // TODO: Handle distributed weight + + int encumbrance = 0; + + std::map mass_to_encumbrance = bp->encumbrance_per_weight; + + std::map::iterator itt = mass_to_encumbrance.lower_bound( weight ); + + if( itt == mass_to_encumbrance.end() ) { + debugmsg( "Can't find a notable point to match this with" ); + return 100; + } + + std::map::iterator next_itt = std::next( itt ); + + // between itt and next_itt need to figure out how much and scale values + float scale = static_cast( weight.value() - itt->first.value() ) / static_cast + ( next_itt->first.value() - itt->first.value() ); + + // encumbrance is scaled by range between the two values + encumbrance = itt->second + std::roundf( static_cast( next_itt->second - itt->second ) * + scale ); + + // then add some modifiers + + for( const encumbrance_modifier &em : encumber_modifiers ) { + encumbrance += armor_portion_data::convert_descriptor_to_int( em ); + } + + return encumbrance; +} + +int armor_portion_data::convert_descriptor_to_int( encumbrance_modifier em ) +{ + // this is where the values for each of these exist + switch( em ) { + case encumbrance_modifier::IMBALANCED: + case encumbrance_modifier::RESTRICTS_NECK: + return 10; + case encumbrance_modifier::WELL_SUPPORTED: + return -10; + case encumbrance_modifier::NONE: + return 0; + case encumbrance_modifier::last: + break; + } + return 0; +} diff --git a/src/itype.h b/src/itype.h index 709b80b3e7374..cc8cb236ec130 100644 --- a/src/itype.h +++ b/src/itype.h @@ -233,11 +233,28 @@ struct part_material { void deserialize( const JsonObject &jo ); }; +// values for attributes related to encumbrance +enum class encumbrance_modifier : int { + IMBALANCED = 0, + RESTRICTS_NECK, + WELL_SUPPORTED, + NONE, + last +}; + +template<> +struct enum_traits { + static constexpr encumbrance_modifier last = encumbrance_modifier::last; +}; + struct armor_portion_data { // The base volume for an item const units::volume volume_per_encumbrance = 250_ml; // NOLINT(cata-serialize) + // descriptors used to infer encumbrance + std::vector encumber_modifiers; + // How much this piece encumbers the player. int encumber = 0; @@ -320,6 +337,12 @@ struct armor_portion_data { */ int max_coverage( bodypart_str_id bp ) const; + // helper function to return encumbrance value by descriptor and weight + int calc_encumbrance( units::mass weight, bodypart_id bp ) const; + + // converts a specific encumbrance modifier to an actual encumbrance value + static int convert_descriptor_to_int( encumbrance_modifier em ); + void deserialize( const JsonObject &jo ); }; @@ -387,7 +410,7 @@ struct islot_armor { std::vector valid_mods; /** - * If the item in question has any sub coverage when testing for encumberance + * If the item in question has any sub coverage when testing for encumbrance */ bool has_sub_coverage = false;