bool player::can_disassemble( const item &obj, const inventory &inv, std::string *err ) const { const auto error = [&err]( const std::string & message ) { if( err != nullptr ) { *err = message; } return false; }; const auto &r = recipe_dictionary::get_uncraft( obj.typeId() ); if( !r ) { return error( string_format( _( "You cannot disassemble this." ) ) ); } // check sufficient light if( lighting_craft_speed_multiplier( r ) == 0.0f ) { return error( _( "You can't see to craft!" ) ); } // refuse to disassemble rotten items if( obj.goes_bad() || ( obj.is_food_container() && obj.contents.front().goes_bad() ) ) { if( obj.rotten() || ( obj.is_food_container() && obj.contents.front().rotten() ) ) { return error( _( "It's rotten, I'm not taking that apart." ) ); } } if( obj.count_by_charges() && !r.has_flag( "UNCRAFT_SINGLE_CHARGE" ) ) { // Create a new item to get the default charges int qty = r.create_result().charges; if( obj.charges < qty ) { auto msg = ngettext( "You need at least %d charge of %s.", "You need at least %d charges of %s.", qty ); return error( string_format( msg, qty, obj.tname().c_str() ) ); } } const auto &dis = r.disassembly_requirements(); for( const auto &opts : dis.get_qualities() ) { for( const auto &qual : opts ) { if( !qual.has( inv ) ) { // Here should be no dot at the end of the string as 'to_string()' provides it. return error( string_format( _( "You need %s" ), qual.to_string().c_str() ) ); } } } for( const auto &opts : dis.get_tools() ) { const bool found = std::any_of( opts.begin(), opts.end(), [&]( const tool_comp & tool ) { return ( tool.count <= 0 && inv.has_tools( tool.type, 1 ) ) || ( tool.count > 0 && inv.has_charges( tool.type, tool.count ) ); } ); if( !found ) { if( opts.front().count <= 0 ) { return error( string_format( _( "You need %s." ), item::nname( opts.front().type ).c_str() ) ); } else { return error( string_format( ngettext( "You need a %s with %d charge.", "You need a %s with %d charges.", opts.front().count ), item::nname( opts.front().type ).c_str(), opts.front().count ) ); } } } return true; }
ret_val<edible_rating> player::will_eat( const item &food, bool interactive ) const { const auto ret = can_eat( food ); if( !ret.success() ) { if( interactive ) { add_msg_if_player( m_info, "%s", ret.c_str() ); } return ret; } std::vector<ret_val<edible_rating>> consequences; const auto add_consequence = [&consequences]( const std::string & msg, edible_rating code ) { consequences.emplace_back( ret_val<edible_rating>::make_failure( code, msg ) ); }; const bool saprophage = has_trait( trait_id( "SAPROPHAGE" ) ); const auto &comest = food.type->comestible; if( food.rotten() ) { const bool saprovore = has_trait( trait_id( "SAPROVORE" ) ); if( !saprophage && !saprovore ) { add_consequence( _( "This is rotten and smells awful!" ), ROTTEN ); } } const bool carnivore = has_trait( trait_id( "CARNIVORE" ) ); if( food.has_flag( "CANNIBALISM" ) && !has_trait_flag( "CANNIBAL" ) ) { add_consequence( _( "The thought of eating human flesh makes you feel sick." ), CANNIBALISM ); } const bool edible = comest->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); if( edible && has_effect( effect_nausea ) ) { add_consequence( _( "You still feel nauseous and will probably puke it all up again." ), NAUSEA ); } if( ( allergy_type( food ) != MORALE_NULL ) || ( carnivore && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) ) ) { add_consequence( _( "Your stomach won't be happy (allergy)." ), ALLERGY ); } if( saprophage && edible && food.rotten() && !food.has_flag( "FERTILIZER" ) ) { // Note: We're allowing all non-solid "food". This includes drugs // Hard-coding fertilizer for now - should be a separate flag later //~ No, we don't eat "rotten" food. We eat properly aged food, like a normal person. //~ Semantic difference, but greatly facilitates people being proud of their character. add_consequence( _( "Your stomach won't be happy (not rotten enough)." ), ALLERGY_WEAK ); } const int nutr = nutrition_for( food ); const int quench = comest->quench; const int temp_hunger = get_hunger() - nutr; const int temp_thirst = get_thirst() - quench; if( !has_active_mutation( trait_id( "EATHEALTH" ) ) && !has_active_mutation( trait_id( "HIBERNATE" ) ) && !has_trait( trait_id( "SLIMESPAWNER" ) ) ) { if( get_hunger() < 0 && nutr >= 5 && !has_active_mutation( trait_id( "GOURMAND" ) ) ) { add_consequence( _( "You're full already and will be forcing yourself to eat." ), TOO_FULL ); } else if( ( ( nutr > 0 && temp_hunger < stomach_capacity() ) || ( comest->quench > 0 && temp_thirst < stomach_capacity() ) ) && !food.has_infinite_charges() ) { add_consequence( _( "You will not be able to finish it all." ), TOO_FULL ); } } if( !consequences.empty() ) { if( !interactive ) { return consequences.front(); } std::ostringstream req; for( const auto &elem : consequences ) { req << elem.str() << std::endl; } const bool eat_verb = food.has_flag( "USE_EAT_VERB" ); if( eat_verb || comest->comesttype == "FOOD" ) { req << string_format( _( "Eat your %s anyway?" ), food.tname().c_str() ); } else if( !eat_verb && comest->comesttype == "DRINK" ) { req << string_format( _( "Drink your %s anyway?" ), food.tname().c_str() ); } else { req << string_format( _( "Consume your %s anyway?" ), food.tname().c_str() ); } if( !query_yn( req.str() ) ) { return consequences.front(); } } // All checks ended, it's edible (or we're pretending it is) return ret_val<edible_rating>::make_success(); }
bool player::eat( item &food, bool force ) { if( !food.is_food() ) { return false; } // Check if it's rotten before eating! food.calc_rot( global_square_location() ); const auto ret = force ? can_eat( food ) : will_eat( food, is_player() ); if( !ret.success() ) { return false; } if( food.type->has_use() ) { if( food.type->invoke( *this, food, pos() ) <= 0 ) { return false; } } // Note: the block below assumes we decided to eat it // No coming back from here const bool hibernate = has_active_mutation( trait_id( "HIBERNATE" ) ); const int nutr = nutrition_for( food ); const int quench = food.type->comestible->quench; const bool spoiled = food.rotten(); // The item is solid food const bool chew = food.type->comestible->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); // This item is a drink and not a solid food (and not a thick soup) const bool drinkable = !chew && food.type->comestible->comesttype == "DRINK"; // If neither of the above is true then it's a drug and shouldn't get mealtime penalty/bonus if( hibernate && ( get_hunger() > -60 && get_thirst() > -60 ) && ( get_hunger() - nutr < -60 || get_thirst() - quench < -60 ) ) { add_memorial_log( pgettext( "memorial_male", "Began preparing for hibernation." ), pgettext( "memorial_female", "Began preparing for hibernation." ) ); add_msg_if_player( _( "You've begun stockpiling calories and liquid for hibernation. You get the feeling that you should prepare for bed, just in case, but...you're hungry again, and you could eat a whole week's worth of food RIGHT NOW." ) ); } const bool will_vomit = get_hunger() < 0 && nutr >= 5 && !has_trait( trait_id( "GOURMAND" ) ) && !hibernate && !has_trait( trait_id( "SLIMESPAWNER" ) ) && !has_trait( trait_id( "EATHEALTH" ) ) && rng( -200, 0 ) > get_hunger() - nutr; const bool saprophage = has_trait( trait_id( "SAPROPHAGE" ) ); if( spoiled && !saprophage ) { add_msg_if_player( m_bad, _( "Ick, this %s doesn't taste so good..." ), food.tname().c_str() ); if( !has_trait( trait_id( "SAPROVORE" ) ) && !has_trait( trait_id( "EATDEAD" ) ) && ( !has_bionic( bio_digestion ) || one_in( 3 ) ) ) { add_effect( effect_foodpoison, rng( 60, ( nutr + 1 ) * 60 ) ); } consume_effects( food ); } else if( spoiled && saprophage ) { add_msg_if_player( m_good, _( "Mmm, this %s tastes delicious..." ), food.tname().c_str() ); consume_effects( food ); } else { consume_effects( food ); } const bool amorphous = has_trait( trait_id( "AMORPHOUS" ) ); int mealtime = 250; if( drinkable || chew ) { // Those bonuses/penalties only apply to food // Not to smoking weed or applying bandages! if( has_trait( trait_id( "MOUTH_TENTACLES" ) ) || has_trait( trait_id( "MANDIBLES" ) ) ) { mealtime /= 2; } else if( has_trait( trait_id( "GOURMAND" ) ) ) { // Don't stack those two - that would be 25 moves per item mealtime -= 100; } if( has_trait( trait_id( "BEAK_HUM" ) ) && !drinkable ) { mealtime += 200; // Much better than PROBOSCIS but still optimized for fluids } else if( has_trait( trait_id( "SABER_TEETH" ) ) ) { mealtime += 250; // They get In The Way } if( amorphous ) { mealtime *= 1.1; // Minor speed penalty for having to flow around it // rather than just grab & munch } } moves -= mealtime; // If it's poisonous... poison us. // TODO: Move this to a flag if( food.poison > 0 && !has_trait( trait_id( "EATPOISON" ) ) && !has_trait( trait_id( "EATDEAD" ) ) ) { if( food.poison >= rng( 2, 4 ) ) { add_effect( effect_poison, food.poison * 100 ); } add_effect( effect_foodpoison, food.poison * 300 ); } if( amorphous ) { add_msg_player_or_npc( _( "You assimilate your %s." ), _( "<npcname> assimilates a %s." ), food.tname().c_str() ); } else if( drinkable ) { add_msg_player_or_npc( _( "You drink your %s." ), _( "<npcname> drinks a %s." ), food.tname().c_str() ); } else if( chew ) { add_msg_player_or_npc( _( "You eat your %s." ), _( "<npcname> eats a %s." ), food.tname().c_str() ); } if( item::find_type( food.type->comestible->tool )->tool ) { // Tools like lighters get used use_charges( food.type->comestible->tool, 1 ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL" ) ) { charge_power( rng( 50, 200 ) ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL_WEAK" ) ) { charge_power( rng( 25, 100 ) ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL_STRONG" ) ) { charge_power( rng( 75, 300 ) ); } if( food.has_flag( "CANNIBALISM" ) ) { // Sapiovores don't recognize humans as the same species. // But let them possibly feel cool about eating sapient stuff - treat like psycho const bool cannibal = has_trait( trait_id( "CANNIBAL" ) ); const bool psycho = has_trait( trait_id( "PSYCHOPATH" ) ) || has_trait( trait_id( "SAPIOVORE" ) ); const bool spiritual = has_trait( trait_id( "SPIRITUAL" ) ); if( cannibal && 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 && 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 && 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 ) { 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, 600, 300 ); } else { add_msg_if_player( m_bad, _( "You feel horrible for eating a person." ) ); add_morale( MORALE_CANNIBAL, -60, -400, 600, 300 ); } } // 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, 300, 240 ); } // Carnivores CAN eat junk food, but they won't like it much. // Pizza-scraping happens in consume_effects. if( has_trait( trait_id( "CARNIVORE" ) ) && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_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, 300, 240 ); } if( !spoiled && chew && has_trait( trait_id( "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, 300, 240 ); } if( food.has_flag( "URSINE_HONEY" ) && ( !crossed_threshold() || has_trait( trait_id( "THRESH_URSINE" ) ) ) && mutation_category_level["MUTCAT_URSINE"] > 40 ) { //Need at least 5 bear mutations for effect to show, to filter out mutations in common with other mutcats int honey_fun = has_trait( trait_id( "THRESH_URSINE" ) ) ? std::min( mutation_category_level["MUTCAT_URSINE"] / 8, 20 ) : mutation_category_level["MUTCAT_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 ); } if( will_vomit ) { vomit(); } // chance to become parasitised if( !( has_bionic( bio_digestion ) || has_trait( trait_id( "PARAIMMUNE" ) ) ) ) { if( food.type->comestible->parasites > 0 && one_in( food.type->comestible->parasites ) ) { switch( rng( 0, 3 ) ) { case 0: if( !has_trait( trait_id( "EATHEALTH" ) ) ) { add_effect( effect_tapeworm, 1, num_bp, true ); } break; case 1: if( !has_trait( trait_id( "ACIDBLOOD" ) ) ) { add_effect( effect_bloodworms, 1, num_bp, true ); } break; case 2: add_effect( effect_brainworms, 1, num_bp, true ); break; case 3: add_effect( effect_paincysts, 1, num_bp, true ); } } } for( const auto &v : this->vitamins_from( food ) ) { auto qty = has_effect( effect_tapeworm ) ? v.second / 2 : v.second; // can never develop hypervitaminosis from consuming food vitamin_mod( v.first, qty ); } food.mod_charges( -1 ); return true; }
edible_rating player::can_eat( const item &food, bool interactive, bool force ) const { if( is_npc() || force ) { // Just to be sure interactive = false; } const std::string &itname = food.tname(); // Helper to avoid ton of `if( interactive )` // Prints if interactive is true, does nothing otherwise const auto maybe_print = [interactive, &itname] ( game_message_type type, const char *str ) { if( interactive ) { add_msg( type, str, itname.c_str() ); } }; // As above, but for queries // Asks if interactive and not force // Always true if force // Never true otherwise const auto maybe_query = [force, interactive, &itname, this]( const char *str ) { if( force ) { return true; } else if( !interactive ) { return false; } return query_yn( str, itname.c_str() ); }; const auto comest = food.type->comestible.get(); if( comest == nullptr ) { maybe_print( m_info, _( "That doesn't look edible." ) ); return INEDIBLE; } if( comest->tool != "null" ) { bool has = has_amount( comest->tool, 1 ); if( item::count_by_charges( comest->tool ) ) { has = has_charges( comest->tool, 1 ); } if( !has ) { if( interactive ) { add_msg_if_player( m_info, _( "You need a %s to consume that!" ), item::nname( comest->tool ).c_str() ); } return NO_TOOL; } } if( is_underwater() ) { maybe_print( m_info, _( "You can't do that while underwater." ) ); return INEDIBLE; } // For all those folks who loved eating marloss berries. D:< mwuhahaha if( has_trait( "M_DEPENDENT" ) && food.type->id != "mycus_fruit" ) { maybe_print( m_info, _( "We can't eat that. It's not right for us." ) ); return INEDIBLE_MUTATION; } const bool drinkable = comest->comesttype == "DRINK" && !food.has_flag( "USE_EAT_VERB" ); // Here's why PROBOSCIS is such a negative trait. if( has_trait( "PROBOSCIS" ) && !drinkable ) { maybe_print( m_info, _( "Ugh, you can't drink that!" ) ); return INEDIBLE_MUTATION; } int capacity = stomach_capacity(); // TODO: Move this cache to a structure and pass it around // to speed up checking entire inventory for edibles const bool gourmand = has_trait( "GOURMAND" ); const bool hibernate = has_active_mutation( "HIBERNATE" ); const bool eathealth = has_trait( "EATHEALTH" ); const bool slimespawner = has_trait( "SLIMESPAWNER" ); const int nutr = nutrition_for( food.type ); const int quench = comest->quench; bool spoiled = food.rotten(); const int temp_hunger = get_hunger() - nutr; const int temp_thirst = get_thirst() - quench; const bool overeating = get_hunger() < 0 && nutr >= 5 && !gourmand && !eathealth && !slimespawner && !hibernate; if( interactive && hibernate && ( get_hunger() >= -60 && get_thirst() >= -60 ) && ( temp_hunger < -60 || temp_thirst < -60 ) ) { if( !maybe_query( _( "You're adequately fueled. Prepare for hibernation?" ) ) ) { return TOO_FULL; } } const bool carnivore = has_trait( "CARNIVORE" ); if( carnivore && nutr > 0 && food.has_any_flag( carnivore_blacklist ) && !food.has_flag( "CARNIVORE_OK" ) ) { maybe_print( m_info, _( "Eww. Inedible plant stuff!" ) ); return INEDIBLE_MUTATION; } if( ( has_trait( "HERBIVORE" ) || has_trait( "RUMINANT" ) ) && food.has_any_flag( herbivore_blacklist ) ) { // Like non-cannibal, but more strict! maybe_print( m_info, _( "The thought of eating that makes you feel sick. You decide not to." ) ); return INEDIBLE_MUTATION; } if( food.has_flag( "CANNIBALISM" ) ) { if( !has_trait_flag( "CANNIBAL" ) && !maybe_query( _( "The thought of eating that makes you feel sick. Really do it?" ) ) ) { return CANNIBALISM; } } if( is_allergic( food ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { return ALLERGY; } if( carnivore && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { return ALLERGY; } const bool saprophage = has_trait( "SAPROPHAGE" ); // The item is solid food const bool chew = comest->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); if( spoiled ) { if( !saprophage && !has_trait( "SAPROVORE" ) && !maybe_query( _( "This %s smells awful! Eat it?" ) ) ) { return ROTTEN; } } else if( saprophage && chew && !food.has_flag( "FERTILIZER" ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { // Note: We're allowing all non-solid "food". This includes drugs // Hardcoding fertilizer for now - should be a separate flag later //~ No, we don't eat "rotten" food. We eat properly aged food, like a normal person. //~ Semantic difference, but greatly facilitates people being proud of their character. maybe_print( m_info, _( "It's too fresh, let it age a little first." ) ); return ROTTEN; } // Print at most one of those bool overfull = false; if( overeating ) { overfull = !maybe_query( _( "You're full. Force yourself to eat?" ) ); } else if( ( ( nutr > 0 && temp_hunger < capacity ) || ( comest->quench > 0 && temp_thirst < capacity ) ) && !eathealth && !slimespawner ) { overfull = !maybe_query( _( "You will not be able to finish it all. Consume it?" ) ); } if( overfull ) { return TOO_FULL; } // All checks ended, it's edible (or we're pretending it is) return EDIBLE; }