From cd4c6872446f4ff1e32d1555be17e36018e3576a Mon Sep 17 00:00:00 2001 From: Fris0uman Date: Sun, 19 Jan 2020 12:22:19 +0100 Subject: [PATCH] move body and health update to character --- src/character.cpp | 893 ++++++++++++++++++++++++++++++++++++++++++++ src/character.h | 43 ++- src/consumption.cpp | 4 +- src/game.cpp | 1 - src/player.cpp | 879 ------------------------------------------- src/player.h | 46 --- 6 files changed, 937 insertions(+), 929 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 3ff0e669b44d0..926bce8312679 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -17,6 +17,7 @@ #include "coordinate_conversions.h" #include "debug.h" #include "effect.h" +#include "enums.h" #include "event_bus.h" #include "field.h" #include "fungal_effects.h" @@ -44,6 +45,7 @@ #include "player.h" #include "ret_val.h" #include "scent_map.h" +#include "submap.h" #include "skill.h" #include "skill_boost.h" #include "sounds.h" @@ -83,6 +85,9 @@ static const bionic_id bio_gills( "bio_gills" ); static const bionic_id bio_heatsink( "bio_heatsink" ); static const bionic_id bio_laser( "bio_laser" ); static const bionic_id bio_lighter( "bio_lighter" ); +static const bionic_id bio_memory( "bio_memory" ); +static const bionic_id bio_recycler( "bio_recycler" ); +static const bionic_id bio_synaptic_regen( "bio_synaptic_regen" ); static const bionic_id bio_tattoo_led( "bio_tattoo_led" ); static const bionic_id bio_tools( "bio_tools" ); static const bionic_id bio_ups( "bio_ups" ); @@ -123,8 +128,11 @@ static const efftype_id effect_hot( "hot" ); static const efftype_id effect_hot_speed( "hot_speed" ); static const efftype_id effect_infected( "infected" ); static const efftype_id effect_in_pit( "in_pit" ); +static const efftype_id effect_jetinjector( "jetinjector" ); +static const efftype_id effect_lack_sleep( "lack_sleep" ); static const efftype_id effect_lightsnare( "lightsnare" ); static const efftype_id effect_lying_down( "lying_down" ); +static const efftype_id effect_magnesium_supplements( "magnesium" ); static const efftype_id effect_mending( "mending" ); static const efftype_id effect_narcosis( "narcosis" ); static const efftype_id effect_nausea( "nausea" ); @@ -158,12 +166,15 @@ static const trait_id trait_BARK( "BARK" ); static const trait_id trait_BIRD_EYE( "BIRD_EYE" ); static const trait_id trait_CEPH_EYES( "CEPH_EYES" ); static const trait_id trait_CEPH_VISION( "CEPH_VISION" ); +static const trait_id trait_CHLOROMORPH( "CHLOROMORPH" ); static const trait_id trait_DEAF( "DEAF" ); static const trait_id trait_DEBUG_CLOAK( "DEBUG_CLOAK" ); +static const trait_id trait_DEBUG_LS( "DEBUG_LS" ); static const trait_id trait_DEBUG_NIGHTVISION( "DEBUG_NIGHTVISION" ); static const trait_id trait_DEBUG_NODMG( "DEBUG_NODMG" ); static const trait_id trait_DEBUG_NOTEMP( "DEBUG_NOTEMP" ); static const trait_id trait_DISORGANIZED( "DISORGANIZED" ); +static const trait_id trait_DISRESISTANT( "DISRESISTANT" ); static const trait_id trait_DOWN( "DOWN" ); static const trait_id trait_ELECTRORECEPTORS( "ELECTRORECEPTORS" ); static const trait_id trait_ELFA_FNV( "ELFA_FNV" ); @@ -176,22 +187,31 @@ static const trait_id trait_GLASSJAW( "GLASSJAW" ); static const trait_id trait_HORNS_POINTED( "HORNS_POINTED" ); static const trait_id trait_HOARDER( "HOARDER" ); static const trait_id trait_HOLLOW_BONES( "HOLLOW_BONES" ); +static const trait_id trait_HUGE( "HUGE" ); +static const trait_id trait_HUGE_OK( "HUGE_OK" ); static const trait_id trait_LEG_TENT_BRACE( "LEG_TENT_BRACE" ); static const trait_id trait_LIGHT_BONES( "LIGHT_BONES" ); static const trait_id trait_M_SKIN2( "M_SKIN2" ); static const trait_id trait_M_SKIN3( "M_SKIN3" ); +static const trait_id trait_M_DEPENDENT( "M_DEPENDENT" ); static const trait_id trait_MEMBRANE( "MEMBRANE" ); static const trait_id trait_MYOPIC( "MYOPIC" ); static const trait_id trait_NIGHTVISION2( "NIGHTVISION2" ); static const trait_id trait_NIGHTVISION3( "NIGHTVISION3" ); static const trait_id trait_NIGHTVISION( "NIGHTVISION" ); +static const trait_id trait_NO_THIRST( "NO_THIRST" ); +static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_NOMAD( "NOMAD" ); static const trait_id trait_NOMAD2( "NOMAD2" ); static const trait_id trait_NOMAD3( "NOMAD3" ); static const trait_id trait_PACKMULE( "PACKMULE" ); static const trait_id trait_PER_SLIME_OK( "PER_SLIME_OK" ); static const trait_id trait_PER_SLIME( "PER_SLIME" ); +static const trait_id trait_PRED2( "PRED2" ); +static const trait_id trait_PRED3( "PRED3" ); +static const trait_id trait_PRED4( "PRED4" ); static const trait_id trait_PYROMANIA( "PYROMANIA" ); +static const trait_id trait_RADIOGENIC( "RADIOGENIC" ); static const trait_id trait_ROOTS2( "ROOTS2" ); static const trait_id trait_ROOTS3( "ROOTS3" ); static const trait_id trait_SEESLEEP( "SEESLEEP" ); @@ -203,12 +223,17 @@ static const trait_id trait_SHOUT3( "SHOUT3" ); static const trait_id trait_SLIMY( "SLIMY" ); static const trait_id trait_SQUEAMISH( "SQUEAMISH" ); static const trait_id trait_STRONGSTOMACH( "STRONGSTOMACH" ); +static const trait_id trait_TRANSPIRATION( "TRANSPIRATION" ); static const trait_id trait_THRESH_CEPHALOPOD( "THRESH_CEPHALOPOD" ); static const trait_id trait_THRESH_INSECT( "THRESH_INSECT" ); static const trait_id trait_THRESH_PLANT( "THRESH_PLANT" ); static const trait_id trait_THRESH_SPIDER( "THRESH_SPIDER" ); static const trait_id trait_URSINE_EYE( "URSINE_EYE" ); static const trait_id trait_VISCOUS( "VISCOUS" ); +static const trait_id trait_WATERSLEEP( "WATERSLEEP" ); +static const trait_id trait_WEB_SPINNER( "WEB_SPINNER" ); +static const trait_id trait_WEB_WALKER( "WEB_WALKER" ); +static const trait_id trait_WEB_WEAVER( "WEB_WEAVER" ); static const trait_id trait_WOOLALLERGY( "WOOLALLERGY" ); static const trait_id debug_nodmg( "DEBUG_NODMG" ); @@ -2581,6 +2606,28 @@ std::string Character::enumerate_unmet_requirements( const item &it, const item return enumerate_as_string( unmet_reqs ); } +int Character::rust_rate( bool return_stat_effect ) const +{ + if( get_option( "SKILL_RUST" ) == "off" ) { + return 0; + } + + // Stat window shows stat effects on based on current stat + int intel = get_int(); + /** @EFFECT_INT reduces skill rust */ + int ret = ( ( get_option( "SKILL_RUST" ) == "vanilla" || + get_option( "SKILL_RUST" ) == "capped" ) ? 500 : 500 - 35 * ( intel - 8 ) ); + + ret *= mutation_value( "skill_rust_multiplier" ); + + if( ret < 0 ) { + ret = 0; + } + + // return_stat_effect actually matters here + return ( return_stat_effect ? ret : ret / 10 ); +} + bool Character::meets_skill_requirements( const std::map &req, const item &context ) const { @@ -2685,6 +2732,55 @@ void Character::apply_skill_boost() } } +void Character::do_skill_rust() +{ + const int rate = rust_rate(); + if( rate <= 0 ) { + return; + } + for( auto &pair : *_skills ) { + if( rate <= rng( 0, 1000 ) ) { + continue; + } + + const Skill &aSkill = *pair.first; + SkillLevel &skill_level_obj = pair.second; + + if( aSkill.is_combat_skill() && + ( ( has_trait( trait_PRED2 ) && one_in( 4 ) ) || + ( has_trait( trait_PRED3 ) && one_in( 2 ) ) || + ( has_trait( trait_PRED4 ) && x_in_y( 2, 3 ) ) ) ) { + // Their brain is optimized to remember this + if( one_in( 15600 ) ) { + // They've already passed the roll to avoid rust at + // this point, but print a message about it now and + // then. + // + // 13 combat skills, 600 turns/hr, 7800 tests/hr. + // This means PRED2/PRED3/PRED4 think of hunting on + // average every 8/4/3 hours, enough for immersion + // without becoming an annoyance. + // + add_msg_if_player( _( "Your heart races as you recall your most recent hunt." ) ); + mod_stim( 1 ); + } + continue; + } + + const bool charged_bio_mem = get_power_level() > 25_kJ && has_active_bionic( bio_memory ); + const int oldSkillLevel = skill_level_obj.level(); + if( skill_level_obj.rust( charged_bio_mem ) ) { + add_msg_if_player( m_warning, + _( "Your knowledge of %s begins to fade, but your memory banks retain it!" ), aSkill.name() ); + mod_power_level( -25_kJ ); + } + const int newSkill = skill_level_obj.level(); + if( newSkill < oldSkillLevel ) { + add_msg_if_player( m_bad, _( "Your skill in %s has reduced to %d!" ), aSkill.name(), newSkill ); + } + } +} + void Character::reset_stats() { // Bionic buffs @@ -3694,6 +3790,78 @@ int Character::get_max_healthy() const ( bmi - character_weight_category::overweight ) + 200 ) ), -200, 200 ); } +void Character::regen( int rate_multiplier ) +{ + int pain_ticks = rate_multiplier; + while( get_pain() > 0 && pain_ticks-- > 0 ) { + mod_pain( -roll_remainder( 0.2f + get_pain() / 50.0f ) ); + } + + float rest = rest_quality(); + float heal_rate = healing_rate( rest ) * to_turns( 5_minutes ); + if( heal_rate > 0.0f ) { + healall( roll_remainder( rate_multiplier * heal_rate ) ); + } else if( heal_rate < 0.0f ) { + int rot_rate = roll_remainder( rate_multiplier * -heal_rate ); + // Has to be in loop because some effects depend on rounding + while( rot_rate-- > 0 ) { + hurtall( 1, nullptr, false ); + } + } + + // include healing effects + for( int i = 0; i < num_hp_parts; i++ ) { + body_part bp = hp_to_bp( static_cast( i ) ); + float healing = healing_rate_medicine( rest, bp ) * to_turns( 5_minutes ); + + int healing_apply = roll_remainder( healing ); + healed_bp( i, healing_apply ); + heal( bp, healing_apply ); + if( damage_bandaged[i] > 0 ) { + damage_bandaged[i] -= healing_apply; + if( damage_bandaged[i] <= 0 ) { + damage_bandaged[i] = 0; + remove_effect( effect_bandaged, bp ); + add_msg_if_player( _( "Bandaged wounds on your %s was healed." ), body_part_name( bp ) ); + } + } + if( damage_disinfected[i] > 0 ) { + damage_disinfected[i] -= healing_apply; + if( damage_disinfected[i] <= 0 ) { + damage_disinfected[i] = 0; + remove_effect( effect_disinfected, bp ); + add_msg_if_player( _( "Disinfected wounds on your %s was healed." ), body_part_name( bp ) ); + } + } + + // remove effects if the limb was healed by other way + if( has_effect( effect_bandaged, bp ) && ( hp_cur[i] == hp_max[i] ) ) { + damage_bandaged[i] = 0; + remove_effect( effect_bandaged, bp ); + add_msg_if_player( _( "Bandaged wounds on your %s was healed." ), body_part_name( bp ) ); + } + if( has_effect( effect_disinfected, bp ) && ( hp_cur[i] == hp_max[i] ) ) { + damage_disinfected[i] = 0; + remove_effect( effect_disinfected, bp ); + add_msg_if_player( _( "Disinfected wounds on your %s was healed." ), body_part_name( bp ) ); + } + } + + if( get_rad() > 0 ) { + mod_rad( -roll_remainder( rate_multiplier / 50.0f ) ); + } +} + +void Character::enforce_minimum_healing() +{ + for( int i = 0; i < num_hp_parts; i++ ) { + if( healed_total[i] <= 0 ) { + heal( static_cast( i ), 1 ); + } + healed_total[i] = 0; + } +} + void Character::update_health( int external_modifiers ) { if( has_artifact_with( AEP_SICK ) ) { @@ -3729,6 +3897,604 @@ void Character::update_health( int external_modifiers ) add_msg( m_debug, "Health: %d, Health mod: %d", get_healthy(), get_healthy_mod() ); } +// Returns the number of multiples of tick_length we would "pass" on our way `from` to `to` +// For example, if `tick_length` is 1 hour, then going from 0:59 to 1:01 should return 1 +inline int ticks_between( const time_point &from, const time_point &to, + const time_duration &tick_length ) +{ + return ( to_turn( to ) / to_turns( tick_length ) ) - ( to_turn + ( from ) / to_turns( tick_length ) ); +} + +void Character::update_body() +{ + update_body( calendar::turn - 1_turns, calendar::turn ); +} + +void Character::update_body( const time_point &from, const time_point &to ) +{ + if( !is_npc() ) { + update_stamina( to_turns( to - from ) ); + } + update_stomach( from, to ); + recalculate_enchantment_cache(); + if( ticks_between( from, to, 3_minutes ) > 0 ) { + magic.update_mana( *this->as_player(), to_turns( 3_minutes ) ); + } + const int five_mins = ticks_between( from, to, 5_minutes ); + if( five_mins > 0 ) { + check_needs_extremes(); + update_needs( five_mins ); + regen( five_mins ); + // Note: mend ticks once per 5 minutes, but wants rate in TURNS, not 5 minute intervals + // TODO: change @ref med to take time_duration + mend( five_mins * to_turns( 5_minutes ) ); + } + if( ticks_between( from, to, 24_hours ) > 0 ) { + enforce_minimum_healing(); + } + + const int thirty_mins = ticks_between( from, to, 30_minutes ); + if( thirty_mins > 0 ) { + if( activity.is_null() ) { + reset_activity_level(); + } + // Radiation kills health even at low doses + update_health( has_trait( trait_RADIOGENIC ) ? 0 : -get_rad() ); + get_sick(); + } + + for( const auto &v : vitamin::all() ) { + const time_duration rate = vitamin_rate( v.first ); + if( rate > 0_turns ) { + int qty = ticks_between( from, to, rate ); + if( qty > 0 ) { + vitamin_mod( v.first, 0 - qty ); + } + + } else if( rate < 0_turns ) { + // mutations can result in vitamins being generated (but never accumulated) + int qty = ticks_between( from, to, -rate ); + if( qty > 0 ) { + vitamin_mod( v.first, qty ); + } + } + } + + do_skill_rust(); +} + +void Character::update_stomach( const time_point &from, const time_point &to ) +{ + const needs_rates rates = calc_needs_rates(); + // No food/thirst/fatigue clock at all + const bool debug_ls = has_trait( trait_DEBUG_LS ); + // No food/thirst, capped fatigue clock (only up to tired) + const bool npc_no_food = is_npc() && get_option( "NO_NPC_FOOD" ); + const bool foodless = debug_ls || npc_no_food; + const bool mouse = has_trait( trait_NO_THIRST ); + const bool mycus = has_trait( trait_M_DEPENDENT ); + const float kcal_per_time = get_bmr() / ( 12.0f * 24.0f ); + const int five_mins = ticks_between( from, to, 5_minutes ); + const int half_hours = ticks_between( from, to, 30_minutes ); + const units::volume stomach_capacity = stomach.capacity( *this ); + + if( five_mins > 0 ) { + // Digest nutrients in stomach, they are destined for the guts (except water) + food_summary digested_to_guts = stomach.digest( *this, rates, five_mins, half_hours ); + // Digest nutrients in guts, they will be distributed to needs levels + food_summary digested_to_body = guts.digest( *this, rates, five_mins, half_hours ); + // Water from stomach skips guts and gets absorbed by body + mod_thirst( -units::to_milliliter( digested_to_guts.water ) / 5 ); + guts.ingest( digested_to_guts ); + // Apply nutrients, unless this is an NPC and NO_NPC_FOOD is enabled. + if( !is_npc() || !get_option( "NO_NPC_FOOD" ) ) { + mod_stored_kcal( digested_to_body.nutr.kcal ); + vitamins_mod( digested_to_body.nutr.vitamins, false ); + } + } + if( stomach.time_since_ate() > 10_minutes ) { + if( stomach.contains() >= stomach_capacity && get_hunger() > -61 ) { + // you're engorged! your stomach is full to bursting! + set_hunger( -61 ); + } else if( stomach.contains() >= stomach_capacity / 2 && get_hunger() > -21 ) { + // sated + set_hunger( -21 ); + } else if( stomach.contains() >= stomach_capacity / 8 && get_hunger() > -1 ) { + // that's really all the food you need to feel full + set_hunger( -1 ); + } else if( stomach.contains() == 0_ml ) { + if( guts.get_calories() == 0 && get_stored_kcal() < get_healthy_kcal() && get_hunger() < 300 ) { + // there's no food except what you have stored in fat + set_hunger( 300 ); + } else if( get_hunger() < 100 && ( ( guts.get_calories() == 0 && + get_stored_kcal() >= get_healthy_kcal() ) || get_stored_kcal() < get_healthy_kcal() ) ) { + set_hunger( 100 ); + } else if( get_hunger() < 0 ) { + set_hunger( 0 ); + } + } + if( !foodless && rates.hunger > 0.0f ) { + mod_hunger( roll_remainder( rates.hunger * five_mins ) ); + // instead of hunger keeping track of how you're living, burn calories instead + mod_stored_kcal( -roll_remainder( five_mins * kcal_per_time ) ); + } + } else + // you fill up when you eat fast, but less so than if you eat slow + // if you just ate but your stomach is still empty it will still + // delay your filling up (drugs?) + { + if( stomach.contains() >= stomach_capacity && get_hunger() > -61 ) { + // you're engorged! your stomach is full to bursting! + set_hunger( -61 ); + } else if( stomach.contains() >= stomach_capacity * 3 / 4 && get_hunger() > -21 ) { + // sated + set_hunger( -21 ); + } else if( stomach.contains() >= stomach_capacity / 2 && get_hunger() > -1 ) { + // that's really all the food you need to feel full + set_hunger( -1 ); + } else if( stomach.contains() > 0_ml && get_kcal_percent() > 0.95 ) { + // usually eating something cools your hunger + set_hunger( 0 ); + } + } + + if( !foodless && rates.thirst > 0.0f ) { + mod_thirst( roll_remainder( rates.thirst * five_mins ) ); + } + // Mycus and Metabolic Rehydration makes thirst unnecessary + // since water is not limited by intake but by absorption, we can just set thirst to zero + if( mycus || mouse ) { + set_thirst( 0 ); + } +} + +void Character::update_needs( int rate_multiplier ) +{ + const int current_stim = get_stim(); + // Hunger, thirst, & fatigue up every 5 minutes + effect &sleep = get_effect( effect_sleep ); + // No food/thirst/fatigue clock at all + const bool debug_ls = has_trait( trait_DEBUG_LS ); + // No food/thirst, capped fatigue clock (only up to tired) + const bool npc_no_food = is_npc() && get_option( "NO_NPC_FOOD" ); + const bool asleep = !sleep.is_null(); + const bool lying = asleep || has_effect( effect_lying_down ) || + activity.id() == "ACT_TRY_SLEEP"; + + needs_rates rates = calc_needs_rates(); + + const bool wasnt_fatigued = get_fatigue() <= DEAD_TIRED; + // Don't increase fatigue if sleeping or trying to sleep or if we're at the cap. + if( get_fatigue() < 1050 && !asleep && !debug_ls ) { + if( rates.fatigue > 0.0f ) { + int fatigue_roll = roll_remainder( rates.fatigue * rate_multiplier ); + mod_fatigue( fatigue_roll ); + + if( get_option< bool >( "SLEEP_DEPRIVATION" ) ) { + // Synaptic regen bionic stops SD while awake and boosts it while sleeping + if( !has_active_bionic( bio_synaptic_regen ) ) { + // fatigue_roll should be around 1 - so the counter increases by 1 every minute on average, + // but characters who need less sleep will also get less sleep deprived, and vice-versa. + + // Note: Since needs are updated in 5-minute increments, we have to multiply the roll again by + // 5. If rate_multiplier is > 1, fatigue_roll will be higher and this will work out. + mod_sleep_deprivation( fatigue_roll * 5 ); + } + } + + if( npc_no_food && get_fatigue() > TIRED ) { + set_fatigue( TIRED ); + set_sleep_deprivation( 0 ); + } + } + } else if( asleep ) { + if( rates.recovery > 0.0f ) { + int recovered = roll_remainder( rates.recovery * rate_multiplier ); + if( get_fatigue() - recovered < -20 ) { + // Should be wake up, but that could prevent some retroactive regeneration + sleep.set_duration( 1_turns ); + mod_fatigue( -25 ); + } else { + if( has_effect( effect_recently_coughed ) ) { + recovered *= .5; + } + mod_fatigue( -recovered ); + if( get_option< bool >( "SLEEP_DEPRIVATION" ) ) { + // Sleeping on the ground, no bionic = 1x rest_modifier + // Sleeping on a bed, no bionic = 2x rest_modifier + // Sleeping on a comfy bed, no bionic= 3x rest_modifier + + // Sleeping on the ground, bionic = 3x rest_modifier + // Sleeping on a bed, bionic = 6x rest_modifier + // Sleeping on a comfy bed, bionic = 9x rest_modifier + float rest_modifier = ( has_active_bionic( bio_synaptic_regen ) ? 3 : 1 ); + // Magnesium supplements also add a flat bonus to recovery speed + if( has_effect( effect_magnesium_supplements ) ) { + rest_modifier += 1; + } + + comfort_level comfort = base_comfort_value( pos() ); + + if( comfort >= comfort_level::very_comfortable ) { + rest_modifier *= 3; + } else if( comfort >= comfort_level::comfortable ) { + rest_modifier *= 2.5; + } else if( comfort >= comfort_level::slightly_comfortable ) { + rest_modifier *= 2; + } + + // If we're just tired, we'll get a decent boost to our sleep quality. + // The opposite is true for very tired characters. + if( get_fatigue() < DEAD_TIRED ) { + rest_modifier += 2; + } else if( get_fatigue() >= EXHAUSTED ) { + rest_modifier = ( rest_modifier > 2 ) ? rest_modifier - 2 : 1; + } + + // Recovered is multiplied by 2 as well, since we spend 1/3 of the day sleeping + mod_sleep_deprivation( -rest_modifier * ( recovered * 2 ) ); + } + } + } + } + if( is_player() && wasnt_fatigued && get_fatigue() > DEAD_TIRED && !lying ) { + if( !activity ) { + add_msg_if_player( m_warning, _( "You're feeling tired. %s to lie down for sleep." ), + press_x( ACTION_SLEEP ) ); + } else { + g->cancel_activity_query( _( "You're feeling tired." ) ); + } + } + + if( current_stim < 0 ) { + set_stim( std::min( current_stim + rate_multiplier, 0 ) ); + } else if( current_stim > 0 ) { + set_stim( std::max( current_stim - rate_multiplier, 0 ) ); + } + + if( get_painkiller() > 0 ) { + mod_painkiller( -std::min( get_painkiller(), rate_multiplier ) ); + } + + // Huge folks take penalties for cramming themselves in vehicles + if( in_vehicle && ( has_trait( trait_HUGE ) || has_trait( trait_HUGE_OK ) ) ) { + vehicle *veh = veh_pointer_or_null( g->m.veh_at( pos() ) ); + // it's painful to work the controls, but passengers in open topped vehicles are fine + if( veh && ( veh->enclosed_at( pos() ) || veh->player_in_control( *this->as_player() ) ) ) { + add_msg_if_player( m_bad, + _( "You're cramping up from stuffing yourself in this vehicle." ) ); + if( is_npc() ) { + npc &as_npc = dynamic_cast( *this ); + as_npc.complain_about( "cramped_vehicle", 1_hours, "", false ); + } + mod_pain_noresist( 2 * rng( 2, 3 ) ); + focus_pool -= 1; + } + } +} +needs_rates Character::calc_needs_rates() const +{ + const effect &sleep = get_effect( effect_sleep ); + const bool has_recycler = has_bionic( bio_recycler ); + const bool asleep = !sleep.is_null(); + + needs_rates rates; + rates.hunger = metabolic_rate(); + + // TODO: this is where calculating basal metabolic rate, in kcal per day would go + rates.kcal = 2500.0; + + add_msg_if_player( m_debug, "Metabolic rate: %.2f", rates.hunger ); + + rates.thirst = get_option< float >( "PLAYER_THIRST_RATE" ); + rates.thirst *= 1.0f + mutation_value( "thirst_modifier" ); + if( worn_with_flag( "SLOWS_THIRST" ) ) { + rates.thirst *= 0.7f; + } + + rates.fatigue = get_option< float >( "PLAYER_FATIGUE_RATE" ); + rates.fatigue *= 1.0f + mutation_value( "fatigue_modifier" ); + + // Note: intentionally not in metabolic rate + if( has_recycler ) { + // Recycler won't help much with mutant metabolism - it is intended for human one + rates.hunger = std::min( rates.hunger, std::max( 0.5f, rates.hunger - 0.5f ) ); + rates.thirst = std::min( rates.thirst, std::max( 0.5f, rates.thirst - 0.5f ) ); + } + + if( asleep ) { + rates.recovery = 1.0f + mutation_value( "fatigue_regen_modifier" ); + if( !is_hibernating() ) { + // Hunger and thirst advance more slowly while we sleep. This is the standard rate. + rates.hunger *= 0.5f; + rates.thirst *= 0.5f; + const int intense = sleep.is_null() ? 0 : sleep.get_intensity(); + // Accelerated recovery capped to 2x over 2 hours + // After 16 hours of activity, equal to 7.25 hours of rest + const int accelerated_recovery_chance = 24 - intense + 1; + const float accelerated_recovery_rate = 1.0f / accelerated_recovery_chance; + rates.recovery += accelerated_recovery_rate; + } else { + // Hunger and thirst advance *much* more slowly whilst we hibernate. + rates.hunger *= ( 2.0f / 7.0f ); + rates.thirst *= ( 2.0f / 7.0f ); + } + rates.recovery -= static_cast( get_perceived_pain() ) / 60; + + } else { + rates.recovery = 0; + } + + if( has_activity( activity_id( "ACT_TREE_COMMUNION" ) ) ) { + // Much of the body's needs are taken care of by the trees. + // Hair Roots dont provide any bodily needs. + if( has_trait( trait_ROOTS2 ) || has_trait( trait_ROOTS3 ) ) { + rates.hunger *= 0.5f; + rates.thirst *= 0.5f; + rates.fatigue *= 0.5f; + } + } + + if( has_trait( trait_TRANSPIRATION ) ) { + // Transpiration, the act of moving nutrients with evaporating water, can take a very heavy toll on your thirst when it's really hot. + rates.thirst *= ( ( g->weather.get_temperature( pos() ) - 32.5f ) / 40.0f ); + } + + if( is_npc() ) { + rates.hunger *= 0.25f; + rates.thirst *= 0.25f; + } + + return rates; +} + +void Character::check_needs_extremes() +{ + // Check if we've overdosed... in any deadly way. + if( get_stim() > 250 ) { + add_msg_if_player( m_bad, _( "You have a sudden heart attack!" ) ); + g->events().send( getID(), efftype_id() ); + hp_cur[hp_torso] = 0; + } else if( get_stim() < -200 || get_painkiller() > 240 ) { + add_msg_if_player( m_bad, _( "Your breathing stops completely." ) ); + g->events().send( getID(), efftype_id() ); + hp_cur[hp_torso] = 0; + } else if( has_effect( effect_jetinjector ) && get_effect_dur( effect_jetinjector ) > 40_minutes ) { + if( !( has_trait( trait_NOPAIN ) ) ) { + add_msg_if_player( m_bad, _( "Your heart spasms painfully and stops." ) ); + } else { + add_msg_if_player( _( "Your heart spasms and stops." ) ); + } + g->events().send( getID(), effect_jetinjector ); + hp_cur[hp_torso] = 0; + } else if( get_effect_dur( effect_adrenaline ) > 50_minutes ) { + add_msg_if_player( m_bad, _( "Your heart spasms and stops." ) ); + g->events().send( getID(), effect_adrenaline ); + hp_cur[hp_torso] = 0; + } else if( get_effect_int( effect_drunk ) > 4 ) { + add_msg_if_player( m_bad, _( "Your breathing slows down to a stop." ) ); + g->events().send( getID(), effect_drunk ); + hp_cur[hp_torso] = 0; + } + + // check if we've starved + if( is_player() ) { + if( get_stored_kcal() <= 0 ) { + add_msg_if_player( m_bad, _( "You have starved to death." ) ); + g->events().send( getID() ); + hp_cur[hp_torso] = 0; + } else { + if( calendar::once_every( 1_hours ) ) { + std::string message; + if( stomach.contains() <= stomach.capacity( *this ) / 4 ) { + if( get_kcal_percent() < 0.1f ) { + message = _( "Food…" ); + } else if( get_kcal_percent() < 0.25f ) { + message = _( "Due to insufficient nutrition, your body is suffering from starvation." ); + } else if( get_kcal_percent() < 0.5f ) { + message = _( "Despite having something in your stomach, you still feel like you haven't eaten in days…" ); + } else if( get_kcal_percent() < 0.8f ) { + message = _( "Your stomach feels so empty…" ); + } + } else { + if( get_kcal_percent() < 0.1f ) { + message = _( "Food…" ); + } else if( get_kcal_percent() < 0.25f ) { + message = _( "You are EMACIATED!" ); + } else if( get_kcal_percent() < 0.5f ) { + message = _( "You feel weak due to malnutrition." ); + } else if( get_kcal_percent() < 0.8f ) { + message = _( "You feel that your body needs more nutritious food." ); + } + } + add_msg_if_player( m_warning, message ); + } + } + } + + // Check if we're dying of thirst + if( is_player() && get_thirst() >= 600 && ( stomach.get_water() == 0_ml || + guts.get_water() == 0_ml ) ) { + if( get_thirst() >= 1200 ) { + add_msg_if_player( m_bad, _( "You have died of dehydration." ) ); + g->events().send( getID() ); + hp_cur[hp_torso] = 0; + } else if( get_thirst() >= 1000 && calendar::once_every( 30_minutes ) ) { + add_msg_if_player( m_warning, _( "Even your eyes feel dry…" ) ); + } else if( get_thirst() >= 800 && calendar::once_every( 30_minutes ) ) { + add_msg_if_player( m_warning, _( "You are THIRSTY!" ) ); + } else if( calendar::once_every( 30_minutes ) ) { + add_msg_if_player( m_warning, _( "Your mouth feels so dry…" ) ); + } + } + + // Check if we're falling asleep, unless we're sleeping + if( get_fatigue() >= EXHAUSTED + 25 && !in_sleep_state() ) { + if( get_fatigue() >= MASSIVE_FATIGUE ) { + add_msg_if_player( m_bad, _( "Survivor sleep now." ) ); + g->events().send( getID() ); + mod_fatigue( -10 ); + fall_asleep(); + } else if( get_fatigue() >= 800 && calendar::once_every( 30_minutes ) ) { + add_msg_if_player( m_warning, _( "Anywhere would be a good place to sleep…" ) ); + } else if( calendar::once_every( 30_minutes ) ) { + add_msg_if_player( m_warning, _( "You feel like you haven't slept in days." ) ); + } + } + + // Even if we're not Exhausted, we really should be feeling lack/sleep earlier + // Penalties start at Dead Tired and go from there + if( get_fatigue() >= DEAD_TIRED && !in_sleep_state() ) { + if( get_fatigue() >= 700 ) { + if( calendar::once_every( 30_minutes ) ) { + add_msg_if_player( m_warning, _( "You're too physically tired to stop yawning." ) ); + add_effect( effect_lack_sleep, 30_minutes + 1_turns ); + } + /** @EFFECT_INT slightly decreases occurrence of short naps when dead tired */ + if( one_in( 50 + int_cur ) ) { + // Rivet's idea: look out for microsleeps! + fall_asleep( 30_seconds ); + } + } else if( get_fatigue() >= EXHAUSTED ) { + if( calendar::once_every( 30_minutes ) ) { + add_msg_if_player( m_warning, _( "How much longer until bedtime?" ) ); + add_effect( effect_lack_sleep, 30_minutes + 1_turns ); + } + /** @EFFECT_INT slightly decreases occurrence of short naps when exhausted */ + if( one_in( 100 + int_cur ) ) { + fall_asleep( 30_seconds ); + } + } else if( get_fatigue() >= DEAD_TIRED && calendar::once_every( 30_minutes ) ) { + add_msg_if_player( m_warning, _( "*yawn* You should really get some sleep." ) ); + add_effect( effect_lack_sleep, 30_minutes + 1_turns ); + } + } + + // Sleep deprivation kicks in if lack of sleep is avoided with stimulants or otherwise for long periods of time + int sleep_deprivation = get_sleep_deprivation(); + float sleep_deprivation_pct = sleep_deprivation / static_cast( SLEEP_DEPRIVATION_MASSIVE ); + + if( sleep_deprivation >= SLEEP_DEPRIVATION_HARMLESS && !in_sleep_state() ) { + if( calendar::once_every( 60_minutes ) ) { + if( sleep_deprivation < SLEEP_DEPRIVATION_MINOR ) { + add_msg_if_player( m_warning, + _( "Your mind feels tired. It's been a while since you've slept well." ) ); + mod_fatigue( 1 ); + } else if( sleep_deprivation < SLEEP_DEPRIVATION_SERIOUS ) { + add_msg_if_player( m_bad, + _( "Your mind feels foggy from lack of good sleep, and your eyes keep trying to close against your will." ) ); + mod_fatigue( 5 ); + + if( one_in( 10 ) ) { + mod_healthy_mod( -1, 0 ); + } + } else if( sleep_deprivation < SLEEP_DEPRIVATION_MAJOR ) { + add_msg_if_player( m_bad, + _( "Your mind feels weary, and you dread every wakeful minute that passes. You crave sleep, and feel like you're about to collapse." ) ); + mod_fatigue( 10 ); + + if( one_in( 5 ) ) { + mod_healthy_mod( -2, 0 ); + } + } else if( sleep_deprivation < SLEEP_DEPRIVATION_MASSIVE ) { + add_msg_if_player( m_bad, + _( "You haven't slept decently for so long that your whole body is screaming for mercy. It's a miracle that you're still awake, but it just feels like a curse now." ) ); + mod_fatigue( 40 ); + + mod_healthy_mod( -5, 0 ); + } + // else you pass out for 20 hours, guaranteed + + // Microsleeps are slightly worse if you're sleep deprived, but not by much. (chance: 1 in (75 + int_cur) at lethal sleep deprivation) + // Note: these can coexist with fatigue-related microsleeps + /** @EFFECT_INT slightly decreases occurrence of short naps when sleep deprived */ + if( one_in( static_cast( sleep_deprivation_pct * 75 ) + int_cur ) ) { + fall_asleep( 30_seconds ); + } + + // Stimulants can be used to stay awake a while longer, but after a while you'll just collapse. + bool can_pass_out = ( get_stim() < 30 && sleep_deprivation >= SLEEP_DEPRIVATION_MINOR ) || + sleep_deprivation >= SLEEP_DEPRIVATION_MAJOR; + + if( can_pass_out && calendar::once_every( 10_minutes ) ) { + /** @EFFECT_PER slightly increases resilience against passing out from sleep deprivation */ + if( one_in( static_cast( ( 1 - sleep_deprivation_pct ) * 100 ) + per_cur ) || + sleep_deprivation >= SLEEP_DEPRIVATION_MASSIVE ) { + add_msg_player_or_npc( m_bad, + _( "Your body collapses due to sleep deprivation, your neglected fatigue rushing back all at once, and you pass out on the spot." ) + , _( " collapses to the ground from exhaustion." ) ); + if( get_fatigue() < EXHAUSTED ) { + set_fatigue( EXHAUSTED ); + } + + if( sleep_deprivation >= SLEEP_DEPRIVATION_MAJOR ) { + fall_asleep( 20_hours ); + } else if( sleep_deprivation >= SLEEP_DEPRIVATION_SERIOUS ) { + fall_asleep( 16_hours ); + } else { + fall_asleep( 12_hours ); + } + } + } + + } + } +} + +void Character::get_sick() +{ + // NPCs are too dumb to handle infections now + if( is_npc() || has_trait_flag( "NO_DISEASE" ) ) { + // In a shocking twist, disease immunity prevents diseases. + return; + } + + if( has_effect( effect_flu ) || has_effect( effect_common_cold ) ) { + // While it's certainly possible to get sick when you already are, + // it wouldn't be very fun. + return; + } + + // Normal people get sick about 2-4 times/year. + int base_diseases_per_year = 3; + if( has_trait( trait_DISRESISTANT ) ) { + // Disease resistant people only get sick once a year. + base_diseases_per_year = 1; + } + + // This check runs once every 30 minutes, so double to get hours, *24 to get days. + const int checks_per_year = 2 * 24 * 365; + + // Health is in the range [-200,200]. + // Diseases are half as common for every 50 health you gain. + float health_factor = std::pow( 2.0f, get_healthy() / 50.0f ); + + int disease_rarity = static_cast( checks_per_year * health_factor / base_diseases_per_year ); + add_msg( m_debug, "disease_rarity = %d", disease_rarity ); + if( one_in( disease_rarity ) ) { + if( one_in( 6 ) ) { + // The flu typically lasts 3-10 days. + add_env_effect( effect_flu, bp_mouth, 3, rng( 3_days, 10_days ) ); + } else { + // A cold typically lasts 1-14 days. + add_env_effect( effect_common_cold, bp_mouth, 3, rng( 1_days, 14_days ) ); + } + } +} + +bool Character::is_hibernating() const +{ + // Hibernating only kicks in whilst Engorged; separate tracking for hunger/thirst here + // as a safety catch. One test subject managed to get two Colds during hibernation; + // since those add fatigue and dry out the character, the subject went for the full 10 days plus + // a little, and came out of it well into Parched. Hibernating shouldn't endanger your + // life like that--but since there's much less fluid reserve than food reserve, + // simply using the same numbers won't work. + return has_effect( effect_sleep ) && get_kcal_percent() > 0.8f && + get_thirst() <= 80 && has_active_mutation( trait_id( "HIBERNATE" ) ); +} + /* Here lies the intended effects of body temperature Assumption 1 : a naked person is comfortable at 19C/66.2F (31C/87.8F at rest). @@ -4266,6 +5032,133 @@ void Character::temp_equalizer( body_part bp1, body_part bp2 ) temp_cur[bp1] += diff; } +Character::comfort_level Character::base_comfort_value( const tripoint &p ) const +{ + // Comfort of sleeping spots is "objective", while sleep_spot( p ) is "subjective" + // As in the latter also checks for fatigue and other variables while this function + // only looks at the base comfyness of something. It's still subjective, in a sense, + // as arachnids who sleep in webs will find most places comfortable for instance. + int comfort = 0; + + bool plantsleep = has_trait( trait_CHLOROMORPH ); + bool fungaloid_cosplay = has_trait( trait_M_SKIN3 ); + bool websleep = has_trait( trait_WEB_WALKER ); + bool webforce = has_trait( trait_THRESH_SPIDER ) && ( has_trait( trait_WEB_SPINNER ) || + ( has_trait( trait_WEB_WEAVER ) ) ); + bool in_shell = has_active_mutation( trait_SHELL2 ); + bool watersleep = has_trait( trait_WATERSLEEP ); + + const optional_vpart_position vp = g->m.veh_at( p ); + const maptile tile = g->m.maptile_at( p ); + const trap &trap_at_pos = tile.get_trap_t(); + const ter_id ter_at_pos = tile.get_ter(); + const furn_id furn_at_pos = tile.get_furn(); + + int web = g->m.get_field_intensity( p, fd_web ); + + // Some mutants have different comfort needs + if( !plantsleep && !webforce ) { + if( in_shell ) { + comfort += 1 + static_cast( comfort_level::slightly_comfortable ); + // Note: shelled individuals can still use sleeping aids! + } else if( vp ) { + vehicle &veh = vp->vehicle(); + const cata::optional carg = vp.part_with_feature( "CARGO", false ); + const cata::optional board = vp.part_with_feature( "BOARDABLE", true ); + if( carg ) { + vehicle_stack items = veh.get_items( carg->part_index() ); + for( auto &items_it : items ) { + if( items_it.has_flag( "SLEEP_AID" ) ) { + // Note: BED + SLEEP_AID = 9 pts, or 1 pt below very_comfortable + comfort += 1 + static_cast( comfort_level::slightly_comfortable ); + add_msg_if_player( m_info, _( "You use your %s for comfort." ), items_it.tname() ); + break; // prevents using more than 1 sleep aid + } + } + } + if( board ) { + comfort += board->info().comfort; + } else { + comfort -= g->m.move_cost( p ); + } + } + // Not in a vehicle, start checking furniture/terrain/traps at this point in decreasing order + else if( furn_at_pos != f_null ) { + comfort += 0 + furn_at_pos.obj().comfort; + } + // Web sleepers can use their webs if better furniture isn't available + else if( websleep && web >= 3 ) { + comfort += 1 + static_cast( comfort_level::slightly_comfortable ); + } else if( ter_at_pos == t_improvised_shelter ) { + comfort += 0 + static_cast( comfort_level::slightly_comfortable ); + } else if( ter_at_pos == t_floor || ter_at_pos == t_floor_waxed || + ter_at_pos == t_carpet_red || ter_at_pos == t_carpet_yellow || + ter_at_pos == t_carpet_green || ter_at_pos == t_carpet_purple ) { + comfort += 1 + static_cast( comfort_level::neutral ); + } else if( !trap_at_pos.is_null() ) { + comfort += 0 + trap_at_pos.comfort; + } else { + // Not a comfortable sleeping spot + comfort -= g->m.move_cost( p ); + } + + auto items = g->m.i_at( p ); + for( auto &items_it : items ) { + if( items_it.has_flag( "SLEEP_AID" ) ) { + // Note: BED + SLEEP_AID = 9 pts, or 1 pt below very_comfortable + comfort += 1 + static_cast( comfort_level::slightly_comfortable ); + add_msg_if_player( m_info, _( "You use your %s for comfort." ), items_it.tname() ); + break; // prevents using more than 1 sleep aid + } + } + + if( fungaloid_cosplay && g->m.has_flag_ter_or_furn( "FUNGUS", pos() ) ) { + comfort += static_cast( comfort_level::very_comfortable ); + } else if( watersleep && g->m.has_flag_ter( "SWIMMABLE", pos() ) ) { + comfort += static_cast( comfort_level::very_comfortable ); + } + } else if( plantsleep ) { + if( vp || furn_at_pos != f_null ) { + // Sleep ain't happening in a vehicle or on furniture + comfort = static_cast( comfort_level::impossible ); + } else { + // It's very easy for Chloromorphs to get to sleep on soil! + if( ter_at_pos == t_dirt || ter_at_pos == t_pit || ter_at_pos == t_dirtmound || + ter_at_pos == t_pit_shallow ) { + comfort += static_cast( comfort_level::very_comfortable ); + } + // Not as much if you have to dig through stuff first + else if( ter_at_pos == t_grass ) { + comfort += static_cast( comfort_level::comfortable ); + } + // Sleep ain't happening + else { + comfort = static_cast( comfort_level::impossible ); + } + } + // Has webforce + } else { + if( web >= 3 ) { + // Thick Web and you're good to go + comfort += static_cast( comfort_level::very_comfortable ); + } else { + comfort = static_cast( comfort_level::impossible ); + } + } + + if( comfort > static_cast( comfort_level::comfortable ) ) { + return comfort_level::very_comfortable; + } else if( comfort > static_cast( comfort_level::slightly_comfortable ) ) { + return comfort_level::comfortable; + } else if( comfort > static_cast( comfort_level::neutral ) ) { + return comfort_level::slightly_comfortable; + } else if( comfort == static_cast( comfort_level::neutral ) ) { + return comfort_level::neutral; + } else { + return comfort_level::uncomfortable; + } +} + int Character::blood_loss( body_part bp ) const { int hp_cur_sum = 1; diff --git a/src/character.h b/src/character.h index c6487bfb398ab..6634a1fc212d4 100644 --- a/src/character.h +++ b/src/character.h @@ -230,6 +230,15 @@ class Character : public Creature, public visitable const std::string &symbol() const override; + enum class comfort_level { + impossible = -999, + uncomfortable = -7, + neutral = 0, + slightly_comfortable = 3, + comfortable = 5, + very_comfortable = 10 + }; + enum stat { STRENGTH, DEXTERITY, @@ -384,15 +393,40 @@ class Character : public Creature, public visitable */ std::string get_miss_reason(); + /** + * Handles passive regeneration of pain and maybe hp. + */ + void regen( int rate_multiplier ); + // called once per 24 hours to enforce the minimum of 1 hp healed per day + // TODO: Move to Character once heal() is moved + void enforce_minimum_healing(); + /** Handles health fluctuations over time */ virtual void update_health( int external_modifiers = 0 ); - + /** Updates all "biology" by one turn. Should be called once every turn. */ + void update_body(); + /** Updates all "biology" as if time between `from` and `to` passed. */ + void update_body( const time_point &from, const time_point &to ); + /** Updates the stomach to give accurate hunger messages */ + void update_stomach( const time_point &from, const time_point &to ); + /** Increases hunger, thirst, fatigue and stimulants wearing off. `rate_multiplier` is for retroactive updates. */ + void update_needs( int rate_multiplier ); + needs_rates calc_needs_rates() const; + /** Kills the player if too hungry, stimmed up etc., forces tired player to sleep and prints warnings. */ + void check_needs_extremes(); + /** Handles the chance to be infected by random diseases */ + void get_sick(); + /** Returns if the player has hibernation mutation and is asleep and well fed */ + bool is_hibernating() const; /** Maintains body temperature */ void update_bodytemp(); /** Equalizes heat between body parts */ void temp_equalizer( body_part bp1, body_part bp2 ); + /** Rate point's ability to serve as a bed. Only takes certain mutations into account, and not fatigue nor stimulants. */ + comfort_level base_comfort_value( const tripoint &p ) const; + /** Define blood loss (in percents) */ int blood_loss( body_part bp ) const; @@ -666,6 +700,7 @@ class Character : public Creature, public visitable /** Applies skill-based boosts to stats **/ void apply_skill_boost(); protected: + void do_skill_rust(); /** Applies stat mods to character. */ void apply_mods( const trait_id &mut, bool add_remove ); @@ -1225,6 +1260,9 @@ class Character : public Creature, public visitable /** Returns a string of missed requirements (both stats and skills) */ std::string enumerate_unmet_requirements( const item &it, const item &context = item() ) const; + /** Returns the player's skill rust rate */ + int rust_rate( bool return_stat_effect = true ) const; + // --------------- Other Stuff --------------- /** return the calendar::turn the character expired */ @@ -1601,6 +1639,9 @@ class Character : public Creature, public visitable * @return adjusted level for the vitamin or zero if vitamin does not exist */ int vitamin_mod( const vitamin_id &vit, int qty, bool capped = true ); + void vitamins_mod( const std::map &, bool capped = true ); + /** Get vitamin usage rate (minutes per unit) accounting for bionics, mutations and effects */ + time_duration vitamin_rate( const vitamin_id &vit ) const; /** Returns true if the player is wearing something on the entered body_part */ bool wearing_something_on( body_part bp ) const; diff --git a/src/consumption.cpp b/src/consumption.cpp index 80650e07ed2f2..e282534471a96 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -430,7 +430,7 @@ std::pair Character::fun_for( const item &comest ) const return { static_cast< int >( fun ), static_cast< int >( fun_max ) }; } -time_duration player::vitamin_rate( const vitamin_id &vit ) const +time_duration Character::vitamin_rate( const vitamin_id &vit ) const { time_duration res = vit.obj().rate(); @@ -466,7 +466,7 @@ int Character::vitamin_mod( const vitamin_id &vit, int qty, bool capped ) return it->second; } -void player::vitamins_mod( const std::map &vitamins, bool capped ) +void Character::vitamins_mod( const std::map &vitamins, bool capped ) { for( auto vit : vitamins ) { vitamin_mod( vit.first, vit.second, capped ); diff --git a/src/game.cpp b/src/game.cpp index 0a2b1a471cdc0..4d2dcd692b0a4 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1582,7 +1582,6 @@ bool game::do_turn() u.update_bodytemp(); u.update_body_wetness( *weather.weather_precise ); u.apply_wetness_morale( weather.temperature ); - u.do_skill_rust(); if( calendar::once_every( 1_minutes ) ) { u.update_morale(); diff --git a/src/player.cpp b/src/player.cpp index 45ed76dee9f9c..1e1ecf9c8f873 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -131,8 +131,6 @@ static const efftype_id effect_got_checked( "got_checked" ); static const efftype_id effect_grabbed( "grabbed" ); static const efftype_id effect_grabbing( "grabbing" ); static const efftype_id effect_infected( "infected" ); -static const efftype_id effect_jetinjector( "jetinjector" ); -static const efftype_id effect_lack_sleep( "lack_sleep" ); static const efftype_id effect_lying_down( "lying_down" ); static const efftype_id effect_masked_scent( "masked_scent" ); static const efftype_id effect_mending( "mending" ); @@ -147,7 +145,6 @@ static const efftype_id effect_sleep( "sleep" ); static const efftype_id effect_stunned( "stunned" ); static const efftype_id effect_tapeworm( "tapeworm" ); static const efftype_id effect_weed_high( "weed_high" ); -static const efftype_id effect_magnesium_supplements( "magnesium" ); static const bionic_id bio_armor_arms( "bio_armor_arms" ); static const bionic_id bio_armor_eyes( "bio_armor_eyes" ); @@ -164,13 +161,11 @@ static const bionic_id bio_ground_sonar( "bio_ground_sonar" ); static const bionic_id bio_jointservo( "bio_jointservo" ); static const bionic_id bio_membrane( "bio_membrane" ); static const bionic_id bio_memory( "bio_memory" ); -static const bionic_id bio_recycler( "bio_recycler" ); static const bionic_id bio_soporific( "bio_soporific" ); static const bionic_id bio_speed( "bio_speed" ); static const bionic_id bio_syringe( "bio_syringe" ); static const bionic_id bio_uncanny_dodge( "bio_uncanny_dodge" ); static const bionic_id bio_watch( "bio_watch" ); -static const bionic_id bio_synaptic_regen( "bio_synaptic_regen" ); // Aftershock stuff! static const bionic_id afs_bio_linguistic_coprocessor( "afs_bio_linguistic_coprocessor" ); @@ -191,9 +186,7 @@ static const trait_id trait_DEFT( "DEFT" ); static const trait_id trait_DEBUG_BIONIC_POWER( "DEBUG_BIONIC_POWER" ); static const trait_id trait_DEBUG_CLOAK( "DEBUG_CLOAK" ); static const trait_id trait_DEBUG_HS( "DEBUG_HS" ); -static const trait_id trait_DEBUG_LS( "DEBUG_LS" ); static const trait_id trait_DEBUG_NODMG( "DEBUG_NODMG" ); -static const trait_id trait_DISRESISTANT( "DISRESISTANT" ); static const trait_id trait_EASYSLEEPER( "EASYSLEEPER" ); static const trait_id trait_EASYSLEEPER2( "EASYSLEEPER2" ); static const trait_id trait_EATHEALTH( "EATHEALTH" ); @@ -222,7 +215,6 @@ static const trait_id trait_MOREPAIN( "MORE_PAIN" ); static const trait_id trait_MOREPAIN2( "MORE_PAIN2" ); static const trait_id trait_MOREPAIN3( "MORE_PAIN3" ); static const trait_id trait_MYOPIC( "MYOPIC" ); -static const trait_id trait_M_DEPENDENT( "M_DEPENDENT" ); static const trait_id trait_M_IMMUNE( "M_IMMUNE" ); static const trait_id trait_M_SKIN3( "M_SKIN3" ); static const trait_id trait_NAUSEA( "NAUSEA" ); @@ -230,7 +222,6 @@ static const trait_id trait_NOMAD( "NOMAD" ); static const trait_id trait_NOMAD2( "NOMAD2" ); static const trait_id trait_NOMAD3( "NOMAD3" ); static const trait_id trait_NOPAIN( "NOPAIN" ); -static const trait_id trait_NO_THIRST( "NO_THIRST" ); static const trait_id trait_PACIFIST( "PACIFIST" ); static const trait_id trait_PADDED_FEET( "PADDED_FEET" ); static const trait_id trait_PAINRESIST( "PAINRESIST" ); @@ -247,7 +238,6 @@ static const trait_id trait_PRED4( "PRED4" ); static const trait_id trait_PROF_SKATER( "PROF_SKATER" ); static const trait_id trait_PSYCHOPATH( "PSYCHOPATH" ); static const trait_id trait_QUILLS( "QUILLS" ); -static const trait_id trait_RADIOGENIC( "RADIOGENIC" ); static const trait_id trait_ROOTS2( "ROOTS2" ); static const trait_id trait_ROOTS3( "ROOTS3" ); static const trait_id trait_SAPIOVORE( "SAPIOVORE" ); @@ -265,7 +255,6 @@ static const trait_id trait_SUNLIGHT_DEPENDENT( "SUNLIGHT_DEPENDENT" ); static const trait_id trait_THORNS( "THORNS" ); static const trait_id trait_THRESH_SPIDER( "THRESH_SPIDER" ); static const trait_id trait_TOUGH_FEET( "TOUGH_FEET" ); -static const trait_id trait_TRANSPIRATION( "TRANSPIRATION" ); static const trait_id trait_URSINE_EYE( "URSINE_EYE" ); static const trait_id trait_URSINE_FUR( "URSINE_FUR" ); static const trait_id trait_VOMITOUS( "VOMITOUS" ); @@ -1369,28 +1358,6 @@ int player::read_speed( bool return_stat_effect ) const return return_stat_effect ? ret : ret / 10; } -int player::rust_rate( bool return_stat_effect ) const -{ - if( get_option( "SKILL_RUST" ) == "off" ) { - return 0; - } - - // Stat window shows stat effects on based on current stat - int intel = get_int(); - /** @EFFECT_INT reduces skill rust */ - int ret = ( ( get_option( "SKILL_RUST" ) == "vanilla" || - get_option( "SKILL_RUST" ) == "capped" ) ? 500 : 500 - 35 * ( intel - 8 ) ); - - ret *= mutation_value( "skill_rust_multiplier" ); - - if( ret < 0 ) { - ret = 0; - } - - // return_stat_effect actually matters here - return ( return_stat_effect ? ret : ret / 10 ); -} - int player::talk_skill() const { /** @EFFECT_INT slightly increases talking skill */ @@ -2159,666 +2126,6 @@ int player::hp_percentage() const return ( 100 * total_cur ) / total_max; } -// Returns the number of multiples of tick_length we would "pass" on our way `from` to `to` -// For example, if `tick_length` is 1 hour, then going from 0:59 to 1:01 should return 1 -inline int ticks_between( const time_point &from, const time_point &to, - const time_duration &tick_length ) -{ - return ( to_turn( to ) / to_turns( tick_length ) ) - ( to_turn - ( from ) / to_turns( tick_length ) ); -} - -void player::update_body() -{ - update_body( calendar::turn - 1_turns, calendar::turn ); -} - -void player::update_body( const time_point &from, const time_point &to ) -{ - if( !is_npc() ) { - update_stamina( to_turns( to - from ) ); - } - update_stomach( from, to ); - recalculate_enchantment_cache(); - if( ticks_between( from, to, 3_minutes ) > 0 ) { - magic.update_mana( *this, to_turns( 3_minutes ) ); - } - const int five_mins = ticks_between( from, to, 5_minutes ); - if( five_mins > 0 ) { - check_needs_extremes(); - update_needs( five_mins ); - regen( five_mins ); - // Note: mend ticks once per 5 minutes, but wants rate in TURNS, not 5 minute intervals - // TODO: change @ref med to take time_duration - mend( five_mins * to_turns( 5_minutes ) ); - } - if( ticks_between( from, to, 24_hours ) > 0 ) { - enforce_minimum_healing(); - } - - const int thirty_mins = ticks_between( from, to, 30_minutes ); - if( thirty_mins > 0 ) { - if( activity.is_null() ) { - reset_activity_level(); - } - // Radiation kills health even at low doses - update_health( has_trait( trait_RADIOGENIC ) ? 0 : -get_rad() ); - get_sick(); - } - - for( const auto &v : vitamin::all() ) { - const time_duration rate = vitamin_rate( v.first ); - if( rate > 0_turns ) { - int qty = ticks_between( from, to, rate ); - if( qty > 0 ) { - vitamin_mod( v.first, 0 - qty ); - } - - } else if( rate < 0_turns ) { - // mutations can result in vitamins being generated (but never accumulated) - int qty = ticks_between( from, to, -rate ); - if( qty > 0 ) { - vitamin_mod( v.first, qty ); - } - } - } -} - -void player::update_stomach( const time_point &from, const time_point &to ) -{ - const needs_rates rates = calc_needs_rates(); - // No food/thirst/fatigue clock at all - const bool debug_ls = has_trait( trait_DEBUG_LS ); - // No food/thirst, capped fatigue clock (only up to tired) - const bool npc_no_food = is_npc() && get_option( "NO_NPC_FOOD" ); - const bool foodless = debug_ls || npc_no_food; - const bool mouse = has_trait( trait_NO_THIRST ); - const bool mycus = has_trait( trait_M_DEPENDENT ); - const float kcal_per_time = get_bmr() / ( 12.0f * 24.0f ); - const int five_mins = ticks_between( from, to, 5_minutes ); - const int half_hours = ticks_between( from, to, 30_minutes ); - const units::volume stomach_capacity = stomach.capacity( *this ); - - if( five_mins > 0 ) { - // Digest nutrients in stomach, they are destined for the guts (except water) - food_summary digested_to_guts = stomach.digest( *this, rates, five_mins, half_hours ); - // Digest nutrients in guts, they will be distributed to needs levels - food_summary digested_to_body = guts.digest( *this, rates, five_mins, half_hours ); - // Water from stomach skips guts and gets absorbed by body - mod_thirst( - units::to_milliliter( digested_to_guts.water ) / 5 ); - guts.ingest( digested_to_guts ); - // Apply nutrients, unless this is an NPC and NO_NPC_FOOD is enabled. - if( !is_npc() || !get_option( "NO_NPC_FOOD" ) ) { - mod_stored_kcal( digested_to_body.nutr.kcal ); - vitamins_mod( digested_to_body.nutr.vitamins, false ); - } - } - if( stomach.time_since_ate() > 10_minutes ) { - if( stomach.contains() >= stomach_capacity && get_hunger() > -61 ) { - // you're engorged! your stomach is full to bursting! - set_hunger( -61 ); - } else if( stomach.contains() >= stomach_capacity / 2 && get_hunger() > -21 ) { - // sated - set_hunger( -21 ); - } else if( stomach.contains() >= stomach_capacity / 8 && get_hunger() > -1 ) { - // that's really all the food you need to feel full - set_hunger( -1 ); - } else if( stomach.contains() == 0_ml ) { - if( guts.get_calories() == 0 && get_stored_kcal() < get_healthy_kcal() && get_hunger() < 300 ) { - // there's no food except what you have stored in fat - set_hunger( 300 ); - } else if( get_hunger() < 100 && ( ( guts.get_calories() == 0 && - get_stored_kcal() >= get_healthy_kcal() ) || get_stored_kcal() < get_healthy_kcal() ) ) { - set_hunger( 100 ); - } else if( get_hunger() < 0 ) { - set_hunger( 0 ); - } - } - if( !foodless && rates.hunger > 0.0f ) { - mod_hunger( roll_remainder( rates.hunger * five_mins ) ); - // instead of hunger keeping track of how you're living, burn calories instead - mod_stored_kcal( -roll_remainder( five_mins * kcal_per_time ) ); - } - } else - // you fill up when you eat fast, but less so than if you eat slow - // if you just ate but your stomach is still empty it will still - // delay your filling up (drugs?) - { - if( stomach.contains() >= stomach_capacity && get_hunger() > -61 ) { - // you're engorged! your stomach is full to bursting! - set_hunger( -61 ); - } else if( stomach.contains() >= stomach_capacity * 3 / 4 && get_hunger() > -21 ) { - // sated - set_hunger( -21 ); - } else if( stomach.contains() >= stomach_capacity / 2 && get_hunger() > -1 ) { - // that's really all the food you need to feel full - set_hunger( -1 ); - } else if( stomach.contains() > 0_ml && get_kcal_percent() > 0.95 ) { - // usually eating something cools your hunger - set_hunger( 0 ); - } - } - - if( !foodless && rates.thirst > 0.0f ) { - mod_thirst( roll_remainder( rates.thirst * five_mins ) ); - } - // Mycus and Metabolic Rehydration makes thirst unnecessary - // since water is not limited by intake but by absorption, we can just set thirst to zero - if( mycus || mouse ) { - set_thirst( 0 ); - } -} - -void player::get_sick() -{ - // NPCs are too dumb to handle infections now - if( is_npc() || has_trait_flag( "NO_DISEASE" ) ) { - // In a shocking twist, disease immunity prevents diseases. - return; - } - - if( has_effect( effect_flu ) || has_effect( effect_common_cold ) ) { - // While it's certainly possible to get sick when you already are, - // it wouldn't be very fun. - return; - } - - // Normal people get sick about 2-4 times/year. - int base_diseases_per_year = 3; - if( has_trait( trait_DISRESISTANT ) ) { - // Disease resistant people only get sick once a year. - base_diseases_per_year = 1; - } - - // This check runs once every 30 minutes, so double to get hours, *24 to get days. - const int checks_per_year = 2 * 24 * 365; - - // Health is in the range [-200,200]. - // Diseases are half as common for every 50 health you gain. - float health_factor = std::pow( 2.0f, get_healthy() / 50.0f ); - - int disease_rarity = static_cast( checks_per_year * health_factor / base_diseases_per_year ); - add_msg( m_debug, "disease_rarity = %d", disease_rarity ); - if( one_in( disease_rarity ) ) { - if( one_in( 6 ) ) { - // The flu typically lasts 3-10 days. - add_env_effect( effect_flu, bp_mouth, 3, rng( 3_days, 10_days ) ); - } else { - // A cold typically lasts 1-14 days. - add_env_effect( effect_common_cold, bp_mouth, 3, rng( 1_days, 14_days ) ); - } - } -} - -void player::check_needs_extremes() -{ - // Check if we've overdosed... in any deadly way. - if( get_stim() > 250 ) { - add_msg_if_player( m_bad, _( "You have a sudden heart attack!" ) ); - g->events().send( getID(), efftype_id() ); - hp_cur[hp_torso] = 0; - } else if( get_stim() < -200 || get_painkiller() > 240 ) { - add_msg_if_player( m_bad, _( "Your breathing stops completely." ) ); - g->events().send( getID(), efftype_id() ); - hp_cur[hp_torso] = 0; - } else if( has_effect( effect_jetinjector ) && get_effect_dur( effect_jetinjector ) > 40_minutes ) { - if( !( has_trait( trait_NOPAIN ) ) ) { - add_msg_if_player( m_bad, _( "Your heart spasms painfully and stops." ) ); - } else { - add_msg_if_player( _( "Your heart spasms and stops." ) ); - } - g->events().send( getID(), effect_jetinjector ); - hp_cur[hp_torso] = 0; - } else if( get_effect_dur( effect_adrenaline ) > 50_minutes ) { - add_msg_if_player( m_bad, _( "Your heart spasms and stops." ) ); - g->events().send( getID(), effect_adrenaline ); - hp_cur[hp_torso] = 0; - } else if( get_effect_int( effect_drunk ) > 4 ) { - add_msg_if_player( m_bad, _( "Your breathing slows down to a stop." ) ); - g->events().send( getID(), effect_drunk ); - hp_cur[hp_torso] = 0; - } - - // check if we've starved - if( is_player() ) { - if( get_stored_kcal() <= 0 ) { - add_msg_if_player( m_bad, _( "You have starved to death." ) ); - g->events().send( getID() ); - hp_cur[hp_torso] = 0; - } else { - if( calendar::once_every( 1_hours ) ) { - std::string message; - if( stomach.contains() <= stomach.capacity( *this ) / 4 ) { - if( get_kcal_percent() < 0.1f ) { - message = _( "Food…" ); - } else if( get_kcal_percent() < 0.25f ) { - message = _( "Due to insufficient nutrition, your body is suffering from starvation." ); - } else if( get_kcal_percent() < 0.5f ) { - message = _( "Despite having something in your stomach, you still feel like you haven't eaten in days…" ); - } else if( get_kcal_percent() < 0.8f ) { - message = _( "Your stomach feels so empty…" ); - } - } else { - if( get_kcal_percent() < 0.1f ) { - message = _( "Food…" ); - } else if( get_kcal_percent() < 0.25f ) { - message = _( "You are EMACIATED!" ); - } else if( get_kcal_percent() < 0.5f ) { - message = _( "You feel weak due to malnutrition." ); - } else if( get_kcal_percent() < 0.8f ) { - message = _( "You feel that your body needs more nutritious food." ); - } - } - add_msg_if_player( m_warning, message ); - } - } - } - - // Check if we're dying of thirst - if( is_player() && get_thirst() >= 600 && ( stomach.get_water() == 0_ml || - guts.get_water() == 0_ml ) ) { - if( get_thirst() >= 1200 ) { - add_msg_if_player( m_bad, _( "You have died of dehydration." ) ); - g->events().send( getID() ); - hp_cur[hp_torso] = 0; - } else if( get_thirst() >= 1000 && calendar::once_every( 30_minutes ) ) { - add_msg_if_player( m_warning, _( "Even your eyes feel dry…" ) ); - } else if( get_thirst() >= 800 && calendar::once_every( 30_minutes ) ) { - add_msg_if_player( m_warning, _( "You are THIRSTY!" ) ); - } else if( calendar::once_every( 30_minutes ) ) { - add_msg_if_player( m_warning, _( "Your mouth feels so dry…" ) ); - } - } - - // Check if we're falling asleep, unless we're sleeping - if( get_fatigue() >= EXHAUSTED + 25 && !in_sleep_state() ) { - if( get_fatigue() >= MASSIVE_FATIGUE ) { - add_msg_if_player( m_bad, _( "Survivor sleep now." ) ); - g->events().send( getID() ); - mod_fatigue( -10 ); - fall_asleep(); - } else if( get_fatigue() >= 800 && calendar::once_every( 30_minutes ) ) { - add_msg_if_player( m_warning, _( "Anywhere would be a good place to sleep…" ) ); - } else if( calendar::once_every( 30_minutes ) ) { - add_msg_if_player( m_warning, _( "You feel like you haven't slept in days." ) ); - } - } - - // Even if we're not Exhausted, we really should be feeling lack/sleep earlier - // Penalties start at Dead Tired and go from there - if( get_fatigue() >= DEAD_TIRED && !in_sleep_state() ) { - if( get_fatigue() >= 700 ) { - if( calendar::once_every( 30_minutes ) ) { - add_msg_if_player( m_warning, _( "You're too physically tired to stop yawning." ) ); - add_effect( effect_lack_sleep, 30_minutes + 1_turns ); - } - /** @EFFECT_INT slightly decreases occurrence of short naps when dead tired */ - if( one_in( 50 + int_cur ) ) { - // Rivet's idea: look out for microsleeps! - fall_asleep( 30_seconds ); - } - } else if( get_fatigue() >= EXHAUSTED ) { - if( calendar::once_every( 30_minutes ) ) { - add_msg_if_player( m_warning, _( "How much longer until bedtime?" ) ); - add_effect( effect_lack_sleep, 30_minutes + 1_turns ); - } - /** @EFFECT_INT slightly decreases occurrence of short naps when exhausted */ - if( one_in( 100 + int_cur ) ) { - fall_asleep( 30_seconds ); - } - } else if( get_fatigue() >= DEAD_TIRED && calendar::once_every( 30_minutes ) ) { - add_msg_if_player( m_warning, _( "*yawn* You should really get some sleep." ) ); - add_effect( effect_lack_sleep, 30_minutes + 1_turns ); - } - } - - // Sleep deprivation kicks in if lack of sleep is avoided with stimulants or otherwise for long periods of time - int sleep_deprivation = get_sleep_deprivation(); - float sleep_deprivation_pct = sleep_deprivation / static_cast( SLEEP_DEPRIVATION_MASSIVE ); - - if( sleep_deprivation >= SLEEP_DEPRIVATION_HARMLESS && !in_sleep_state() ) { - if( calendar::once_every( 60_minutes ) ) { - if( sleep_deprivation < SLEEP_DEPRIVATION_MINOR ) { - add_msg_if_player( m_warning, - _( "Your mind feels tired. It's been a while since you've slept well." ) ); - mod_fatigue( 1 ); - } else if( sleep_deprivation < SLEEP_DEPRIVATION_SERIOUS ) { - add_msg_if_player( m_bad, - _( "Your mind feels foggy from lack of good sleep, and your eyes keep trying to close against your will." ) ); - mod_fatigue( 5 ); - - if( one_in( 10 ) ) { - mod_healthy_mod( -1, 0 ); - } - } else if( sleep_deprivation < SLEEP_DEPRIVATION_MAJOR ) { - add_msg_if_player( m_bad, - _( "Your mind feels weary, and you dread every wakeful minute that passes. You crave sleep, and feel like you're about to collapse." ) ); - mod_fatigue( 10 ); - - if( one_in( 5 ) ) { - mod_healthy_mod( -2, 0 ); - } - } else if( sleep_deprivation < SLEEP_DEPRIVATION_MASSIVE ) { - add_msg_if_player( m_bad, - _( "You haven't slept decently for so long that your whole body is screaming for mercy. It's a miracle that you're still awake, but it just feels like a curse now." ) ); - mod_fatigue( 40 ); - - mod_healthy_mod( -5, 0 ); - } - // else you pass out for 20 hours, guaranteed - - // Microsleeps are slightly worse if you're sleep deprived, but not by much. (chance: 1 in (75 + int_cur) at lethal sleep deprivation) - // Note: these can coexist with fatigue-related microsleeps - /** @EFFECT_INT slightly decreases occurrence of short naps when sleep deprived */ - if( one_in( static_cast( sleep_deprivation_pct * 75 ) + int_cur ) ) { - fall_asleep( 30_seconds ); - } - - // Stimulants can be used to stay awake a while longer, but after a while you'll just collapse. - bool can_pass_out = ( get_stim() < 30 && sleep_deprivation >= SLEEP_DEPRIVATION_MINOR ) || - sleep_deprivation >= SLEEP_DEPRIVATION_MAJOR; - - if( can_pass_out && calendar::once_every( 10_minutes ) ) { - /** @EFFECT_PER slightly increases resilience against passing out from sleep deprivation */ - if( one_in( static_cast( ( 1 - sleep_deprivation_pct ) * 100 ) + per_cur ) || - sleep_deprivation >= SLEEP_DEPRIVATION_MASSIVE ) { - add_msg_player_or_npc( m_bad, - _( "Your body collapses due to sleep deprivation, your neglected fatigue rushing back all at once, and you pass out on the spot." ) - , _( " collapses to the ground from exhaustion." ) ); - if( get_fatigue() < EXHAUSTED ) { - set_fatigue( EXHAUSTED ); - } - - if( sleep_deprivation >= SLEEP_DEPRIVATION_MAJOR ) { - fall_asleep( 20_hours ); - } else if( sleep_deprivation >= SLEEP_DEPRIVATION_SERIOUS ) { - fall_asleep( 16_hours ); - } else { - fall_asleep( 12_hours ); - } - } - } - - } - } - -} - -needs_rates player::calc_needs_rates() const -{ - const effect &sleep = get_effect( effect_sleep ); - const bool has_recycler = has_bionic( bio_recycler ); - const bool asleep = !sleep.is_null(); - - needs_rates rates; - rates.hunger = metabolic_rate(); - - // TODO: this is where calculating basal metabolic rate, in kcal per day would go - rates.kcal = 2500.0; - - add_msg_if_player( m_debug, "Metabolic rate: %.2f", rates.hunger ); - - rates.thirst = get_option< float >( "PLAYER_THIRST_RATE" ); - rates.thirst *= 1.0f + mutation_value( "thirst_modifier" ); - if( worn_with_flag( "SLOWS_THIRST" ) ) { - rates.thirst *= 0.7f; - } - - rates.fatigue = get_option< float >( "PLAYER_FATIGUE_RATE" ); - rates.fatigue *= 1.0f + mutation_value( "fatigue_modifier" ); - - // Note: intentionally not in metabolic rate - if( has_recycler ) { - // Recycler won't help much with mutant metabolism - it is intended for human one - rates.hunger = std::min( rates.hunger, std::max( 0.5f, rates.hunger - 0.5f ) ); - rates.thirst = std::min( rates.thirst, std::max( 0.5f, rates.thirst - 0.5f ) ); - } - - if( asleep ) { - rates.recovery = 1.0f + mutation_value( "fatigue_regen_modifier" ); - if( !is_hibernating() ) { - // Hunger and thirst advance more slowly while we sleep. This is the standard rate. - rates.hunger *= 0.5f; - rates.thirst *= 0.5f; - const int intense = sleep.is_null() ? 0 : sleep.get_intensity(); - // Accelerated recovery capped to 2x over 2 hours - // After 16 hours of activity, equal to 7.25 hours of rest - const int accelerated_recovery_chance = 24 - intense + 1; - const float accelerated_recovery_rate = 1.0f / accelerated_recovery_chance; - rates.recovery += accelerated_recovery_rate; - } else { - // Hunger and thirst advance *much* more slowly whilst we hibernate. - rates.hunger *= ( 2.0f / 7.0f ); - rates.thirst *= ( 2.0f / 7.0f ); - } - rates.recovery -= static_cast( get_perceived_pain() ) / 60; - - } else { - rates.recovery = 0; - } - - if( has_activity( activity_id( "ACT_TREE_COMMUNION" ) ) ) { - // Much of the body's needs are taken care of by the trees. - // Hair Roots dont provide any bodily needs. - if( has_trait( trait_ROOTS2 ) || has_trait( trait_ROOTS3 ) ) { - rates.hunger *= 0.5f; - rates.thirst *= 0.5f; - rates.fatigue *= 0.5f; - } - } - - if( has_trait( trait_TRANSPIRATION ) ) { - // Transpiration, the act of moving nutrients with evaporating water, can take a very heavy toll on your thirst when it's really hot. - rates.thirst *= ( ( g->weather.get_temperature( pos() ) - 32.5f ) / 40.0f ); - } - - if( is_npc() ) { - rates.hunger *= 0.25f; - rates.thirst *= 0.25f; - } - - return rates; -} - -void player::update_needs( int rate_multiplier ) -{ - const int current_stim = get_stim(); - // Hunger, thirst, & fatigue up every 5 minutes - effect &sleep = get_effect( effect_sleep ); - // No food/thirst/fatigue clock at all - const bool debug_ls = has_trait( trait_DEBUG_LS ); - // No food/thirst, capped fatigue clock (only up to tired) - const bool npc_no_food = is_npc() && get_option( "NO_NPC_FOOD" ); - const bool asleep = !sleep.is_null(); - const bool lying = asleep || has_effect( effect_lying_down ) || - activity.id() == "ACT_TRY_SLEEP"; - - needs_rates rates = calc_needs_rates(); - - const bool wasnt_fatigued = get_fatigue() <= DEAD_TIRED; - // Don't increase fatigue if sleeping or trying to sleep or if we're at the cap. - if( get_fatigue() < 1050 && !asleep && !debug_ls ) { - if( rates.fatigue > 0.0f ) { - int fatigue_roll = roll_remainder( rates.fatigue * rate_multiplier ); - mod_fatigue( fatigue_roll ); - - if( get_option< bool >( "SLEEP_DEPRIVATION" ) ) { - // Synaptic regen bionic stops SD while awake and boosts it while sleeping - if( !has_active_bionic( bio_synaptic_regen ) ) { - // fatigue_roll should be around 1 - so the counter increases by 1 every minute on average, - // but characters who need less sleep will also get less sleep deprived, and vice-versa. - - // Note: Since needs are updated in 5-minute increments, we have to multiply the roll again by - // 5. If rate_multiplier is > 1, fatigue_roll will be higher and this will work out. - mod_sleep_deprivation( fatigue_roll * 5 ); - } - } - - if( npc_no_food && get_fatigue() > TIRED ) { - set_fatigue( TIRED ); - set_sleep_deprivation( 0 ); - } - } - } else if( asleep ) { - if( rates.recovery > 0.0f ) { - int recovered = roll_remainder( rates.recovery * rate_multiplier ); - if( get_fatigue() - recovered < -20 ) { - // Should be wake up, but that could prevent some retroactive regeneration - sleep.set_duration( 1_turns ); - mod_fatigue( -25 ); - } else { - if( has_effect( effect_recently_coughed ) ) { - recovered *= .5; - } - mod_fatigue( -recovered ); - if( get_option< bool >( "SLEEP_DEPRIVATION" ) ) { - // Sleeping on the ground, no bionic = 1x rest_modifier - // Sleeping on a bed, no bionic = 2x rest_modifier - // Sleeping on a comfy bed, no bionic= 3x rest_modifier - - // Sleeping on the ground, bionic = 3x rest_modifier - // Sleeping on a bed, bionic = 6x rest_modifier - // Sleeping on a comfy bed, bionic = 9x rest_modifier - float rest_modifier = ( has_active_bionic( bio_synaptic_regen ) ? 3 : 1 ); - // Magnesium supplements also add a flat bonus to recovery speed - if( has_effect( effect_magnesium_supplements ) ) { - rest_modifier += 1; - } - - comfort_level comfort = base_comfort_value( pos() ); - - if( comfort >= comfort_level::very_comfortable ) { - rest_modifier *= 3; - } else if( comfort >= comfort_level::comfortable ) { - rest_modifier *= 2.5; - } else if( comfort >= comfort_level::slightly_comfortable ) { - rest_modifier *= 2; - } - - // If we're just tired, we'll get a decent boost to our sleep quality. - // The opposite is true for very tired characters. - if( get_fatigue() < DEAD_TIRED ) { - rest_modifier += 2; - } else if( get_fatigue() >= EXHAUSTED ) { - rest_modifier = ( rest_modifier > 2 ) ? rest_modifier - 2 : 1; - } - - // Recovered is multiplied by 2 as well, since we spend 1/3 of the day sleeping - mod_sleep_deprivation( -rest_modifier * ( recovered * 2 ) ); - } - } - } - } - if( is_player() && wasnt_fatigued && get_fatigue() > DEAD_TIRED && !lying ) { - if( !activity ) { - add_msg_if_player( m_warning, _( "You're feeling tired. %s to lie down for sleep." ), - press_x( ACTION_SLEEP ) ); - } else { - g->cancel_activity_query( _( "You're feeling tired." ) ); - } - } - - if( current_stim < 0 ) { - set_stim( std::min( current_stim + rate_multiplier, 0 ) ); - } else if( current_stim > 0 ) { - set_stim( std::max( current_stim - rate_multiplier, 0 ) ); - } - - if( get_painkiller() > 0 ) { - mod_painkiller( -std::min( get_painkiller(), rate_multiplier ) ); - } - - // Huge folks take penalties for cramming themselves in vehicles - if( in_vehicle && ( has_trait( trait_HUGE ) || has_trait( trait_HUGE_OK ) ) ) { - vehicle *veh = veh_pointer_or_null( g->m.veh_at( pos() ) ); - // it's painful to work the controls, but passengers in open topped vehicles are fine - if( veh && ( veh->enclosed_at( pos() ) || veh->player_in_control( *this ) ) ) { - add_msg_if_player( m_bad, - _( "You're cramping up from stuffing yourself in this vehicle." ) ); - if( is_npc() ) { - npc &as_npc = dynamic_cast( *this ); - as_npc.complain_about( "cramped_vehicle", 1_hours, "", false ); - } - mod_pain_noresist( 2 * rng( 2, 3 ) ); - focus_pool -= 1; - } - } -} - -void player::regen( int rate_multiplier ) -{ - int pain_ticks = rate_multiplier; - while( get_pain() > 0 && pain_ticks-- > 0 ) { - mod_pain( -roll_remainder( 0.2f + get_pain() / 50.0f ) ); - } - - float rest = rest_quality(); - float heal_rate = healing_rate( rest ) * to_turns( 5_minutes ); - if( heal_rate > 0.0f ) { - healall( roll_remainder( rate_multiplier * heal_rate ) ); - } else if( heal_rate < 0.0f ) { - int rot_rate = roll_remainder( rate_multiplier * -heal_rate ); - // Has to be in loop because some effects depend on rounding - while( rot_rate-- > 0 ) { - hurtall( 1, nullptr, false ); - } - } - - // include healing effects - for( int i = 0; i < num_hp_parts; i++ ) { - body_part bp = hp_to_bp( static_cast( i ) ); - float healing = healing_rate_medicine( rest, bp ) * to_turns( 5_minutes ); - - int healing_apply = roll_remainder( healing ); - healed_bp( i, healing_apply ); - heal( bp, healing_apply ); - if( damage_bandaged[i] > 0 ) { - damage_bandaged[i] -= healing_apply; - if( damage_bandaged[i] <= 0 ) { - damage_bandaged[i] = 0; - remove_effect( effect_bandaged, bp ); - add_msg_if_player( _( "Bandaged wounds on your %s was healed." ), body_part_name( bp ) ); - } - } - if( damage_disinfected[i] > 0 ) { - damage_disinfected[i] -= healing_apply; - if( damage_disinfected[i] <= 0 ) { - damage_disinfected[i] = 0; - remove_effect( effect_disinfected, bp ); - add_msg_if_player( _( "Disinfected wounds on your %s was healed." ), body_part_name( bp ) ); - } - } - - // remove effects if the limb was healed by other way - if( has_effect( effect_bandaged, bp ) && ( hp_cur[i] == hp_max[i] ) ) { - damage_bandaged[i] = 0; - remove_effect( effect_bandaged, bp ); - add_msg_if_player( _( "Bandaged wounds on your %s was healed." ), body_part_name( bp ) ); - } - if( has_effect( effect_disinfected, bp ) && ( hp_cur[i] == hp_max[i] ) ) { - damage_disinfected[i] = 0; - remove_effect( effect_disinfected, bp ); - add_msg_if_player( _( "Disinfected wounds on your %s was healed." ), body_part_name( bp ) ); - } - } - - if( get_rad() > 0 ) { - mod_rad( -roll_remainder( rate_multiplier / 50.0f ) ); - } -} - -bool player::is_hibernating() const -{ - // Hibernating only kicks in whilst Engorged; separate tracking for hunger/thirst here - // as a safety catch. One test subject managed to get two Colds during hibernation; - // since those add fatigue and dry out the character, the subject went for the full 10 days plus - // a little, and came out of it well into Parched. Hibernating shouldn't endanger your - // life like that--but since there's much less fluid reserve than food reserve, - // simply using the same numbers won't work. - return has_effect( effect_sleep ) && get_kcal_percent() > 0.8f && - get_thirst() <= 80 && has_active_mutation( trait_id( "HIBERNATE" ) ); -} - void player::siphon( vehicle &veh, const itype_id &desired_liquid ) { auto qty = veh.fuel_left( desired_liquid ); @@ -5316,133 +4623,6 @@ void player::try_to_sleep( const time_duration &dur ) assign_activity( activity_id( "ACT_TRY_SLEEP" ), to_moves( dur ) ); } -comfort_level player::base_comfort_value( const tripoint &p ) const -{ - // Comfort of sleeping spots is "objective", while sleep_spot( p ) is "subjective" - // As in the latter also checks for fatigue and other variables while this function - // only looks at the base comfyness of something. It's still subjective, in a sense, - // as arachnids who sleep in webs will find most places comfortable for instance. - int comfort = 0; - - bool plantsleep = has_trait( trait_CHLOROMORPH ); - bool fungaloid_cosplay = has_trait( trait_M_SKIN3 ); - bool websleep = has_trait( trait_WEB_WALKER ); - bool webforce = has_trait( trait_THRESH_SPIDER ) && ( has_trait( trait_WEB_SPINNER ) || - ( has_trait( trait_WEB_WEAVER ) ) ); - bool in_shell = has_active_mutation( trait_SHELL2 ); - bool watersleep = has_trait( trait_WATERSLEEP ); - - const optional_vpart_position vp = g->m.veh_at( p ); - const maptile tile = g->m.maptile_at( p ); - const trap &trap_at_pos = tile.get_trap_t(); - const ter_id ter_at_pos = tile.get_ter(); - const furn_id furn_at_pos = tile.get_furn(); - - int web = g->m.get_field_intensity( p, fd_web ); - - // Some mutants have different comfort needs - if( !plantsleep && !webforce ) { - if( in_shell ) { - comfort += 1 + static_cast( comfort_level::slightly_comfortable ); - // Note: shelled individuals can still use sleeping aids! - } else if( vp ) { - vehicle &veh = vp->vehicle(); - const cata::optional carg = vp.part_with_feature( "CARGO", false ); - const cata::optional board = vp.part_with_feature( "BOARDABLE", true ); - if( carg ) { - vehicle_stack items = veh.get_items( carg->part_index() ); - for( auto &items_it : items ) { - if( items_it.has_flag( "SLEEP_AID" ) ) { - // Note: BED + SLEEP_AID = 9 pts, or 1 pt below very_comfortable - comfort += 1 + static_cast( comfort_level::slightly_comfortable ); - add_msg_if_player( m_info, _( "You use your %s for comfort." ), items_it.tname() ); - break; // prevents using more than 1 sleep aid - } - } - } - if( board ) { - comfort += board->info().comfort; - } else { - comfort -= g->m.move_cost( p ); - } - } - // Not in a vehicle, start checking furniture/terrain/traps at this point in decreasing order - else if( furn_at_pos != f_null ) { - comfort += 0 + furn_at_pos.obj().comfort; - } - // Web sleepers can use their webs if better furniture isn't available - else if( websleep && web >= 3 ) { - comfort += 1 + static_cast( comfort_level::slightly_comfortable ); - } else if( ter_at_pos == t_improvised_shelter ) { - comfort += 0 + static_cast( comfort_level::slightly_comfortable ); - } else if( ter_at_pos == t_floor || ter_at_pos == t_floor_waxed || - ter_at_pos == t_carpet_red || ter_at_pos == t_carpet_yellow || - ter_at_pos == t_carpet_green || ter_at_pos == t_carpet_purple ) { - comfort += 1 + static_cast( comfort_level::neutral ); - } else if( !trap_at_pos.is_null() ) { - comfort += 0 + trap_at_pos.comfort; - } else { - // Not a comfortable sleeping spot - comfort -= g->m.move_cost( p ); - } - - auto items = g->m.i_at( p ); - for( auto &items_it : items ) { - if( items_it.has_flag( "SLEEP_AID" ) ) { - // Note: BED + SLEEP_AID = 9 pts, or 1 pt below very_comfortable - comfort += 1 + static_cast( comfort_level::slightly_comfortable ); - add_msg_if_player( m_info, _( "You use your %s for comfort." ), items_it.tname() ); - break; // prevents using more than 1 sleep aid - } - } - - if( fungaloid_cosplay && g->m.has_flag_ter_or_furn( "FUNGUS", pos() ) ) { - comfort += static_cast( comfort_level::very_comfortable ); - } else if( watersleep && g->m.has_flag_ter( "SWIMMABLE", pos() ) ) { - comfort += static_cast( comfort_level::very_comfortable ); - } - } else if( plantsleep ) { - if( vp || furn_at_pos != f_null ) { - // Sleep ain't happening in a vehicle or on furniture - comfort = static_cast( comfort_level::impossible ); - } else { - // It's very easy for Chloromorphs to get to sleep on soil! - if( ter_at_pos == t_dirt || ter_at_pos == t_pit || ter_at_pos == t_dirtmound || - ter_at_pos == t_pit_shallow ) { - comfort += static_cast( comfort_level::very_comfortable ); - } - // Not as much if you have to dig through stuff first - else if( ter_at_pos == t_grass ) { - comfort += static_cast( comfort_level::comfortable ); - } - // Sleep ain't happening - else { - comfort = static_cast( comfort_level::impossible ); - } - } - // Has webforce - } else { - if( web >= 3 ) { - // Thick Web and you're good to go - comfort += static_cast( comfort_level::very_comfortable ); - } else { - comfort = static_cast( comfort_level::impossible ); - } - } - - if( comfort > static_cast( comfort_level::comfortable ) ) { - return comfort_level::very_comfortable; - } else if( comfort > static_cast( comfort_level::slightly_comfortable ) ) { - return comfort_level::comfortable; - } else if( comfort > static_cast( comfort_level::neutral ) ) { - return comfort_level::slightly_comfortable; - } else if( comfort == static_cast( comfort_level::neutral ) ) { - return comfort_level::neutral; - } else { - return comfort_level::uncomfortable; - } -} - int player::sleep_spot( const tripoint &p ) const { const int current_stim = get_stim(); @@ -6660,55 +5840,6 @@ std::set player::get_path_avoid() const return ret; } -void player::do_skill_rust() -{ - const int rate = rust_rate(); - if( rate <= 0 ) { - return; - } - for( auto &pair : *_skills ) { - if( rate <= rng( 0, 1000 ) ) { - continue; - } - - const Skill &aSkill = *pair.first; - SkillLevel &skill_level_obj = pair.second; - - if( aSkill.is_combat_skill() && - ( ( has_trait( trait_PRED2 ) && one_in( 4 ) ) || - ( has_trait( trait_PRED3 ) && one_in( 2 ) ) || - ( has_trait( trait_PRED4 ) && x_in_y( 2, 3 ) ) ) ) { - // Their brain is optimized to remember this - if( one_in( 15600 ) ) { - // They've already passed the roll to avoid rust at - // this point, but print a message about it now and - // then. - // - // 13 combat skills, 600 turns/hr, 7800 tests/hr. - // This means PRED2/PRED3/PRED4 think of hunting on - // average every 8/4/3 hours, enough for immersion - // without becoming an annoyance. - // - add_msg_if_player( _( "Your heart races as you recall your most recent hunt." ) ); - mod_stim( 1 ); - } - continue; - } - - const bool charged_bio_mem = get_power_level() > 25_kJ && has_active_bionic( bio_memory ); - const int oldSkillLevel = skill_level_obj.level(); - if( skill_level_obj.rust( charged_bio_mem ) ) { - add_msg_if_player( m_warning, - _( "Your knowledge of %s begins to fade, but your memory banks retain it!" ), aSkill.name() ); - mod_power_level( -25_kJ ); - } - const int newSkill = skill_level_obj.level(); - if( newSkill < oldSkillLevel ) { - add_msg_if_player( m_bad, _( "Your skill in %s has reduced to %d!" ), aSkill.name(), newSkill ); - } - } -} - std::pair player::get_hunger_description() const { const bool calorie_deficit = get_bmi() < character_weight_category::normal; @@ -6795,13 +5926,3 @@ std::pair player::get_pain_description() const } return std::make_pair( pain_string, pain_color ); } - -void player::enforce_minimum_healing() -{ - for( int i = 0; i < num_hp_parts; i++ ) { - if( healed_total[i] <= 0 ) { - heal( static_cast( i ), 1 ); - } - healed_total[i] = 0; - } -} diff --git a/src/player.h b/src/player.h index 89de95852fe12..7ece00e6fa7e2 100644 --- a/src/player.h +++ b/src/player.h @@ -122,15 +122,6 @@ enum class rechargeable_cbm { other }; -enum class comfort_level { - impossible = -999, - uncomfortable = -7, - neutral = 0, - slightly_comfortable = 3, - comfortable = 5, - very_comfortable = 10 -}; - struct special_attack { std::string text; damage_instance damage; @@ -232,29 +223,6 @@ class player : public Character int swim_speed() const; /** Maintains body wetness and handles the rate at which the player dries */ void update_body_wetness( const w_point &weather ); - /** Updates all "biology" by one turn. Should be called once every turn. */ - void update_body(); - /** Updates all "biology" as if time between `from` and `to` passed. */ - void update_body( const time_point &from, const time_point &to ); - /** Updates the stomach to give accurate hunger messages */ - void update_stomach( const time_point &from, const time_point &to ); - /** Increases hunger, thirst, fatigue and stimulants wearing off. `rate_multiplier` is for retroactive updates. */ - void update_needs( int rate_multiplier ); - needs_rates calc_needs_rates() const; - - /** - * Handles passive regeneration of pain and maybe hp. - */ - void regen( int rate_multiplier ); - // called once per 24 hours to enforce the minimum of 1 hp healed per day - // TODO: Move to Character once heal() is moved - void enforce_minimum_healing(); - - /** Kills the player if too hungry, stimmed up etc., forces tired player to sleep and prints warnings. */ - void check_needs_extremes(); - - /** Returns if the player has hibernation mutation and is asleep and well fed */ - bool is_hibernating() const; /** Returns true if the player has a conflicting trait to the entered trait * Uses has_opposite_trait(), has_lower_trait(), and has_higher_trait() to determine conflicts. @@ -509,8 +477,6 @@ class player : public Character // Mental skills and stats /** Returns the player's reading speed */ int read_speed( bool return_stat_effect = true ) const; - /** Returns the player's skill rust rate */ - int rust_rate( bool return_stat_effect = true ) const; /** Returns a value used when attempting to convince NPC's of something */ int talk_skill() const; /** Returns a value used when attempting to intimidate NPC's */ @@ -556,8 +522,6 @@ class player : public Character /** Returns overall % of HP remaining */ int hp_percentage() const override; - /** Handles the chance to be infected by random diseases */ - void get_sick(); /** Returns list of rc items in player inventory. **/ std::list get_radio_items(); /** Returns list of artifacts in player inventory. **/ @@ -631,11 +595,6 @@ class player : public Character std::pair compute_nutrient_range( const itype_id &, const cata::flat_set &extra_flags = {} ) const; - /** Get vitamin usage rate (minutes per unit) accounting for bionics, mutations and effects */ - time_duration vitamin_rate( const vitamin_id &vit ) const; - - void vitamins_mod( const std::map &, bool capped = true ); - /** * Sets level of a vitamin or returns false if id given in vit does not exist * @@ -788,8 +747,6 @@ class player : public Character /** Handles sleep attempts by the player, starts ACT_TRY_SLEEP activity */ void try_to_sleep( const time_duration &dur = 30_minutes ); - /** Rate point's ability to serve as a bed. Only takes certain mutations into account, and not fatigue nor stimulants. */ - comfort_level base_comfort_value( const tripoint &p ) const; /** Rate point's ability to serve as a bed. Takes all mutations, fatigue and stimulants into account. */ int sleep_spot( const tripoint &p ) const; /** Checked each turn during "lying_down", returns true if the player falls asleep */ @@ -1137,9 +1094,6 @@ class player : public Character /** Search surrounding squares for traps (and maybe other things in the future). */ void search_surroundings(); - // TODO: make protected and move into Character - void do_skill_rust(); - /** * Called when a mutation is gained */