diff --git a/src/consumption.cpp b/src/consumption.cpp index a05d41ad5cd91..06821eb763823 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -805,13 +805,7 @@ bool player::eat( item &food, bool force ) add_effect( effect_foodpoison, food.poison * 30_minutes ); } - const bool spiritual = has_trait( trait_SPIRITUAL ); if( food.has_flag( flag_HIDDEN_HALLU ) ) { - if( spiritual ) { - add_morale( MORALE_FOOD_GOOD, 36, 72, 2_hours, 1_hours, false ); - } else { - add_morale( MORALE_FOOD_GOOD, 18, 36, 1_hours, 30_minutes, false ); - } if( !has_effect( effect_hallu ) ) { add_effect( effect_hallu, 6_hours ); } @@ -839,28 +833,6 @@ bool player::eat( item &food, bool force ) } else { add_msg_player_or_npc( _( "You eat your %s." ), _( " eats a %s." ), food.tname() ); - if( !spoiled && !food.has_flag( flag_ALLERGEN_JUNK ) ) { - bool has_chair_nearby = false; - for( const tripoint &pt : g->m.points_in_radius( pos(), 1 ) ) { - if( g->m.has_flag_furn( flag_CAN_SIT, pt ) || g->m.has_flag( flag_CAN_SIT, pt ) || - ( g->m.veh_at( pt ) && ( g->m.veh_at( pt )->vehicle().has_part( "SEAT" ) ) ) ) { - has_chair_nearby = true; - } - } - if( has_chair_nearby && g->m.has_nearby_table( pos(), 1 ) ) { - if( has_trait( trait_TABLEMANNERS ) ) { - rem_morale( MORALE_ATE_WITHOUT_TABLE ); - add_morale( MORALE_ATE_WITH_TABLE, 3, 3, 3_hours, 2_hours, true ); - } else { - add_morale( MORALE_ATE_WITH_TABLE, 1, 1, 3_hours, 2_hours, true ); - } - } else { - if( has_trait( trait_TABLEMANNERS ) ) { - rem_morale( MORALE_ATE_WITH_TABLE ); - add_morale( MORALE_ATE_WITHOUT_TABLE, -2, -4, 3_hours, 2_hours, true ); - } - } - } } } @@ -873,44 +845,6 @@ bool player::eat( item &food, bool force ) mod_power_level( units::from_kilojoule( -abs( food.get_comestible_fun() ) ) ); } - if( food.has_flag( flag_CANNIBALISM ) ) { - // Sapiovores don't recognize humans as the same species. - // But let them possibly feel cool about eating sapient stuff - treat like psycho - // However, spiritual sapiovores should still recognize humans as having a soul or special for religious reasons - const bool cannibal = has_trait( trait_CANNIBAL ); - const bool psycho = has_trait( trait_PSYCHOPATH ); - const bool sapiovore = has_trait( trait_SAPIOVORE ); - if( ( cannibal || sapiovore ) && psycho && spiritual ) { - add_msg_if_player( m_good, - _( "You feast upon the human flesh, and in doing so, devour their spirit." ) ); - // You're not really consuming anything special; you just think you are. - add_morale( MORALE_CANNIBAL, 25, 300 ); - } else if( cannibal && psycho ) { - add_msg_if_player( m_good, _( "You feast upon the human flesh." ) ); - add_morale( MORALE_CANNIBAL, 15, 200 ); - } else if( ( cannibal || sapiovore ) && spiritual ) { - add_msg_if_player( m_good, _( "You consume the sacred human flesh." ) ); - // Boosted because you understand the philosophical implications of your actions, and YOU LIKE THEM. - add_morale( MORALE_CANNIBAL, 15, 200 ); - } else if( cannibal ) { - add_msg_if_player( m_good, _( "You indulge your shameful hunger." ) ); - add_morale( MORALE_CANNIBAL, 10, 50 ); - } else if( ( psycho || sapiovore ) && spiritual ) { - add_msg_if_player( _( "You greedily devour the taboo meat." ) ); - // Small bonus for violating a taboo. - add_morale( MORALE_CANNIBAL, 5, 50 ); - } else if( psycho || sapiovore ) { - add_msg_if_player( _( "Meh. You've eaten worse." ) ); - } else if( spiritual ) { - add_msg_if_player( m_bad, - _( "This is probably going to count against you if there's still an afterlife." ) ); - add_morale( MORALE_CANNIBAL, -60, -400, 60_minutes, 30_minutes ); - } else { - add_msg_if_player( m_bad, _( "You feel horrible for eating a person." ) ); - add_morale( MORALE_CANNIBAL, -60, -400, 60_minutes, 30_minutes ); - } - } - if( food.has_flag( flag_FUNGAL_VECTOR ) && !has_trait( trait_M_IMMUNE ) ) { add_effect( effect_fungus, 1_turns, num_bp, true ); } @@ -929,53 +863,6 @@ bool player::eat( item &food, bool force ) } } - // Allergy check - const auto allergy = allergy_type( food ); - if( allergy != MORALE_NULL ) { - add_msg_if_player( m_bad, _( "Yuck! How can anybody eat this stuff?" ) ); - add_morale( allergy, -75, -400, 30_minutes, 24_minutes ); - } - if( food.has_flag( flag_ALLERGEN_JUNK ) ) { - if( has_trait( trait_PROJUNK ) ) { - add_msg_if_player( m_good, _( "Mmm, junk food." ) ); - add_morale( MORALE_SWEETTOOTH, 5, 30, 30_minutes, 24_minutes ); - } - if( has_trait( trait_PROJUNK2 ) ) { - if( !one_in( 100 ) ) { - add_msg_if_player( m_good, _( "When life's got you down, there's always sugar." ) ); - } else { - add_msg_if_player( m_good, _( "They may do what they must… you've already won." ) ); - } - add_morale( MORALE_SWEETTOOTH, 10, 50, 1_hours, 50_minutes ); - } - } - // Carnivores CAN eat junk food, but they won't like it much. - // Pizza-scraping happens in consume_effects. - if( has_trait( trait_CARNIVORE ) && food.has_flag( flag_ALLERGEN_JUNK ) && - !food.has_flag( flag_CARNIVORE_OK ) ) { - add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); - add_morale( MORALE_NO_DIGEST, -25, -125, 30_minutes, 24_minutes ); - } - if( !spoiled && chew && has_trait( trait_SAPROPHAGE ) ) { - // It's OK to *drink* things that haven't rotted. Alternative is to ban water. D: - add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); - add_morale( MORALE_NO_DIGEST, -75, -400, 30_minutes, 24_minutes ); - } - if( food.has_flag( flag_URSINE_HONEY ) && ( !crossed_threshold() || - has_trait( trait_THRESH_URSINE ) ) && - mutation_category_level["URSINE"] > 40 ) { - // Need at least 5 bear mutations for effect to show, to filter out mutations in common with other categories - int honey_fun = has_trait( trait_THRESH_URSINE ) ? - std::min( mutation_category_level["URSINE"] / 8, 20 ) : - mutation_category_level["URSINE"] / 12; - if( honey_fun < 10 ) { - add_msg_if_player( m_good, _( "You find the sweet taste of honey surprisingly palatable." ) ); - } else { - add_msg_if_player( m_good, _( "You feast upon the sweet honey." ) ); - } - add_morale( MORALE_HONEY, honey_fun, 100 ); - } - // Chance to become parasitised if( !will_vomit && !( has_bionic( bio_digestion ) || has_trait( trait_PARAIMMUNE ) ) ) { if( food.get_comestible()->parasites > 0 && !food.has_flag( flag_NO_PARASITES ) && @@ -1089,6 +976,119 @@ void Character::modify_morale( item &food, const int nutr ) add_morale( MORALE_FOOD_GOOD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } + + // Morale bonus for eating unspoiled food with char/table nearby + if( !food.rotten() && !food.has_flag( flag_ALLERGEN_JUNK ) ) { + if( g->m.has_nearby_chair( pos(), 1 ) && g->m.has_nearby_table( pos(), 1 ) ) { + if( has_trait( trait_TABLEMANNERS ) ) { + rem_morale( MORALE_ATE_WITHOUT_TABLE ); + add_morale( MORALE_ATE_WITH_TABLE, 3, 3, 3_hours, 2_hours, true ); + } else { + add_morale( MORALE_ATE_WITH_TABLE, 1, 1, 3_hours, 2_hours, true ); + } + } else { + if( has_trait( trait_TABLEMANNERS ) ) { + rem_morale( MORALE_ATE_WITH_TABLE ); + add_morale( MORALE_ATE_WITHOUT_TABLE, -2, -4, 3_hours, 2_hours, true ); + } + } + } + + if( food.has_flag( flag_HIDDEN_HALLU ) ) { + if( has_trait( trait_SPIRITUAL ) ) { + add_morale( MORALE_FOOD_GOOD, 36, 72, 2_hours, 1_hours, false ); + } else { + add_morale( MORALE_FOOD_GOOD, 18, 36, 1_hours, 30_minutes, false ); + } + } + + if( food.has_flag( flag_CANNIBALISM ) ) { + // Sapiovores don't recognize humans as the same species. + // But let them possibly feel cool about eating sapient stuff - treat like psycho + // However, spiritual sapiovores should still recognize humans as having a soul or special for religious reasons + const bool cannibal = has_trait( trait_CANNIBAL ); + const bool psycho = has_trait( trait_PSYCHOPATH ); + const bool sapiovore = has_trait( trait_SAPIOVORE ); + const bool spiritual = has_trait( trait_SPIRITUAL ); + if( ( cannibal || sapiovore ) && psycho && spiritual ) { + add_msg_if_player( m_good, + _( "You feast upon the human flesh, and in doing so, devour their spirit." ) ); + // You're not really consuming anything special; you just think you are. + add_morale( MORALE_CANNIBAL, 25, 300 ); + } else if( cannibal && psycho ) { + add_msg_if_player( m_good, _( "You feast upon the human flesh." ) ); + add_morale( MORALE_CANNIBAL, 15, 200 ); + } else if( ( cannibal || sapiovore ) && spiritual ) { + add_msg_if_player( m_good, _( "You consume the sacred human flesh." ) ); + // Boosted because you understand the philosophical implications of your actions, and YOU LIKE THEM. + add_morale( MORALE_CANNIBAL, 15, 200 ); + } else if( cannibal ) { + add_msg_if_player( m_good, _( "You indulge your shameful hunger." ) ); + add_morale( MORALE_CANNIBAL, 10, 50 ); + } else if( ( psycho || sapiovore ) && spiritual ) { + add_msg_if_player( _( "You greedily devour the taboo meat." ) ); + // Small bonus for violating a taboo. + add_morale( MORALE_CANNIBAL, 5, 50 ); + } else if( psycho || sapiovore ) { + add_msg_if_player( _( "Meh. You've eaten worse." ) ); + } else if( spiritual ) { + add_msg_if_player( m_bad, + _( "This is probably going to count against you if there's still an afterlife." ) ); + add_morale( MORALE_CANNIBAL, -60, -400, 60_minutes, 30_minutes ); + } else { + add_msg_if_player( m_bad, _( "You feel horrible for eating a person." ) ); + add_morale( MORALE_CANNIBAL, -60, -400, 60_minutes, 30_minutes ); + } + } + + // Allergy check + const auto allergy = allergy_type( food ); + if( allergy != MORALE_NULL ) { + add_msg_if_player( m_bad, _( "Yuck! How can anybody eat this stuff?" ) ); + add_morale( allergy, -75, -400, 30_minutes, 24_minutes ); + } + if( food.has_flag( flag_ALLERGEN_JUNK ) ) { + if( has_trait( trait_PROJUNK ) ) { + add_msg_if_player( m_good, _( "Mmm, junk food." ) ); + add_morale( MORALE_SWEETTOOTH, 5, 30, 30_minutes, 24_minutes ); + } + if( has_trait( trait_PROJUNK2 ) ) { + if( !one_in( 100 ) ) { + add_msg_if_player( m_good, _( "When life's got you down, there's always sugar." ) ); + } else { + add_msg_if_player( m_good, _( "They may do what they must… you've already won." ) ); + } + add_morale( MORALE_SWEETTOOTH, 10, 50, 1_hours, 50_minutes ); + } + } + // Carnivores CAN eat junk food, but they won't like it much. + // Pizza-scraping happens in consume_effects. + if( has_trait( trait_CARNIVORE ) && food.has_flag( flag_ALLERGEN_JUNK ) && + !food.has_flag( flag_CARNIVORE_OK ) ) { + add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); + add_morale( MORALE_NO_DIGEST, -25, -125, 30_minutes, 24_minutes ); + } + const bool chew = food.get_comestible()->comesttype == comesttype_FOOD || + food.has_flag( flag_USE_EAT_VERB ); + if( !food.rotten() && chew && has_trait( trait_SAPROPHAGE ) ) { + // It's OK to *drink* things that haven't rotted. Alternative is to ban water. D: + add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); + add_morale( MORALE_NO_DIGEST, -75, -400, 30_minutes, 24_minutes ); + } + if( food.has_flag( flag_URSINE_HONEY ) && ( !crossed_threshold() || + has_trait( trait_THRESH_URSINE ) ) && + mutation_category_level["URSINE"] > 40 ) { + // Need at least 5 bear mutations for effect to show, to filter out mutations in common with other categories + int honey_fun = has_trait( trait_THRESH_URSINE ) ? + std::min( mutation_category_level["URSINE"] / 8, 20 ) : + mutation_category_level["URSINE"] / 12; + if( honey_fun < 10 ) { + add_msg_if_player( m_good, _( "You find the sweet taste of honey surprisingly palatable." ) ); + } else { + add_msg_if_player( m_good, _( "You feast upon the sweet honey." ) ); + } + add_morale( MORALE_HONEY, honey_fun, 100 ); + } } bool Character::consume_effects( item &food ) diff --git a/src/item.cpp b/src/item.cpp index 2fa4b9fd62603..3db57d5ffc59c 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -3295,39 +3295,41 @@ void item::final_info( std::vector &info, const iteminfo_query *parts, } // list recipes you could use it in - itype_id tid; - if( contents.empty() ) { // use this item - tid = typeId(); - } else { // use the contained item - tid = contents.front().typeId(); - } - const std::set &known_recipes = g->u.get_learned_recipes().of_component( tid ); - if( !known_recipes.empty() && parts->test( iteminfo_parts::DESCRIPTION_APPLICABLE_RECIPES ) ) { - const inventory &inv = g->u.crafting_inventory(); + if( parts->test( iteminfo_parts::DESCRIPTION_APPLICABLE_RECIPES ) ) { + itype_id tid = contents.empty() ? typeId() : contents.front().typeId(); + const inventory &crafting_inv = g->u.crafting_inventory(); + const recipe_subset available_recipe_subset = g->u.get_available_recipes( crafting_inv ); + const std::set &item_recipes = available_recipe_subset.of_component( tid ); - if( known_recipes.size() > 24 ) { - insert_separation_line( info ); - info.push_back( iteminfo( "DESCRIPTION", - _( "You know dozens of things you could craft with it." ) ) ); - } else if( known_recipes.size() > 12 ) { + if( item_recipes.empty() ) { insert_separation_line( info ); info.push_back( iteminfo( "DESCRIPTION", - _( "You could use it to craft various other things." ) ) ); + _( "You know of nothing you could craft with it." ) ) ); } else { - const std::string recipes = enumerate_as_string( known_recipes.begin(), known_recipes.end(), - [ &inv ]( const recipe * r ) { - if( r->deduped_requirements().can_make_with_inventory( - inv, r->get_component_filter() ) ) { - return r->result_name(); - } else { - return string_format( "%s", r->result_name() ); - } - } ); - if( !recipes.empty() ) { + if( item_recipes.size() > 24 ) { + insert_separation_line( info ); + info.push_back( iteminfo( "DESCRIPTION", + _( "You know dozens of things you could craft with it." ) ) ); + } else if( item_recipes.size() > 12 ) { insert_separation_line( info ); info.push_back( iteminfo( "DESCRIPTION", - string_format( _( "You could use it to craft: %s" ), - recipes ) ) ); + _( "You could use it to craft various other things." ) ) ); + } else { + const std::string recipes = enumerate_as_string( item_recipes.begin(), item_recipes.end(), + [ &crafting_inv ]( const recipe * r ) { + if( r->deduped_requirements().can_make_with_inventory( + crafting_inv, r->get_component_filter() ) ) { + return r->result_name(); + } else { + return string_format( "%s", r->result_name() ); + } + } ); + if( !recipes.empty() ) { + insert_separation_line( info ); + info.push_back( iteminfo( "DESCRIPTION", + string_format( _( "You could use it to craft: %s" ), + recipes ) ) ); + } } } } diff --git a/src/map.cpp b/src/map.cpp index 52b95ecacddd5..4e318104fa85d 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2601,6 +2601,20 @@ bool map::has_nearby_table( const tripoint &p, int radius ) return false; } +bool map::has_nearby_chair( const tripoint &p, int radius ) +{ + for( const tripoint &pt : points_in_radius( p, radius ) ) { + const optional_vpart_position vp = veh_at( pt ); + if( has_flag( "CAN_SIT", pt ) ) { + return true; + } + if( vp && vp->vehicle().has_part( "SEAT" ) ) { + return true; + } + } + return false; +} + bool map::mop_spills( const tripoint &p ) { bool retval = false; diff --git a/src/map.h b/src/map.h index dd53486b1e3b7..cf7e4e7f0bc0d 100644 --- a/src/map.h +++ b/src/map.h @@ -686,6 +686,10 @@ class map * surface is nearby that could be used for crafting or eating. */ bool has_nearby_table( const tripoint &p, int radius = 1 ); + /** + * Check whether a chair or vehicle seat is nearby. + */ + bool has_nearby_chair( const tripoint &p, int radius = 1 ); /** * Check if creature can see some items at p. Includes: * - check for items at this location (has_items(p)) diff --git a/tests/iteminfo_test.cpp b/tests/iteminfo_test.cpp index 46a1e4ea495e1..7c7fdd7c80cc1 100644 --- a/tests/iteminfo_test.cpp +++ b/tests/iteminfo_test.cpp @@ -7,6 +7,7 @@ #include "game.h" #include "item.h" #include "iteminfo_query.h" +#include "recipe_dictionary.h" static void iteminfo_test( const item &i, const iteminfo_query &q, const std::string &reference ) { @@ -99,3 +100,58 @@ TEST_CASE( "nutrient_ranges_for_recipe_exemplars", "[item][iteminfo]" ) "Vitamins (RDA): Calcium (7-28%), Iron (0-83%), " "Vitamin A (3-11%), Vitamin B12 (2-6%), and Vitamin C (1-85%)\n" ); } + +TEST_CASE( "show available recipes with item as an ingredient", "[item][iteminfo][recipes]" ) +{ + iteminfo_query q( { iteminfo_parts::DESCRIPTION_APPLICABLE_RECIPES } ); + const recipe *purtab = &recipe_id( "pur_tablets" ).obj(); + g->u.empty_traits(); + + GIVEN( "character has a potassium iodide tablet and no skill" ) { + item &iodine = g->u.i_add( item( "iodine" ) ); + g->u.empty_skills(); + + THEN( "nothing is craftable from it" ) { + iteminfo_test( + iodine, q, + "--\nYou know of nothing you could craft with it.\n" ); + } + + WHEN( "they acquire the needed skills" ) { + g->u.set_skill_level( purtab->skill_used, purtab->difficulty ); + REQUIRE( g->u.get_skill_level( purtab->skill_used ) == purtab->difficulty ); + + THEN( "still nothing is craftable from it" ) { + iteminfo_test( + iodine, q, + "--\nYou know of nothing you could craft with it.\n" ); + } + + WHEN( "they have no book, but have the recipe memorized" ) { + g->u.learn_recipe( purtab ); + REQUIRE( g->u.knows_recipe( purtab ) ); + + THEN( "they can use potassium iodide tablets to craft it" ) { + iteminfo_test( + iodine, q, + "--\n" + "You could use it to craft: " + "water purification tablet\n" ); + } + } + + WHEN( "they have the recipe in a book, but not memorized" ) { + g->u.i_add( item( "textbook_chemistry" ) ); + + THEN( "they can use potassium iodide tablets to craft it" ) { + iteminfo_test( + iodine, q, + "--\n" + "You could use it to craft: " + "water purification tablet\n" ); + } + } + } + } +} +