diff --git a/data/json/recipes/food/canned.json b/data/json/recipes/food/canned.json index a0aaa741ffc21..026b33468c78f 100644 --- a/data/json/recipes/food/canned.json +++ b/data/json/recipes/food/canned.json @@ -14,7 +14,7 @@ "book_learn": [ [ "cookbook", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "jar_glass_sealed", - "result_mult": 2, + "charges": 2, "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], "tools": [ [ [ "surface_heat", 100, "LIST" ] ], [ [ "pot_canning", -1 ], [ "pot_canning_clay", -1 ] ] ], "components": [ [ [ "water", 11 ], [ "water_clean", 11 ] ], [ [ "jar_glass", 1 ] ], [ [ "meat_offal", 2, "LIST" ] ] ] @@ -33,7 +33,7 @@ "book_learn": [ [ "manual_sealing", 4 ], [ "atomic_survival", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "contained": true, - "result_mult": 2, + "charges": 2, "qualities": [ { "id": "SAW_M", "level": 1 }, { "id": "HAMMER", "level": 1 }, @@ -85,7 +85,7 @@ "book_learn": [ [ "manual_sealing", 4 ], [ "atomic_survival", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 12, + "charges": 12, "qualities": [ { "id": "SAW_M", "level": 1 }, { "id": "HAMMER", "level": 1 }, @@ -114,7 +114,7 @@ "book_learn": [ [ "cookbook", 6 ], [ "manual_canning", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "jar_glass_sealed", - "result_mult": 2, + "charges": 2, "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], "tools": [ [ [ "surface_heat", 20, "LIST" ] ] ], "components": [ @@ -138,7 +138,7 @@ "book_learn": [ [ "cookbook", 6 ], [ "manual_canning", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "jar_3l_glass_sealed", - "result_mult": 12, + "charges": 12, "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], "tools": [ [ [ "surface_heat", 100, "LIST" ] ] ], "components": [ @@ -214,7 +214,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 12, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -243,7 +243,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "40 m", - "result_mult": 6, + "charges": 6, "book_learn": [ [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -271,7 +271,7 @@ "book_learn": [ [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], - "result_mult": 2, + "charges": 2, "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], "tools": [ [ [ "surface_heat", 100, "LIST" ] ], [ [ "pot_canning", -1 ], [ "pot_canning_clay", -1 ] ] ], "components": [ @@ -321,7 +321,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_medium", - "result_mult": 2, + "charges": 2, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -350,7 +350,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 12, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -378,7 +378,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -550,7 +550,7 @@ "book_learn": [ [ "cookbook", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], - "result_mult": 2, + "charges": 2, "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], "tools": [ [ [ "surface_heat", 100, "LIST" ] ], [ [ "pot_canning", -1 ], [ "pot_canning_clay", -1 ] ] ], "components": [ [ [ "water", 11 ], [ "water_clean", 11 ] ], [ [ "jar_glass_sealed", 1 ] ], [ [ "meat_red_raw", 2, "LIST" ] ] ] @@ -567,7 +567,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "30 m", - "result_mult": 2, + "charges": 2, "book_learn": [ [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -587,7 +587,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "30 m", - "result_mult": 2, + "charges": 2, "book_learn": [ [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -607,7 +607,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "30 m", - "result_mult": 2, + "charges": 2, "book_learn": [ [ "cookbook", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -633,7 +633,7 @@ "time": "30 m", "book_learn": [ [ "cookbook", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], - "result_mult": 4, + "charges": 4, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], "tools": [ [ [ "surface_heat", 100, "LIST" ] ], [ [ "pot_canning", -1 ], [ "pot_canning_clay", -1 ] ] ], @@ -788,7 +788,7 @@ "skills_required": [ "mechanics", 1 ], "difficulty": 3, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "manual_canning", 2 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 80, 4 ], @@ -865,7 +865,7 @@ "time": "30 m", "book_learn": [ [ "cookbook_italian", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], - "result_mult": 4, + "charges": 4, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], "tools": [ [ [ "surface_heat", 100, "LIST" ] ], [ [ "pot_canning", -1 ], [ "pot_canning_clay", -1 ] ] ], @@ -939,7 +939,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_medium", - "result_mult": 2, + "charges": 2, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -968,7 +968,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 12, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1024,7 +1024,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_medium", - "result_mult": 2, + "charges": 2, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1048,7 +1048,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 12, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1099,7 +1099,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_medium", - "result_mult": 2, + "charges": 2, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1123,7 +1123,7 @@ "book_learn": [ [ "manual_sealing", 4 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 12, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1174,7 +1174,7 @@ "book_learn": [ [ "manual_sealing", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_medium", - "result_mult": 2, + "charges": 2, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1203,7 +1203,7 @@ "book_learn": [ [ "manual_sealing", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 12, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1434,7 +1434,7 @@ "book_learn": [ [ "manual_sealing", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 6, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1487,7 +1487,7 @@ "book_learn": [ [ "manual_sealing", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 6, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1536,7 +1536,7 @@ "book_learn": [ [ "manual_sealing", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 6, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1585,7 +1585,7 @@ "book_learn": [ [ "manual_sealing", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 6, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1634,7 +1634,7 @@ "book_learn": [ [ "manual_sealing", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "container": "can_food_big", - "result_mult": 6, + "charges": 12, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "SAW_M", "level": 1 }, @@ -1658,7 +1658,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "cookbook", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -1678,7 +1678,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "cookbook", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -1704,7 +1704,7 @@ "time": "40 m", "book_learn": [ [ "cookbook", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], - "result_mult": 24, + "charges": 24, "autolearn": true, "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], @@ -1728,7 +1728,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -1748,7 +1748,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], "batch_time_factors": [ 83, 5 ], @@ -1767,7 +1767,7 @@ "skill_used": "cooking", "difficulty": 4, "time": "40 m", - "result_mult": 12, + "charges": 12, "batch_time_factors": [ 83, 5 ], "book_learn": [ [ "cookbook", 4 ], [ "manual_canning", 3 ] ], "proficiencies": [ { "proficiency": "prof_food_prep" }, { "proficiency": "prof_preservation" }, { "proficiency": "prof_food_canning" } ], diff --git a/data/json/recipes/recipe_food.json b/data/json/recipes/recipe_food.json index 5c635987a3e46..1cdae376626b8 100644 --- a/data/json/recipes/recipe_food.json +++ b/data/json/recipes/recipe_food.json @@ -7053,7 +7053,7 @@ "skill_used": "cooking", "difficulty": 5, "time": "30 m", - "result_mult": 2, + "charges": 2, "book_learn": [ [ "manual_canning", 4 ] ], "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], @@ -7185,7 +7185,7 @@ "skill_used": "cooking", "difficulty": 6, "time": "30 m", - "result_mult": 2, + "charges": 2, "book_learn": [ [ "manual_canning", 4 ] ], "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], @@ -7208,7 +7208,7 @@ "skill_used": "cooking", "difficulty": 6, "time": "30 m", - "result_mult": 2, + "charges": 2, "book_learn": [ [ "manual_canning", 4 ] ], "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], @@ -7257,7 +7257,7 @@ "skill_used": "cooking", "time": "5 m", "autolearn": true, - "result_mult": 2, + "charges": 2, "qualities": [ { "id": "CONTAIN", "level": 1 } ], "components": [ [ [ "sweet_fruit", 1, "LIST" ] ], [ [ "jar_glass_sealed", 1 ] ], [ [ "hard_liquor", 1, "LIST" ] ] ] }, @@ -7272,7 +7272,7 @@ "skill_used": "cooking", "time": "5 m", "autolearn": true, - "result_mult": 12, + "charges": 12, "qualities": [ { "id": "CONTAIN", "level": 1 } ], "components": [ [ [ "sweet_fruit", 8, "LIST" ] ], [ [ "jar_3l_glass_sealed", 1 ] ], [ [ "hard_liquor", 5, "LIST" ] ] ] }, @@ -9087,7 +9087,7 @@ "skill_used": "cooking", "difficulty": 6, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "manual_canning", 4 ] ], "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], @@ -9110,7 +9110,7 @@ "skill_used": "cooking", "difficulty": 6, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "manual_canning", 4 ] ], "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], @@ -9133,7 +9133,7 @@ "skill_used": "cooking", "difficulty": 5, "time": "40 m", - "result_mult": 12, + "charges": 12, "book_learn": [ [ "manual_canning", 4 ] ], "batch_time_factors": [ 83, 5 ], "qualities": [ { "id": "CUT", "level": 2 }, { "id": "COOK", "level": 3 } ], diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index cc6d5453fe769..3f3192d9de4f0 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -1722,7 +1722,7 @@ Crafting recipes are defined as a JSON object with the following fields: "contained": true, // Boolean value which defines if the resulting item comes in its designated container. Automatically set to true if any container is defined in the recipe. "container": "jar_glass_sealed", //The resulting item will be contained by the item set here, overrides default container. "batch_time_factors": [25, 15], // Optional factors for batch crafting time reduction. First number specifies maximum crafting time reduction as percentage, and the second number the minimal batch size to reach that number. In this example given batch size of 20 the last 6 crafts will take only 3750 time units. -"result_mult": 2, //Create this many stacks of the resulting item per craft. +"result_mult": 2, // Multiplier for resulting items. Also multiplies container items. "flags": [ // A set of strings describing boolean features of the recipe "BLIND_EASY", "ANOTHERFLAG" diff --git a/src/consumption.cpp b/src/consumption.cpp index 27adc80812be9..b3856ef5574dd 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -382,11 +382,19 @@ std::pair Character::compute_nutrient_range( nutrients this_min; nutrients this_max; - item result_it = rec->create_result(); - if( result_it.num_item_stacks() == 1 ) { - const item alt_result = result_it.legacy_front(); - if( alt_result.typeId() == comest_it.typeId() ) { - result_it = alt_result; + std::vector results = rec->create_results(); + item result_it = results.front(); + for( item &it : results ) { + if( it.typeId() == comest_it.typeId() ) { + result_it = it; + break; + } + if( !it.is_container_empty() ) { + const item alt_result = it.legacy_front(); + if( alt_result.typeId() == comest_it.typeId() ) { + result_it = alt_result; + break; + } } } if( result_it.typeId() != comest_it.typeId() ) { diff --git a/src/crafting.cpp b/src/crafting.cpp index 06171a351e0d4..02e0890bb2175 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -1362,6 +1362,53 @@ void item::inherit_flags( const item_components &parents, const recipe &making ) } } +static void set_temp_rot( item &newit, const double relative_rot, const bool should_heat ) +{ + if( newit.has_temperature() ) { + if( newit.goes_bad() ) { + newit.set_relative_rot( relative_rot ); + } + if( should_heat ) { + newit.heat_up(); + } else { + // Really what we should be doing is averaging the temperatures + // between the recipe components if we don't have a heat tool, but + // that's kind of hard. For now just set the item to 20 C + // and reset the temperature. + // + // Temperature is not functional for non-foods + newit.set_item_temperature( units::from_celsius( 20 ) ); + } + } +} + +static void spawn_items( Character &guy, std::vector &results, + const cata::optional &loc, const double relative_rot, const bool should_heat, + bool allow_wield = false ) +{ + for( item &newit : results ) { + // todo: set this up recursively, who knows what kinda crafts will need it + if( !newit.empty() ) { + for( item *new_content : newit.all_items_top() ) { + set_temp_rot( *new_content, relative_rot, should_heat ); + } + } + set_temp_rot( newit, relative_rot, should_heat ); + + newit.set_owner( guy.get_faction()->id ); + if( newit.made_of( phase_id::LIQUID ) ) { + liquid_handler::handle_all_liquid( newit, PICKUP_RANGE ); + } else if( !loc && allow_wield && !guy.has_wield_conflicts( newit ) && + guy.can_wield( newit ).success() ) { + wield_craft( guy, newit ); + } else if( !loc ) { + set_item_inventory( guy, newit ); + } else { + set_item_map_or_vehicle( guy, loc.value_or( guy.pos() ), newit ); + } + } +} + void Character::complete_craft( item &craft, const cata::optional &loc ) { if( !craft.is_craft() ) { @@ -1374,17 +1421,19 @@ void Character::complete_craft( item &craft, const cata::optional &loc item_components &used = craft.components; const double relative_rot = craft.get_relative_rot(); const bool should_heat = making.hot_result(); - const bool remove_raw = making.removes_raw(); std::vector newits; if( making.is_practice() ) { add_msg( _( "You finish practicing %s." ), making.result_name() ); // practice recipes don't produce a result item } else if( !making.result().is_null() ) { - // Set up the new item, and assign an inventory letter if available - newits = making.create_results( batch_size ); + newits = making.create_results( batch_size, &used ); + // only wield crafted items if there's only one + bool allow_wield = newits.size() == 1; + spawn_items( *this, newits, loc, relative_rot, should_heat, allow_wield ); } + // messages, learning of recipe if( !making.is_practice() && ( !newits.empty() || !making.result_eocs.empty() ) ) { // TODO: reconsider recipe memorization @@ -1405,140 +1454,17 @@ void Character::complete_craft( item &craft, const cata::optional &loc std::max( get_skill_level( making.skill_used ), 1 ) * std::max( get_int(), 1 ); const double time_to_learn = 1000 * 8 * std::pow( difficulty, 4 ) / learning_speed; - if( x_in_y( making.time_to_craft_moves( *this ), time_to_learn ) ) { + if( x_in_y( making.time_to_craft_moves( *this ), time_to_learn ) ) { learn_recipe( &making ); - add_msg( m_good, _( "You memorized the recipe for %s!" ), making.result_name() ); + add_msg( m_good, _( "You memorized the recipe for %s!" ), + making.result_name() ); } } } - size_t newit_counter = 0; - for( item &newit : newits ) { - - // Points to newit unless newit is a non-empty container, then it points to newit's contents. - // Necessary for things like canning soup; sometimes we want to operate on the soup, not the can. - item &food_contained = !newit.empty() ? newit.only_item() : newit; - - // Newly-crafted items are perfect by default. Inspect their materials to see if they shouldn't be - food_contained.inherit_flags( used, making ); - - for( const flag_id &flag : making.flags_to_delete ) { - food_contained.unset_flag( flag ); - } - - // Don't store components for things made by charges, - // Don't store components for things that can't be uncrafted. - if( recipe_dictionary::get_uncraft( making.result() ) && !food_contained.count_by_charges() && - making.is_reversible() ) { - // Setting this for items counted by charges gives only problems: - // those items are automatically merged everywhere (map/vehicle/inventory), - // which would either lose this information or merge it somehow. - food_contained.components = used.split( batch_size, newit_counter ); - newit_counter++; - } else if( food_contained.is_food() && !food_contained.has_flag( flag_NUTRIENT_OVERRIDE ) ) { - // use a copy of the used list so that the byproducts don't build up over iterations (#38071) - item_components usedbp; - - // if a component item has "cooks_like" it will be replaced by that item as a component - for( item_components::type_vector_pair &tvp : used ) { - for( item &comp : tvp.second ) { - // only comestibles have cooks_like. any other type of item will throw an exception, so filter those out - if( comp.is_comestible() && !comp.get_comestible()->cooks_like.is_empty() ) { - const double relative_rot = comp.get_relative_rot(); - comp = item( comp.get_comestible()->cooks_like, comp.birthday(), comp.charges ); - comp.set_relative_rot( relative_rot ); - } - // If this recipe is cooked, components are no longer raw. - if( should_heat || remove_raw ) { - comp.set_flag_recursive( flag_COOKED ); - } - - usedbp.add( comp ); - } - } - - // byproducts get stored as a "component" but with a byproduct flag for consumption purposes - if( making.has_byproducts() ) { - for( item &byproduct : making.create_byproducts( batch_size ) ) { - byproduct.set_flag( flag_BYPRODUCT ); - usedbp.add( byproduct ); - } - } - // store components for food recipes that do not have the override flag - food_contained.components = usedbp.split( batch_size, newit_counter ); - - // store the number of charges the recipe would create with batch size 1. - if( &newit != &food_contained ) { // If a canned/contained item was crafted… - // … the container holds exactly one completion of the recipe, no matter the batch size. - food_contained.recipe_charges = food_contained.charges; - } else { // Otherwise, the item is already stacked so we need to divide by batch size. - newit.recipe_charges = newit.charges / batch_size; - } - newit_counter++; - } - - if( food_contained.has_temperature() ) { - if( food_contained.goes_bad() ) { - food_contained.set_relative_rot( relative_rot ); - } - if( should_heat ) { - food_contained.heat_up(); - } else { - // Really what we should be doing is averaging the temperatures - // between the recipe components if we don't have a heat tool, but - // that's kind of hard. For now just set the item to 20 C - // and reset the temperature, don't - // forget byproducts below either when you fix this. - // - // Temperature is not functional for non-foods - food_contained.set_item_temperature( units::from_celsius( 20 ) ); - } - } - - // If the recipe has a `FULL_MAGAZINE` flag, fill it with ammo - if( newit.is_magazine() && making.has_flag( flag_FULL_MAGAZINE ) ) { - newit.ammo_set( newit.ammo_default(), - newit.ammo_capacity( item::find_type( newit.ammo_default() )->ammo->type ) ); - } - - newit.set_owner( get_faction()->id ); - // If these aren't equal, newit is a container, so finalize its contents too. - if( &newit != &food_contained ) { - food_contained.set_owner( get_faction()->id ); - } - - if( newit.made_of( phase_id::LIQUID ) ) { - liquid_handler::handle_all_liquid( newit, PICKUP_RANGE ); - } else if( !loc && !has_wield_conflicts( craft ) && - can_wield( newit ).success() ) { - wield_craft( *this, newit ); - } else { - set_item_map_or_vehicle( *this, loc.value_or( pos() ), newit ); - } - } - if( making.has_byproducts() ) { std::vector bps = making.create_byproducts( batch_size ); - for( item &bp : bps ) { - if( bp.has_temperature() ) { - if( bp.goes_bad() ) { - bp.set_relative_rot( relative_rot ); - } - if( should_heat ) { - bp.heat_up(); - } else { - bp.set_item_temperature( units::from_celsius( 20 ) ); - } - } - bp.set_owner( get_faction()->id ); - if( bp.made_of( phase_id::LIQUID ) ) { - liquid_handler::handle_all_liquid( bp, PICKUP_RANGE ); - } else if( !loc ) { - set_item_inventory( *this, bp ); - } else { - set_item_map_or_vehicle( *this, loc.value_or( pos() ), bp ); - } - } + spawn_items( *this, bps, loc, relative_rot, should_heat ); } recoil = MAX_RECOIL; @@ -1861,7 +1787,7 @@ comp_selection Character::select_item_component( const std::vectorcreate_result().is_comestible(); + is_food = !!rec->result()->comestible; remove_raw = rec->hot_result() || rec->removes_raw(); } enum class inventory_source : int { @@ -2400,7 +2326,11 @@ ret_val Character::can_disassemble( const item &obj, const read_only_visit if( !obj.is_ammo() ) { //we get ammo quantity to disassemble later on if( obj.count_by_charges() ) { // Create a new item to get the default charges - int qty = r.create_result().charges; + int qty = 0; + // it should always only create one item if batch size is 1, but just to be sure loop over results + for( item &it : r.create_results() ) { + qty += it.charges; + } if( obj.charges < qty ) { const char *msg = n_gettext( "You need at least %d charge of %s.", "You need at least %d charges of %s.", qty ); diff --git a/src/crafting_gui.cpp b/src/crafting_gui.cpp index 3d4b9541d35f0..4c119a890f56f 100644 --- a/src/crafting_gui.cpp +++ b/src/crafting_gui.cpp @@ -1934,7 +1934,7 @@ std::string peek_related_recipe( const recipe *current, const recipe_subset &ava std::sort( related_components.begin(), related_components.end(), compare_second ); // current recipe result std::vector> related_results; - item tmp = current->create_result(); + item tmp( current->result() ); // use this item const itype_id tid = tmp.typeId(); const std::set &known_recipes = diff --git a/src/item.cpp b/src/item.cpp index 81c558588e2ae..e358a605682d7 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -1167,11 +1167,7 @@ item item::in_container( const itype_id &cont, const int qty, const bool sealed } item container( cont, birthday() ); if( container.is_container() ) { - if( count_by_charges() ) { - container.fill_with( *this, qty ); - } else { - container.put_in( *this, item_pocket::pocket_type::CONTAINER ); - } + container.fill_with( *this, qty ); container.invlet = invlet; if( sealed ) { container.seal(); @@ -9190,7 +9186,7 @@ bool item::is_food_container() const return food.is_food(); } ) ) || ( is_craft() && !craft_data_->disassembly && - craft_data_->making->create_result().is_food_container() ); + craft_data_->making->create_results().front().is_food_container() ); } bool item::has_temperature() const diff --git a/src/item_components.cpp b/src/item_components.cpp index 454120c968436..a34529e20c6e7 100644 --- a/src/item_components.cpp +++ b/src/item_components.cpp @@ -1,5 +1,6 @@ #include "item_components.h" +#include "flag.h" #include "item.h" #include "itype.h" #include "type_id.h" @@ -107,7 +108,21 @@ item item_components::get_and_remove_random_entry() return ret; } -item_components item_components::split( const int batch_size, const size_t offset ) +static void adjust_new_comp( item &new_comp, bool is_cooked ) +{ + if( new_comp.is_comestible() && !new_comp.get_comestible()->cooks_like.is_empty() ) { + const double relative_rot = new_comp.get_relative_rot(); + new_comp = item( new_comp.get_comestible()->cooks_like, new_comp.birthday(), new_comp.charges ); + new_comp.set_relative_rot( relative_rot ); + } + + if( is_cooked ) { + new_comp.set_flag_recursive( flag_COOKED ); + } +} + +item_components item_components::split( const int batch_size, const size_t offset, + const bool is_cooked ) { item_components ret; @@ -118,22 +133,28 @@ item_components item_components::split( const int batch_size, const size_t offse tvp.first.str() ); return item_components(); } + item new_comp( tvp.second.front() ); + if( new_comp.charges % batch_size != 0 ) { debugmsg( "component %s can't be evenly distributed to resulting items", tvp.first.str() ); return item_components(); } + + adjust_new_comp( new_comp, is_cooked ); + new_comp.charges /= batch_size; ret.add( new_comp ); } else { - if( tvp.second.size() % batch_size != 0 ) { debugmsg( "component %s can't be evenly distributed to resulting items", tvp.first.str() ); return item_components(); } for( size_t i = offset; i < tvp.second.size(); i += batch_size ) { - ret.add( tvp.second[i] ); + item new_comp( tvp.second[i] ); + adjust_new_comp( new_comp, is_cooked ); + ret.add( new_comp ); } } } diff --git a/src/item_components.h b/src/item_components.h index e3e96af42fa65..da59cacb87b4f 100644 --- a/src/item_components.h +++ b/src/item_components.h @@ -2,9 +2,10 @@ #ifndef CATA_SRC_ITEM_COMPONENTS_H #define CATA_SRC_ITEM_COMPONENTS_H +#include #include +#include #include -#include #include "type_id.h" @@ -47,7 +48,7 @@ class item_components item get_and_remove_random_entry(); // used to distribute the components of a finished craft to the resulting items - item_components split( int batch_size, size_t offset ); + item_components split( int batch_size, size_t offset, bool is_cooked = false ); void serialize( JsonOut &jsout ) const; void deserialize( const JsonValue &jv ); diff --git a/src/profession.cpp b/src/profession.cpp index 7241aee8c080b..f648822a2c8a3 100644 --- a/src/profession.cpp +++ b/src/profession.cpp @@ -480,7 +480,7 @@ std::list profession::items( bool male, const std::vector &trait } } for( auto iter = result.begin(); iter != result.end(); ) { - const auto sub = item_substitutions.get_substitution( *iter, traits ); + const std::vector sub = item_substitutions.get_substitution( *iter, traits ); if( !sub.empty() ) { result.insert( result.begin(), sub.begin(), sub.end() ); iter = result.erase( iter ); @@ -793,7 +793,7 @@ std::vector json_item_substitution::get_substitution( const item &it, if( !result.count_by_charges() ) { for( int i = 0; i < new_amount; i++ ) { - ret.push_back( result.in_its_container() ); + ret.push_back( result.in_its_container( 1 ) ); } } else { while( new_amount > 0 ) { diff --git a/src/recipe.cpp b/src/recipe.cpp index d00975469839c..983b4905523bb 100644 --- a/src/recipe.cpp +++ b/src/recipe.cpp @@ -29,6 +29,7 @@ #include "optional.h" #include "output.h" #include "proficiency.h" +#include "recipe_dictionary.h" #include "skill.h" #include "string_formatter.h" #include "string_id_utils.h" @@ -41,6 +42,8 @@ static const itype_id itype_atomic_coffeepot( "atomic_coffeepot" ); static const itype_id itype_hotplate( "hotplate" ); +static const std::string flag_FULL_MAGAZINE( "FULL_MAGAZINE" ); + recipe::recipe() : skill_used( skill_id::NULL_ID() ) {} int recipe::get_difficulty( const Character &crafter ) const @@ -581,7 +584,8 @@ std::string recipe::get_consistency_error() const return std::string(); } -item recipe::create_result() const +std::vector recipe::create_result( bool set_components, bool is_food, + item_components *used ) const { item newit( result_, calendar::turn, item::default_charges_tag{} ); @@ -589,44 +593,81 @@ item recipe::create_result() const newit.set_flag( flag_FIT ); } - if( charges ) { - newit.charges = *charges; + // Newly-crafted items are perfect by default. Inspect their materials to see if they shouldn't be + if( used ) { + newit.inherit_flags( *used, *this ); } - if( !newit.craft_has_charges() ) { - newit.charges = 0; - } else if( result_mult != 1 ) { - // TODO: Make it work for charge-less items (update makes amount) - newit.charges *= result_mult; + for( const flag_id &flag : flags_to_delete ) { + newit.unset_flag( flag ); } - if( contained ) { - if( newit.count_by_charges() ) { - newit = newit.in_container( container, newit.charges, sealed ); + // If the recipe has a `FULL_MAGAZINE` flag, fill it with ammo + if( newit.is_magazine() && has_flag( flag_FULL_MAGAZINE ) ) { + newit.ammo_set( newit.ammo_default(), + newit.ammo_capacity( item::find_type( newit.ammo_default() )->ammo->type ) ); + } + + int amount = charges ? *charges : newit.count(); + + bool is_cooked = hot_result() || removes_raw(); + if( set_components ) { + if( is_food ) { + newit.components = *used; + newit.recipe_charges = amount; } else { - newit = newit.in_container( container, item::INFINITE_CHARGES, sealed ); + newit.components = used->split( amount, 0, is_cooked ); } } - return newit; + if( contained ) { + newit = newit.in_container( container, amount, sealed ); + return { newit }; + } else if( newit.count_by_charges() ) { + newit.charges = amount; + return { newit }; + } else { + std::vector items; + for( int i = 0; i < amount; i++ ) { + if( set_components ) { + newit.components = used->split( amount, i, is_cooked ); + } + items.push_back( newit ); + } + return items; + } } -std::vector recipe::create_results( int batch ) const +std::vector recipe::create_results( int batch, item_components *used ) const { std::vector items; - const bool by_charges = item::count_by_charges( result_ ); - if( contained || !by_charges ) { - // by_charges items get their charges multiplied in create_result - const int num_results = by_charges ? batch : batch * result_mult; - for( int i = 0; i < num_results; i++ ) { - item newit = create_result(); - items.push_back( newit ); + for( int i = 0; i < batch; i++ ) { + item_components batch_comps; + item temp( result_ ); + bool is_uncraftable = recipe_dictionary::get_uncraft( result_ ) && !result_->count_by_charges() && + is_reversible(); + bool is_food_no_override = temp.is_food() && !temp.has_flag( flag_NUTRIENT_OVERRIDE ); + bool set_components = used && ( is_uncraftable || is_food_no_override ); + if( set_components ) { + batch_comps = used->split( batch, i ); + } + for( int j = 0; j < result_mult; j++ ) { + item_components mult_comps = batch_comps.split( result_mult, j ); + std::vector newits = create_result( set_components, temp.is_food(), &mult_comps ); + + for( const item &it : newits ) { + // try to combine batch results for liquid handling + auto found = std::find_if( items.begin(), items.end(), [it]( const item & rhs ) { + return it.can_combine( rhs ); + } ); + if( found != items.end() ) { + found->combine( it ); + } else { + items.emplace_back( it ); + } + } } - } else { - item newit = create_result(); - newit.charges *= batch; - items.push_back( newit ); } return items; @@ -1036,7 +1077,7 @@ bool recipe::will_be_blacklisted() const std::function recipe::get_component_filter( const recipe_filter_flags flags ) const { - const item result = create_result(); + const item result( result_ ); // Disallow crafting of non-perishables with rotten components // Make an exception for items with the ALLOW_ROTTEN flag such as seeds @@ -1195,7 +1236,8 @@ void recipe::check_blueprint_requirements() bool recipe::removes_raw() const { - return create_result().is_comestible() && !create_result().has_flag( flag_RAW ); + item result( result_ ); + return result.is_comestible() && !result.has_flag( flag_RAW ); } bool recipe::hot_result() const @@ -1216,7 +1258,7 @@ bool recipe::hot_result() const // the check includes this tool in addition to the hotplate. // // TODO: Make this less of a hack - if( create_result().has_temperature() ) { + if( item( result_ ).has_temperature() ) { const requirement_data::alter_tool_comp_vector &tool_lists = simple_requirements().get_tools(); for( const std::vector &tools : tool_lists ) { for( const tool_comp &t : tools ) { @@ -1231,13 +1273,13 @@ bool recipe::hot_result() const int recipe::makes_amount() const { - int makes_charges = 1; // stays 1 if item isn't counted in charges + int makes = 1; if( charges.has_value() ) { - makes_charges = charges.value(); + makes = charges.value(); } else if( item::count_by_charges( result_ ) ) { - makes_charges = item::find_type( result_ )->charges_default(); + makes = item::find_type( result_ )->charges_default(); } - return makes_charges * result_mult; + return makes * result_mult; } void recipe::incorporate_build_reqs() diff --git a/src/recipe.h b/src/recipe.h index 0939bde2d6397..14ba15c4f2cf0 100644 --- a/src/recipe.h +++ b/src/recipe.h @@ -229,8 +229,11 @@ class recipe // Create an item instance as if the recipe was just finished, // Contain charges multiplier - item create_result() const; - std::vector create_results( int batch = 1 ) const; + private: + std::vector create_result( bool set_components, bool is_food, + item_components *used = nullptr ) const; + public: + std::vector create_results( int batch = 1, item_components *used = nullptr ) const; // Create byproduct instances as if the recipe was just finished std::vector create_byproducts( int batch = 1 ) const; diff --git a/src/recipe_dictionary.cpp b/src/recipe_dictionary.cpp index 76f2813c06a25..b8ee5c20cd512 100644 --- a/src/recipe_dictionary.cpp +++ b/src/recipe_dictionary.cpp @@ -215,7 +215,7 @@ std::vector recipe_subset::search( if( r->is_practice() ) { return lcmatch( r->description.translated(), txt ); } else { - const item result = r->create_result(); + const item result( r->result() ); return lcmatch( remove_color_tags( result.info( true ) ), txt ); } } diff --git a/tests/comestible_test.cpp b/tests/comestible_test.cpp index 8b0331cfcaa78..b0159015a9abf 100644 --- a/tests/comestible_test.cpp +++ b/tests/comestible_test.cpp @@ -136,18 +136,12 @@ static int byproduct_calories( const recipe &recipe_obj ) int kcal = 0; for( const item &it : byproducts ) { if( it.is_comestible() ) { - kcal += it.type->comestible->default_nutrition.kcal() * it.charges; + kcal += it.type->comestible->default_nutrition.kcal() * it.count(); } } return kcal; } -static item food_or_food_container( const item &it ) -{ - // if it contains an item, it's a food container. it will also contain only one item. - return it.num_item_stacks() > 0 ? it.only_item() : it; -} - static bool has_mutagen_vit( const islot_comestible &comest ) { const std::map &vits = comest.default_nutrition.vitamins; @@ -183,6 +177,21 @@ TEST_CASE( "comestible_health_bounds", "[comestible]" ) } } +static int get_default_calories_recursive( item &it ) +{ + int calories = it.type->comestible ? it.type->comestible->default_nutrition.kcal() : 0; + + if( it.count_by_charges() ) { + calories *= it.charges; + } + + for( item *cont : it.all_items_top() ) { + calories += get_default_calories_recursive( *cont ); + } + + return calories; +} + TEST_CASE( "recipe_permutations", "[recipe]" ) { // Are these tests failing? Here's how to fix that: @@ -195,9 +204,9 @@ TEST_CASE( "recipe_permutations", "[recipe]" ) for( const auto &recipe_pair : recipe_dict ) { // the resulting item const recipe &recipe_obj = recipe_pair.first.obj(); - item res_it = food_or_food_container( recipe_obj.create_result() ); - const bool is_food = res_it.is_food(); - const bool has_override = res_it.has_flag( STATIC( flag_id( "NUTRIENT_OVERRIDE" ) ) ); + item temp( recipe_obj.result() ); + const bool is_food = temp.is_food(); + const bool has_override = temp.has_flag( STATIC( flag_id( "NUTRIENT_OVERRIDE" ) ) ); if( is_food && !has_override ) { // Collection of kcal values of all ingredient permutations all_stats mystats = recipe_permutations( recipe_obj.simple_requirements().get_components(), @@ -205,14 +214,13 @@ TEST_CASE( "recipe_permutations", "[recipe]" ) if( mystats.calories.n() < 2 ) { continue; } + // The calories of the result int default_calories = 0; - if( res_it.type->comestible ) { - default_calories = res_it.type->comestible->default_nutrition.kcal(); - } - if( res_it.charges > 0 ) { - default_calories *= res_it.charges; + for( item &it : recipe_obj.create_results() ) { + default_calories += get_default_calories_recursive( it ); } + // Make the range of acceptable average calories of permutations, using result's calories const float lower_bound = std::min( default_calories - mystats.calories.stddev() * 2, default_calories * 0.75 );