ret_val<edible_rating> player::can_eat( const item &food ) const { // @todo This condition occurs way too often. Unify it. if( is_underwater() ) { return ret_val<edible_rating>::make_failure( _( "You can't do that while underwater." ) ); } const auto &comest = food.type->comestible; if( !comest ) { return ret_val<edible_rating>::make_failure( _( "That doesn't look edible." ) ); } const bool eat_verb = food.has_flag( "USE_EAT_VERB" ); const bool edible = eat_verb || comest->comesttype == "FOOD"; const bool drinkable = !eat_verb && comest->comesttype == "DRINK"; if( edible || drinkable ) { for( const auto &elem : food.type->materials ) { if( !elem->edible() ) { return ret_val<edible_rating>::make_failure( _( "That doesn't look edible in its current form." ) ); } } } if( comest->tool != "null" ) { const bool has = item::count_by_charges( comest->tool ) ? has_charges( comest->tool, 1 ) : has_amount( comest->tool, 1 ); if( !has ) { return ret_val<edible_rating>::make_failure( NO_TOOL, string_format( _( "You need a %s to consume that!" ), item::nname( comest->tool ).c_str() ) ); } } // For all those folks who loved eating marloss berries. D:< mwuhahaha if( has_trait( trait_id( "M_DEPENDENT" ) ) && food.typeId() != "mycus_fruit" ) { return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "We can't eat that. It's not right for us." ) ); } // Here's why PROBOSCIS is such a negative trait. if( has_trait( trait_id( "PROBOSCIS" ) ) && !drinkable ) { return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "Ugh, you can't drink that!" ) ); } if( has_trait( trait_id( "CARNIVORE" ) ) && nutrition_for( food ) > 0 && food.has_any_flag( carnivore_blacklist ) && !food.has_flag( "CARNIVORE_OK" ) ) { return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "Eww. Inedible plant stuff!" ) ); } if( ( has_trait( trait_id( "HERBIVORE" ) ) || has_trait( trait_id( "RUMINANT" ) ) ) && food.has_any_flag( herbivore_blacklist ) ) { // Like non-cannibal, but more strict! return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "The thought of eating that makes you feel sick." ) ); } return ret_val<edible_rating>::make_success(); }
// TODO: Move pizza scraping here. // Same for other kinds of nutrition alterations // This is used by item display, making actual nutrition available to player. int player::nutrition_for( const item &comest ) const { static const trait_id trait_CARNIVORE( "CARNIVORE" ); static const trait_id trait_GIZZARD( "GIZZARD" ); static const trait_id trait_SAPROPHAGE( "SAPROPHAGE" ); static const std::string flag_CARNIVORE_OK( "CARNIVORE_OK" ); if( !comest.is_comestible() ) { return 0; } // As float to avoid rounding too many times float nutr = comest.type->comestible->nutr; if( has_trait( trait_GIZZARD ) ) { nutr *= 0.6f; } if( has_trait( trait_CARNIVORE ) && comest.has_flag( flag_CARNIVORE_OK ) && comest.has_any_flag( carnivore_blacklist ) ) { // TODO: Comment pizza scrapping nutr *= 0.5f; } const float relative_rot = comest.get_relative_rot(); // Saprophages get full nutrition from rotting food if( relative_rot > 1.0f && !has_trait( trait_SAPROPHAGE ) ) { // everyone else only gets a portion of the nutrition // Scaling linearly from 100% at just-rotten to 0 at halfway-rotten-away const float rottedness = clamp( 2 * relative_rot - 2.0f, 0.1f, 1.0f ); nutr *= ( 1.0f - rottedness ); } // Bio-digestion gives extra nutrition if( has_bionic( bio_digestion ) ) { nutr *= 1.5f; } return ( int )nutr; }
void player::consume_effects( const item &food ) { if( !food.is_comestible() ) { debugmsg( "called player::consume_effects with non-comestible" ); return; } const auto &comest = *food.type->comestible; const int capacity = stomach_capacity(); if( has_trait( trait_id( "THRESH_PLANT" ) ) && food.type->can_use( "PLANTBLECH" ) ) { // Just keep nutrition capped, to prevent vomiting cap_nutrition_thirst( *this, capacity, true, true ); return; } if( ( has_trait( trait_id( "HERBIVORE" ) ) || has_trait( trait_id( "RUMINANT" ) ) ) && food.has_any_flag( herbivore_blacklist ) ) { // No good can come of this. return; } // Rotten food causes health loss const float relative_rot = food.get_relative_rot(); if( relative_rot > 1.0f && !has_trait( trait_id( "SAPROPHAGE" ) ) && !has_trait( trait_id( "SAPROVORE" ) ) && !has_bionic( bio_digestion ) ) { const float rottedness = clamp( 2 * relative_rot - 2.0f, 0.1f, 1.0f ); // ~-1 health per 1 nutrition at halfway-rotten-away, ~0 at "just got rotten" // But always round down int h_loss = -rottedness * comest.nutr; mod_healthy_mod( h_loss, -200 ); add_msg( m_debug, "%d health from %0.2f%% rotten food", h_loss, rottedness ); } const auto nutr = nutrition_for( food ); mod_hunger( -nutr ); mod_thirst( -comest.quench ); mod_stomach_food( nutr ); mod_stomach_water( comest.quench ); if( comest.healthy != 0 ) { // Effectively no cap on health modifiers from food mod_healthy_mod( comest.healthy, ( comest.healthy >= 0 ) ? 200 : -200 ); } if( comest.stim != 0 && ( abs( stim ) < ( abs( comest.stim ) * 3 ) || sgn( stim ) != sgn( comest.stim ) ) ) { if( comest.stim < 0 ) { stim = std::max( comest.stim * 3, stim + comest.stim ); } else { stim = std::min( comest.stim * 3, stim + comest.stim ); } } add_addiction( comest.add, comest.addict ); if( addiction_craving( comest.add ) != MORALE_NULL ) { rem_morale( addiction_craving( comest.add ) ); } // Morale is in minutes int morale_time = HOURS( 2 ) / MINUTES( 1 ); if( food.has_flag( "HOT" ) && food.has_flag( "EATEN_HOT" ) ) { morale_time = HOURS( 3 ) / MINUTES( 1 ); int clamped_nutr = std::max( 5, std::min( 20, nutr / 10 ) ); add_morale( MORALE_FOOD_HOT, clamped_nutr, 20, morale_time, morale_time / 2 ); } std::pair<int, int> fun = fun_for( food ); if( fun.first < 0 ) { add_morale( MORALE_FOOD_BAD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } else if( fun.first > 0 ) { add_morale( MORALE_FOOD_GOOD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } const bool hibernate = has_active_mutation( trait_id( "HIBERNATE" ) ); if( hibernate ) { if( ( nutr > 0 && get_hunger() < -60 ) || ( comest.quench > 0 && get_thirst() < -60 ) ) { //Tell the player what's going on add_msg_if_player( _( "You gorge yourself, preparing to hibernate." ) ); if( one_in( 2 ) ) { //50% chance of the food tiring you mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -200 ) || ( comest.quench > 0 && get_thirst() < -200 ) ) { //Hibernation should cut burn to 60/day add_msg_if_player( _( "You feel stocked for a day or two. Got your bed all ready and secured?" ) ); if( one_in( 2 ) ) { //And another 50%, intended cumulative mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -400 ) || ( comest.quench > 0 && get_thirst() < -400 ) ) { add_msg_if_player( _( "Mmm. You can still fit some more in...but maybe you should get comfortable and sleep." ) ); if( !one_in( 3 ) ) { //Third check, this one at 66% mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -600 ) || ( comest.quench > 0 && get_thirst() < -600 ) ) { add_msg_if_player( _( "That filled a hole! Time for bed..." ) ); // At this point, you're done. Schlaf gut. mod_fatigue( nutr ); } } // Moved here and changed a bit - it was too complex // Incredibly minor stuff like this shouldn't require complexity if( !is_npc() && has_trait( trait_id( "SLIMESPAWNER" ) ) && ( get_hunger() < capacity + 40 || get_thirst() < capacity + 40 ) ) { add_msg_if_player( m_mixed, _( "You feel as though you're going to split open! In a good way?" ) ); mod_pain( 5 ); std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( g->is_empty( dest ) ) { valid.push_back( dest ); } } int numslime = 1; for( int i = 0; i < numslime && !valid.empty(); i++ ) { const tripoint target = random_entry_removed( valid ); if( monster *const slime = g->summon_mon( mon_player_blob, target ) ) { slime->friendly = -1; } } mod_hunger( 40 ); mod_thirst( 40 ); //~slimespawns have *small voices* which may be the Nice equivalent //~of the Rat King's ALL CAPS invective. Probably shared-brain telepathy. add_msg_if_player( m_good, _( "hey, you look like me! let's work together!" ) ); } // Last thing that happens before capping hunger if( get_hunger() < capacity && has_trait( trait_id( "EATHEALTH" ) ) ) { int excess_food = capacity - get_hunger(); add_msg_player_or_npc( _( "You feel the %s filling you out." ), _( "<npcname> looks better after eating the %s." ), food.tname().c_str() ); // Guaranteed 1 HP healing, no matter what. You're welcome. ;-) if( excess_food <= 5 ) { healall( 1 ); } else { // Straight conversion, except it's divided amongst all your body parts. healall( excess_food /= 5 ); } // Note: We want this here to prevent "you can't finish this" messages set_hunger( capacity ); } cap_nutrition_thirst( *this, capacity, nutr > 0, comest.quench > 0 ); }
void player::consume_effects( item &food, bool rotten ) { if( !food.is_food() ) { debugmsg( "called player::consume_effects with non-comestible" ); return; } const auto comest = food.type->comestible.get(); const int capacity = stomach_capacity(); if( has_trait( "THRESH_PLANT" ) && food.type->can_use( "PLANTBLECH" ) ) { // Just keep nutrition capped, to prevent vomiting cap_nutrition_thirst( *this, capacity, true, true ); return; } if( ( has_trait( "HERBIVORE" ) || has_trait( "RUMINANT" ) ) && food.has_any_flag( herbivore_blacklist ) ) { // No good can come of this. return; } float factor = 1.0f; float hunger_factor = 1.0f; bool unhealthy_allowed = true; if( has_trait( "GIZZARD" ) ) { factor *= 0.6f; } if( has_trait( "CARNIVORE" ) && food.has_flag( "CARNIVORE_OK" ) ) { // At least partially edible if( food.has_any_flag( carnivore_blacklist ) ) { // Other things are in it, we only get partial benefits add_msg_if_player( _( "You pick out the edible parts and throw away the rest." ) ); factor *= 0.5f; } else { // Carnivores don't get unhealthy off pure meat diets unhealthy_allowed = false; } } // Saprophages get full nutrition from rotting food if( rotten && !has_trait( "SAPROPHAGE" ) ) { // everyone else only gets a portion of the nutrition hunger_factor *= rng_float( 0, 1 ); // and takes a health penalty if they aren't adapted if( !has_trait( "SAPROVORE" ) && !has_bionic( "bio_digestion" ) ) { mod_healthy_mod( -30, -200 ); } } // Bio-digestion gives extra nutrition if( has_bionic( "bio_digestion" ) ) { hunger_factor += rng_float( 0, 1 ); } const auto nutr = nutrition_for( food.type ); mod_hunger( -nutr * factor * hunger_factor ); mod_thirst( -comest->quench * factor ); mod_stomach_food( nutr * factor * hunger_factor ); mod_stomach_water( comest->quench * factor ); if( unhealthy_allowed || comest->healthy > 0 ) { // Effectively no cap on health modifiers from food mod_healthy_mod( comest->healthy, ( comest->healthy >= 0 ) ? 200 : -200 ); } if( comest->stim != 0 && ( abs( stim ) < ( abs( comest->stim ) * 3 ) || sgn( stim ) != sgn( comest->stim ) ) ) { if( comest->stim < 0 ) { stim = std::max( comest->stim * 3, stim + comest->stim ); } else { stim = std::min( comest->stim * 3, stim + comest->stim ); } } add_addiction( comest->add, comest->addict ); if( addiction_craving( comest->add ) != MORALE_NULL ) { rem_morale( addiction_craving( comest->add ) ); } if( food.has_flag( "HOT" ) && food.has_flag( "EATEN_HOT" ) ) { add_morale( MORALE_FOOD_HOT, 5, 10 ); } auto fun = comest->fun; if( food.has_flag( "COLD" ) && food.has_flag( "EATEN_COLD" ) && fun > 0 ) { if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun * 3, fun * 3, 60, 30, false, food.type ); } else { fun = 1; } } const bool gourmand = has_trait( "GOURMAND" ); const bool hibernate = has_active_mutation( "HIBERNATE" ); if( gourmand ) { if( fun < -2 ) { add_morale( MORALE_FOOD_BAD, fun * 0.5, fun, 60, 30, false, food.type ); } else if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun * 3, fun * 6, 60, 30, false, food.type ); } } else if( fun < 0 ) { add_morale( MORALE_FOOD_BAD, fun, fun * 6, 60, 30, false, food.type ); } else if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun, fun * 4, 60, 30, false, food.type ); } if( hibernate ) { if( ( nutr > 0 && get_hunger() < -60 ) || ( comest->quench > 0 && get_thirst() < -60 ) ) { //Tell the player what's going on add_msg_if_player( _( "You gorge yourself, preparing to hibernate." ) ); if( one_in( 2 ) ) { //50% chance of the food tiring you mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -200 ) || ( comest->quench > 0 && get_thirst() < -200 ) ) { //Hibernation should cut burn to 60/day add_msg_if_player( _( "You feel stocked for a day or two. Got your bed all ready and secured?" ) ); if( one_in( 2 ) ) { //And another 50%, intended cumulative mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -400 ) || ( comest->quench > 0 && get_thirst() < -400 ) ) { add_msg_if_player( _( "Mmm. You can still fit some more in...but maybe you should get comfortable and sleep." ) ); if( !one_in( 3 ) ) { //Third check, this one at 66% mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -600 ) || ( comest->quench > 0 && get_thirst() < -600 ) ) { add_msg_if_player( _( "That filled a hole! Time for bed..." ) ); // At this point, you're done. Schlaf gut. mod_fatigue( nutr ); } } // Moved here and changed a bit - it was too complex // Incredibly minor stuff like this shouldn't require complexity if( !is_npc() && has_trait( "SLIMESPAWNER" ) && ( get_hunger() < capacity + 40 || get_thirst() < capacity + 40 ) ) { add_msg_if_player( m_mixed, _( "You feel as though you're going to split open! In a good way?" ) ); mod_pain( 5 ); std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( g->is_empty( dest ) ) { valid.push_back( dest ); } } int numslime = 1; for( int i = 0; i < numslime && !valid.empty(); i++ ) { const tripoint target = random_entry_removed( valid ); if( g->summon_mon( mon_player_blob, target ) ) { monster *slime = g->monster_at( target ); slime->friendly = -1; } } mod_hunger( 40 ); mod_thirst( 40 ); //~slimespawns have *small voices* which may be the Nice equivalent //~of the Rat King's ALL CAPS invective. Probably shared-brain telepathy. add_msg_if_player( m_good, _( "hey, you look like me! let's work together!" ) ); } // Last thing that happens before capping hunger if( get_hunger() < capacity && has_trait( "EATHEALTH" ) ) { int excess_food = capacity - get_hunger(); add_msg_player_or_npc( _( "You feel the %s filling you out." ), _( "<npcname> looks better after eating the %s." ), food.tname().c_str() ); // Guaranteed 1 HP healing, no matter what. You're welcome. ;-) if( excess_food <= 5 ) { healall( 1 ); } else { // Straight conversion, except it's divided amongst all your body parts. healall( excess_food /= 5 ); } // Note: We want this here to prevent "you can't finish this" messages set_hunger( capacity ); } cap_nutrition_thirst( *this, capacity, nutr > 0, comest->quench > 0 ); }
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; }