diff --git a/data/mods/TEST_DATA/items.json b/data/mods/TEST_DATA/items.json index f21a39cfb0dce..ab4a12892c638 100644 --- a/data/mods/TEST_DATA/items.json +++ b/data/mods/TEST_DATA/items.json @@ -2706,5 +2706,108 @@ "unfold_msg": "You painstakingly unfold the bicycle and make it ready to ride.", "moves": 500 } + }, + { + "id": "test_armor_chitin", + "type": "ARMOR", + "category": "armor", + "name": { "str": "chitinous armor" }, + "description": "Leg and body armor made from the exoskeletons of insects.", + "weight": "2632 g", + "volume": "17500 ml", + "price": 120000, + "price_postapoc": 3000, + "to_hit": -5, + "bashing": 2, + "material": [ "chitin" ], + "symbol": "[", + "looks_like": "armor_larmor", + "color": "green", + "warmth": 10, + "longest_side": "60 cm", + "material_thickness": 4, + "environmental_protection": 6, + "environmental_protection_with_filter": 10, + "flags": [ "STURDY", "OUTER" ], + "armor": [ { "encumbrance": 10, "coverage": 90, "covers": [ "torso", "leg_l", "leg_r" ] } ] + }, + { + "id": "test_armor_chitin_copy", + "type": "ARMOR", + "name": { "str": "XL chitinous armor" }, + "copy-from": "test_armor_chitin", + "proportional": { "weight": 1.125, "volume": 1.13, "price": 1.25 }, + "extend": { "flags": [ "OVERSIZE" ] } + }, + { + "id": "test_armor_chitin_copy_w_armor", + "type": "ARMOR", + "name": { "str": "XL chitinous armor" }, + "copy-from": "test_armor_chitin", + "proportional": { "weight": 1.125, "volume": 1.13, "price": 1.25 }, + "extend": { "flags": [ "OVERSIZE" ] }, + "armor": [ { "encumbrance": 10, "coverage": 90, "covers": [ "torso", "leg_l", "leg_r" ] } ] + }, + { + "id": "test_armor_chitin_copy_prop", + "type": "ARMOR", + "name": { "str": "XL chitinous armor" }, + "copy-from": "test_armor_chitin", + "proportional": { + "weight": 1.125, + "volume": 1.13, + "price": 1.25, + "material_thickness": 1.2, + "environmental_protection": 1.2, + "environmental_protection_with_filter": 1.2 + }, + "extend": { "flags": [ "OVERSIZE" ] } + }, + { + "id": "test_armor_chitin_copy_w_armor_prop", + "type": "ARMOR", + "name": { "str": "XL chitinous armor" }, + "copy-from": "test_armor_chitin", + "proportional": { + "weight": 1.125, + "volume": 1.13, + "price": 1.25, + "material_thickness": 1.2, + "environmental_protection": 1.2, + "environmental_protection_with_filter": 1.2 + }, + "extend": { "flags": [ "OVERSIZE" ] }, + "armor": [ { "encumbrance": 10, "coverage": 90, "covers": [ "torso", "leg_l", "leg_r" ] } ] + }, + { + "id": "test_armor_chitin_copy_rel", + "type": "ARMOR", + "name": { "str": "XL chitinous armor" }, + "copy-from": "test_armor_chitin", + "relative": { + "weight": 200, + "volume": 1000, + "price": 100, + "material_thickness": 2, + "environmental_protection": 2, + "environmental_protection_with_filter": 2 + }, + "extend": { "flags": [ "OVERSIZE" ] } + }, + { + "id": "test_armor_chitin_copy_w_armor_rel", + "type": "ARMOR", + "name": { "str": "XL chitinous armor" }, + "copy-from": "test_armor_chitin", + "relative": { + "weight": 200, + "volume": 1000, + "price": 100, + "material_thickness": 2, + "environmental_protection": 2, + "environmental_protection_with_filter": 2 + }, + "extend": { "flags": [ "OVERSIZE" ] }, + "armor": [ { "encumbrance": 10, "coverage": 90, "covers": [ "torso", "leg_l", "leg_r" ] } ] } ] diff --git a/src/item_factory.cpp b/src/item_factory.cpp index 767250d3ab7fe..5948054ac2f6c 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -2479,24 +2479,67 @@ static void apply_optional( T &value, const cata::optional &applied ) } } +// Gets around the issue that cata::optional doesn't support +// the *= and += operators required for "proportional" and "relative". +template +static void get_optional( const JsonObject &jo, bool was_loaded, const std::string &member, + cata::optional &value ) +{ + T tmp; + if( value ) { + tmp = *value; + } + optional( jo, was_loaded, member, tmp ); + if( jo.has_member( member ) ) { + value = tmp; + } +} + +template +static void get_relative( const JsonObject &jo, const std::string &member, cata::optional &value, + T default_val ) +{ + if( jo.has_member( member ) ) { + value = value.value_or( default_val ) + jo.get_float( member ); + } +} + +template +static void get_proportional( const JsonObject &jo, const std::string &member, + cata::optional &value, T default_val ) +{ + if( jo.has_member( member ) ) { + value = value.value_or( default_val ) * jo.get_float( member ); + } +} + void islot_armor::load( const JsonObject &jo ) { optional( jo, was_loaded, "armor", sub_data ); - cata::optional thickness; - cata::optional env_resist; - cata::optional env_resist_w_filter; cata::optional covers; assign_coverage_from_json( jo, "covers", covers ); - optional( jo, false, "material_thickness", thickness, cata::nullopt ); - optional( jo, false, "environmental_protection", env_resist, cata::nullopt ); - optional( jo, false, "environmental_protection_with_filter", env_resist_w_filter, cata::nullopt ); + get_optional( jo, was_loaded, "material_thickness", _material_thickness ); + get_optional( jo, was_loaded, "environmental_protection", _env_resist ); + get_optional( jo, was_loaded, "environmental_protection_with_filter", _env_resist_w_filter ); + + JsonObject relative = jo.get_object( "relative" ); + relative.allow_omitted_members(); + get_relative( relative, "material_thickness", _material_thickness, 0.f ); + get_relative( relative, "environmental_protection", _env_resist, 0 ); + get_relative( relative, "environmental_protection_with_filter", _env_resist_w_filter, 0 ); + + JsonObject proportional = jo.get_object( "proportional" ); + proportional.allow_omitted_members(); + get_proportional( proportional, "material_thickness", _material_thickness, 0.f ); + get_proportional( proportional, "environmental_protection", _env_resist, 0 ); + get_proportional( proportional, "environmental_protection_with_filter", _env_resist_w_filter, 0 ); for( armor_portion_data &armor : sub_data ) { - apply_optional( armor.avg_thickness, thickness ); - apply_optional( armor.env_resist, env_resist ); - apply_optional( armor.env_resist_w_filter, env_resist_w_filter ); + apply_optional( armor.avg_thickness, _material_thickness ); + apply_optional( armor.env_resist, _env_resist ); + apply_optional( armor.env_resist_w_filter, _env_resist_w_filter ); if( covers ) { armor.covers = covers; } diff --git a/src/itype.h b/src/itype.h index 235f73d3ad051..ebb0a730b7d05 100644 --- a/src/itype.h +++ b/src/itype.h @@ -309,77 +309,84 @@ struct armor_portion_data { }; struct islot_armor { - /** - * Whether this item can be worn on either side of the body - */ - bool sided = false; - /** - * The Non-Functional variant of this item. Currently only applies to ablative plates - */ - itype_id non_functional; - /** - * How much warmth this item provides. - */ - int warmth = 0; - /** - * Factor modifying weight capacity - */ - float weight_capacity_modifier = 1.0f; - /** - * Bonus to weight capacity - */ - units::mass weight_capacity_bonus = 0_gram; - /** - * Whether this is a power armor item. - */ - bool power_armor = false; - /** - * Whether this item has ablative pockets - */ - bool ablative = false; - /** - * Whether this item has pockets that generate additional encumbrance - */ - bool additional_pocket_enc = false; - /** - * Whether this item has pockets that can be ripped off - */ - bool ripoff_chance = false; - /** - * Whether this item has pockets that are noisy - */ - bool noisy = false; - /** - * Whitelisted clothing mods. - * Restricted clothing mods must be listed here by id to be compatible. - */ - std::vector valid_mods; + public: + /** + * Whether this item can be worn on either side of the body + */ + bool sided = false; + /** + * The Non-Functional variant of this item. Currently only applies to ablative plates + */ + itype_id non_functional; + /** + * How much warmth this item provides. + */ + int warmth = 0; + /** + * Factor modifying weight capacity + */ + float weight_capacity_modifier = 1.0f; + /** + * Bonus to weight capacity + */ + units::mass weight_capacity_bonus = 0_gram; + /** + * Whether this is a power armor item. + */ + bool power_armor = false; + /** + * Whether this item has ablative pockets + */ + bool ablative = false; + /** + * Whether this item has pockets that generate additional encumbrance + */ + bool additional_pocket_enc = false; + /** + * Whether this item has pockets that can be ripped off + */ + bool ripoff_chance = false; + /** + * Whether this item has pockets that are noisy + */ + bool noisy = false; + /** + * Whitelisted clothing mods. + * Restricted clothing mods must be listed here by id to be compatible. + */ + std::vector valid_mods; - /** - * If the item in question has any sub coverage when testing for encumberance - */ - bool has_sub_coverage = false; + /** + * If the item in question has any sub coverage when testing for encumberance + */ + bool has_sub_coverage = false; - // Layer, encumbrance and coverage information for each body part. - // This isn't directly loaded in but is instead generated from the loaded in - // sub_data vector - std::vector data; + // Layer, encumbrance and coverage information for each body part. + // This isn't directly loaded in but is instead generated from the loaded in + // sub_data vector + std::vector data; - // Layer, encumbrance and coverage information for each sub body part. - // This vector can have duplicates for body parts themselves. - std::vector sub_data; + // Layer, encumbrance and coverage information for each sub body part. + // This vector can have duplicates for body parts themselves. + std::vector sub_data; - // all of the layers this item is involved in - std::vector all_layers; + // all of the layers this item is involved in + std::vector all_layers; - bool was_loaded = false; + bool was_loaded = false; - int avg_env_resist() const; - int avg_env_resist_w_filter() const; - float avg_thickness() const; + int avg_env_resist() const; + int avg_env_resist_w_filter() const; + float avg_thickness() const; - void load( const JsonObject &jo ); - void deserialize( const JsonObject &jo ); + void load( const JsonObject &jo ); + void deserialize( const JsonObject &jo ); + + private: + // Base material thickness, used to derive thickness in sub_data + cata::optional _material_thickness = 0.0f; + cata::optional _env_resist = 0; + cata::optional _env_resist_w_filter = 0; }; struct islot_pet_armor { diff --git a/tests/iteminfo_test.cpp b/tests/iteminfo_test.cpp index 40436d0b32dfe..333ef00fef76a 100644 --- a/tests/iteminfo_test.cpp +++ b/tests/iteminfo_test.cpp @@ -26,6 +26,15 @@ static const itype_id itype_candle_wax( "candle_wax" ); static const itype_id itype_dress_shirt( "dress_shirt" ); static const itype_id itype_match( "match" ); +static const itype_id itype_test_armor_chitin( "test_armor_chitin" ); +static const itype_id itype_test_armor_chitin_copy( "test_armor_chitin_copy" ); +static const itype_id itype_test_armor_chitin_copy_prop( "test_armor_chitin_copy_prop" ); +static const itype_id itype_test_armor_chitin_copy_rel( "test_armor_chitin_copy_rel" ); +static const itype_id itype_test_armor_chitin_copy_w_armor( "test_armor_chitin_copy_w_armor" ); +static const itype_id +itype_test_armor_chitin_copy_w_armor_prop( "test_armor_chitin_copy_w_armor_prop" ); +static const itype_id +itype_test_armor_chitin_copy_w_armor_rel( "test_armor_chitin_copy_w_armor_rel" ); static const itype_id itype_textbook_chemistry( "textbook_chemistry" ); static const itype_id itype_tshirt( "tshirt" ); static const itype_id itype_zentai( "zentai" ); @@ -2881,3 +2890,71 @@ TEST_CASE( "item debug info", "[iteminfo][debug][!mayfail][.]" ) "Freeze point: 32\n" ); } } + +TEST_CASE( "Armor values preserved after copy-from", "[iteminfo][armor][protection]" ) +{ + // Normal item definition, no copy + item armor( itype_test_armor_chitin ); + // Copied item definition, no explicit "armor" field + item armor_copy( itype_test_armor_chitin_copy ); + // Copied item definition, explicit "armor" field defined + item armor_copy_w_armor( itype_test_armor_chitin_copy_w_armor ); + // Copied item definition, no explicit "armor" field, using "proportional" + item armor_copy_prop( itype_test_armor_chitin_copy_prop ); + // Copied item definition, explicit "armor" field defined, using "proportional" + item armor_copy_w_armor_prop( itype_test_armor_chitin_copy_w_armor_prop ); + // Copied item definition, no explicit "armor" field, using "relative" + item armor_copy_rel( itype_test_armor_chitin_copy_rel ); + // Copied item definition, explicit "armor" field defined, using "relative" + item armor_copy_w_armor_rel( itype_test_armor_chitin_copy_w_armor_rel ); + + std::vector infoparts = { iteminfo_parts::ARMOR_PROTECTION }; + + std::string a_str = item_info_str( armor, infoparts ); + std::string a_copy_str = item_info_str( armor_copy, infoparts ); + std::string a_copy_w_armor_str = item_info_str( armor_copy_w_armor, infoparts ); + std::string a_copy_prop_str = item_info_str( armor_copy_prop, infoparts ); + std::string a_copy_w_armor_prop_str = item_info_str( armor_copy_w_armor_prop, infoparts ); + std::string a_copy_rel_str = item_info_str( armor_copy_rel, infoparts ); + std::string a_copy_w_armor_rel_str = item_info_str( armor_copy_w_armor_rel, infoparts ); + + const std::string info_str = + "--\n" + "Protection:\n" + " Bash: 12.00\n" + " Cut: 16.00\n" + " Ballistic: 4.00\n" + " Acid: 3.60\n" + " Fire: 1.20\n" + " Environmental: 6\n"; + + CHECK( a_str == info_str ); + CHECK( a_copy_str == info_str ); + CHECK( a_copy_w_armor_str == info_str ); + + const std::string info_prop_str = + "--\n" + "Protection:\n" + " Bash: 14.40\n" + " Cut: 19.20\n" + " Ballistic: 4.80\n" + " Acid: 4.20\n" + " Fire: 1.40\n" + " Environmental: 7\n"; + + CHECK( a_copy_prop_str == info_prop_str ); + CHECK( a_copy_w_armor_prop_str == info_prop_str ); + + const std::string info_rel_str = + "--\n" + "Protection:\n" + " Bash: 18.00\n" + " Cut: 24.00\n" + " Ballistic: 6.00\n" + " Acid: 4.80\n" + " Fire: 1.60\n" + " Environmental: 8\n"; + + CHECK( a_copy_rel_str == info_rel_str ); + CHECK( a_copy_w_armor_rel_str == info_rel_str ); +}