Esempio n. 1
0
// This must be called when any of the following change:
// - effects
// - bionics
// - traits
// - underwater
// - clothes
// With the exception of clothes, all changes to these character attributes must
// occur through a function in this class which calls this function. Clothes are
// typically added/removed with wear() and takeoff(), but direct access to the
// 'wears' vector is still allowed due to refactor exhaustion.
void Character::recalc_sight_limits()
{
    sight_max = 9999;
    sight_boost = 0;
    sight_boost_cap = 0;

    // Set sight_max.
    if (has_effect("blind")) {
        sight_max = 0;
    } else if (has_effect("in_pit") ||
            (has_effect("boomered") && (!(has_trait("PER_SLIME_OK")))) ||
            (underwater && !has_bionic("bio_membrane") &&
                !has_trait("MEMBRANE") && !worn_with_flag("SWIM_GOGGLES") &&
                !has_trait("CEPH_EYES") && !has_trait("PER_SLIME_OK") ) ) {
        sight_max = 1;
    } else if (has_active_mutation("SHELL2")) {
        // You can kinda see out a bit.
        sight_max = 2;
    } else if ( (has_trait("MYOPIC") || has_trait("URSINE_EYE")) &&
            !is_wearing("glasses_eye") && !is_wearing("glasses_monocle") &&
            !is_wearing("glasses_bifocal") && !has_effect("contacts")) {
        sight_max = 4;
    } else if (has_trait("PER_SLIME")) {
        sight_max = 6;
    }

    // Set sight_boost and sight_boost_cap, based on night vision.
    // (A player will never have more than one night vision trait.)
    sight_boost_cap = 12;
    // Debug-only NV, by vache's request
    if (has_trait("DEBUG_NIGHTVISION")) {
        sight_boost = 59;
        sight_boost_cap = 59;
    } else if (has_nv() || is_wearing("rm13_armor_on") || has_active_mutation("NIGHTVISION3") ||
        has_active_mutation("ELFA_FNV") || (has_active_mutation("CEPH_VISION")) ) {
        // Yes, I'm breaking the cap. I doubt the reality bubble shrinks at night.
        // BIRD_EYE represents excellent fine-detail vision so I think it works.
        if (has_trait("BIRD_EYE")) {
            sight_boost_cap = 13;
        }
        sight_boost = sight_boost_cap;
    } else if (has_active_mutation("ELFA_NV")) {
        sight_boost = 6; // Elf-a and Bird eyes shouldn't coexist
    } else if (has_active_mutation("NIGHTVISION2") || has_active_mutation("FEL_NV") ||
        has_active_mutation("URSINE_EYE")) {
        if (has_trait("BIRD_EYE")) {
            sight_boost = 5;
        } else {
            sight_boost = 4;
         }
    } else if (has_active_mutation("NIGHTVISION")) {
        if (has_trait("BIRD_EYE")) {
            sight_boost = 2;
        } else {
            sight_boost = 1;
        }
    }
}
Esempio n. 2
0
int Character::volume_capacity() const
{
    int ret = 2; // A small bonus (the overflow)
    for (auto &i : worn) {
        ret += i.get_storage();
    }
    if (has_bionic("bio_storage")) {
        ret += 8;
    }
    if (has_trait("SHELL")) {
        ret += 16;
    }
    if (has_trait("SHELL2") && !has_active_mutation("SHELL2")) {
        ret += 24;
    }
    if (has_trait("PACKMULE")) {
        ret = int(ret * 1.4);
    }
    if (has_trait("DISORGANIZED")) {
        ret = int(ret * 0.6);
    }
    if (ret < 2) {
        ret = 2;
    }
    return ret;
}
Esempio n. 3
0
int player::stomach_capacity() const
{
    if( has_trait( trait_id( "GIZZARD" ) ) ) {
        return 0;
    }

    if( has_active_mutation( trait_id( "HIBERNATE" ) ) ) {
        return -620;
    }

    if( has_trait( trait_id( "GOURMAND" ) ) || has_trait( trait_id( "HIBERNATE" ) ) ) {
        return -60;
    }

    return -20;
}
Esempio n. 4
0
int Character::volume_capacity() const
{
    int ret = 0;
    for (auto &i : worn) {
        ret += i.get_storage();
    }
    if (has_bionic("bio_storage")) {
        ret += 8;
    }
    if (has_trait("SHELL")) {
        ret += 16;
    }
    if (has_trait("SHELL2") && !has_active_mutation("SHELL2")) {
        ret += 24;
    }
    if (has_trait("PACKMULE")) {
        ret = int(ret * 1.4);
    }
    if (has_trait("DISORGANIZED")) {
        ret = int(ret * 0.6);
    }
    ret = std::max(ret, 0);
    return ret;
}
Esempio n. 5
0
void Character::reset_stats()
{
    // Bionic buffs
    if (has_active_bionic("bio_hydraulics"))
        mod_str_bonus(20);
    if (has_bionic("bio_eye_enhancer"))
        mod_per_bonus(2);
    if (has_bionic("bio_str_enhancer"))
        mod_str_bonus(2);
    if (has_bionic("bio_int_enhancer"))
        mod_int_bonus(2);
    if (has_bionic("bio_dex_enhancer"))
        mod_dex_bonus(2);

    // Trait / mutation buffs
    if (has_trait("THICK_SCALES")) {
        mod_dex_bonus(-2);
    }
    if (has_trait("CHITIN2") || has_trait("CHITIN3") || has_trait("CHITIN_FUR3")) {
        mod_dex_bonus(-1);
    }
    if (has_trait("BIRD_EYE")) {
        mod_per_bonus(4);
    }
    if (has_trait("INSECT_ARMS")) {
        mod_dex_bonus(-2);
    }
    if (has_trait("WEBBED")) {
        mod_dex_bonus(-1);
    }
    if (has_trait("ARACHNID_ARMS")) {
        mod_dex_bonus(-4);
    }
    if (has_trait("ARM_TENTACLES") || has_trait("ARM_TENTACLES_4") ||
            has_trait("ARM_TENTACLES_8")) {
        mod_dex_bonus(1);
    }

    // Dodge-related effects
    if (has_trait("TAIL_LONG")) {
        mod_dodge_bonus(2);
    }
    if (has_trait("TAIL_CATTLE")) {
        mod_dodge_bonus(1);
    }
    if (has_trait("TAIL_RAT")) {
        mod_dodge_bonus(2);
    }
    if (has_trait("TAIL_THICK") && !(has_active_mutation("TAIL_THICK")) ) {
        mod_dodge_bonus(1);
    }
    if (has_trait("TAIL_RAPTOR")) {
        mod_dodge_bonus(3);
    }
    if (has_trait("TAIL_FLUFFY")) {
        mod_dodge_bonus(4);
    }
    if (has_trait("WINGS_BAT")) {
        mod_dodge_bonus(-3);
    }
    if (has_trait("WINGS_BUTTERFLY")) {
        mod_dodge_bonus(-4);
    }

    ///\EFFECT_STR_MAX above 15 decreases Dodge bonus by 1 (NEGATIVE)
    if (str_max >= 16) {mod_dodge_bonus(-1);} // Penalty if we're huge
    ///\EFFECT_STR_MAX below 6 increases Dodge bonus by 1
    else if (str_max <= 5) {mod_dodge_bonus(1);} // Bonus if we're small

    nv_cached = false;

    // Reset our stats to normal levels
    // Any persistent buffs/debuffs will take place in effects,
    // player::suffer(), etc.

    // repopulate the stat fields
    str_cur = str_max + get_str_bonus();
    dex_cur = dex_max + get_dex_bonus();
    per_cur = per_max + get_per_bonus();
    int_cur = int_max + get_int_bonus();

    // Floor for our stats.  No stat changes should occur after this!
    if( dex_cur < 0 ) {
        dex_cur = 0;
    }
    if( str_cur < 0 ) {
        str_cur = 0;
    }
    if( per_cur < 0 ) {
        per_cur = 0;
    }
    if( int_cur < 0 ) {
        int_cur = 0;
    }

    // Does nothing! TODO: Remove
    Creature::reset_stats();
}
Esempio n. 6
0
// This must be called when any of the following change:
// - effects
// - bionics
// - traits
// - underwater
// - clothes
// With the exception of clothes, all changes to these character attributes must
// occur through a function in this class which calls this function. Clothes are
// typically added/removed with wear() and takeoff(), but direct access to the
// 'wears' vector is still allowed due to refactor exhaustion.
void Character::recalc_sight_limits()
{
    sight_max = 9999;
    vision_mode_cache.reset();

    // Set sight_max.
    if (has_effect("blind") || worn_with_flag("BLIND") || has_active_bionic("bio_blindfold")) {
        sight_max = 0;
    } else if( has_effect("boomered") && (!(has_trait("PER_SLIME_OK"))) ) {
        sight_max = 1;
        vision_mode_cache.set( BOOMERED );
    } else if (has_effect("in_pit") ||
            (underwater && !has_bionic("bio_membrane") &&
                !has_trait("MEMBRANE") && !worn_with_flag("SWIM_GOGGLES") &&
                !has_trait("CEPH_EYES") && !has_trait("PER_SLIME_OK") ) ) {
        sight_max = 1;
    } else if (has_active_mutation("SHELL2")) {
        // You can kinda see out a bit.
        sight_max = 2;
    } else if ( (has_trait("MYOPIC") || has_trait("URSINE_EYE")) &&
            !is_wearing("glasses_eye") && !is_wearing("glasses_monocle") &&
            !is_wearing("glasses_bifocal") && !has_effect("contacts")) {
        sight_max = 4;
    } else if (has_trait("PER_SLIME")) {
        sight_max = 6;
    } else if( has_effect( "darkness" ) ) {
        vision_mode_cache.set( DARKNESS );
        sight_max = 10;
    }

    // Debug-only NV, by vache's request
    if( has_trait("DEBUG_NIGHTVISION") ) {
        vision_mode_cache.set( DEBUG_NIGHTVISION );
    }
    if( has_nv() ) {
        vision_mode_cache.set( NV_GOGGLES );
    }
    if( has_active_mutation("NIGHTVISION3") || is_wearing("rm13_armor_on") ) {
        vision_mode_cache.set( NIGHTVISION_3 );
    }
    if( has_active_mutation("ELFA_FNV") ) {
        vision_mode_cache.set( FULL_ELFA_VISION );
    }
    if( has_active_mutation("CEPH_VISION") ) {
        vision_mode_cache.set( CEPH_VISION );
    }
    if (has_active_mutation("ELFA_NV")) {
        vision_mode_cache.set( ELFA_VISION );
    }
    if( has_active_mutation("NIGHTVISION2") ) {
        vision_mode_cache.set( NIGHTVISION_2 );
    }
    if( has_active_mutation("FEL_NV") ) {
        vision_mode_cache.set( FELINE_VISION );
    }
    if( has_active_mutation("URSINE_EYE") ) {
        vision_mode_cache.set( URSINE_VISION );
    }
    if (has_active_mutation("NIGHTVISION")) {
        vision_mode_cache.set(NIGHTVISION_1);
    }
    if( has_trait("BIRD_EYE") ) {
        vision_mode_cache.set( BIRD_EYE);
    }
}
Esempio n. 7
0
void Character::reset_stats()
{
    Creature::reset_stats();
    
    // Bionic buffs
    if (has_active_bionic("bio_hydraulics"))
        mod_str_bonus(20);
    if (has_bionic("bio_eye_enhancer"))
        mod_per_bonus(2);
    if (has_bionic("bio_str_enhancer"))
        mod_str_bonus(2);
    if (has_bionic("bio_int_enhancer"))
        mod_int_bonus(2);
    if (has_bionic("bio_dex_enhancer"))
        mod_dex_bonus(2);

    // Trait / mutation buffs
    if (has_trait("THICK_SCALES")) {
        mod_dex_bonus(-2);
    }
    if (has_trait("CHITIN2") || has_trait("CHITIN3") || has_trait("CHITIN_FUR3")) {
        mod_dex_bonus(-1);
    }
    if (has_trait("BIRD_EYE")) {
        mod_per_bonus(4);
    }
    if (has_trait("INSECT_ARMS")) {
        mod_dex_bonus(-2);
    }
    if (has_trait("WEBBED")) {
        mod_dex_bonus(-1);
    }
    if (has_trait("ARACHNID_ARMS")) {
        mod_dex_bonus(-4);
    }
    if (has_trait("ARM_TENTACLES") || has_trait("ARM_TENTACLES_4") ||
            has_trait("ARM_TENTACLES_8")) {
        mod_dex_bonus(1);
    }

    // Dodge-related effects
    if (has_trait("TAIL_LONG")) {
        mod_dodge_bonus(2);
    }
    if (has_trait("TAIL_CATTLE")) {
        mod_dodge_bonus(1);
    }
    if (has_trait("TAIL_RAT")) {
        mod_dodge_bonus(2);
    }
    if (has_trait("TAIL_THICK") && !(has_active_mutation("TAIL_THICK")) ) {
        mod_dodge_bonus(1);
    }
    if (has_trait("TAIL_RAPTOR")) {
        mod_dodge_bonus(3);
    }
    if (has_trait("TAIL_FLUFFY")) {
        mod_dodge_bonus(4);
    }
    if (has_trait("WINGS_BAT")) {
        mod_dodge_bonus(-3);
    }
    if (has_trait("WINGS_BUTTERFLY")) {
        mod_dodge_bonus(-4);
    }

    if (str_max >= 16) {mod_dodge_bonus(-1);} // Penalty if we're huge
    else if (str_max <= 5) {mod_dodge_bonus(1);} // Bonus if we're small

    nv_cached = false;
}
Esempio n. 8
0
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 );
}
Esempio n. 9
0
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;
}
Esempio n. 10
0
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();
}
Esempio n. 11
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 );
}
Esempio n. 12
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;
}