From 8f0b78ff635d10b0a05542b93a91c419e0be15f4 Mon Sep 17 00:00:00 2001 From: Fris0uman <41293484+Fris0uman@users.noreply.github.com> Date: Sat, 26 Oct 2019 08:49:30 +0200 Subject: [PATCH] Encapsulate Stamina (#34861) --- src/activity_handlers.cpp | 20 +++---- src/avatar.cpp | 2 +- src/avatar_action.cpp | 2 +- src/character.cpp | 108 +++++++++++++++++++++++++++++++++++- src/character.h | 10 +++- src/debug_menu.cpp | 4 +- src/game.cpp | 4 +- src/handle_action.cpp | 2 +- src/iuse.cpp | 4 +- src/magic.cpp | 6 +- src/melee.cpp | 9 +-- src/panels.cpp | 14 ++--- src/player.cpp | 112 ++++---------------------------------- src/player.h | 5 +- src/player_activity.cpp | 4 +- src/ranged.cpp | 2 +- src/savegame_json.cpp | 3 +- src/sounds.cpp | 22 ++++---- tests/throwing_test.cpp | 4 +- 19 files changed, 180 insertions(+), 157 deletions(-) diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 52147b5ab8f6f..1d914276f3cca 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -1770,7 +1770,7 @@ void activity_handlers::pulp_do_turn( player_activity *act, player *p ) p->practice( skill_survival, 2, 2 ); } - float stamina_ratio = static_cast( p->stamina ) / p->get_stamina_max(); + float stamina_ratio = static_cast( p->get_stamina() ) / p->get_stamina_max(); moves += 100 / std::max( 0.25f, stamina_ratio ); if( stamina_ratio < 0.33 || p->is_npc() ) { p->moves = std::min( 0, p->moves - moves ); @@ -2884,10 +2884,10 @@ void activity_handlers::read_do_turn( player_activity *act, player *p ) if( p->is_player() ) { if( !act->str_values.empty() && act->str_values[0] == "martial_art" && one_in( 3 ) ) { if( act->values.empty() ) { - act->values.push_back( p->stamina ); + act->values.push_back( p->get_stamina() ); } - p->stamina = act->values[0] - 1; - act->values[0] = p->stamina; + p->set_stamina( act->values[0] - 1 ); + act->values[0] = p->get_stamina(); } } else { p->moves = 0; @@ -2979,10 +2979,10 @@ void activity_handlers::wait_stamina_do_turn( player_activity *act, player *p ) stamina_threshold = act->values[0]; // remember initial stamina, only for waiting-with-threshold if( act->values.size() == 1 ) { - act->values.push_back( p->stamina ); + act->values.push_back( p->get_stamina() ); } } - if( p->stamina >= stamina_threshold ) { + if( p->get_stamina() >= stamina_threshold ) { wait_stamina_finish( act, p ); } } @@ -2991,12 +2991,12 @@ void activity_handlers::wait_stamina_finish( player_activity *act, player *p ) { if( !act->values.empty() ) { const int stamina_threshold = act->values[0]; - const int stamina_initial = ( act->values.size() > 1 ) ? act->values[1] : p->stamina; - if( p->stamina < stamina_threshold && p->stamina <= stamina_initial ) { + const int stamina_initial = ( act->values.size() > 1 ) ? act->values[1] : p->get_stamina(); + if( p->get_stamina() < stamina_threshold && p->get_stamina() <= stamina_initial ) { debugmsg( "Failed to wait until stamina threshold %d reached, only at %d. You may not be regaining stamina.", - act->values.front(), p->stamina ); + act->values.front(), p->get_stamina() ); } - } else if( p->stamina < p->get_stamina_max() ) { + } else if( p->get_stamina() < p->get_stamina_max() ) { p->add_msg_if_player( _( "You are bored of waiting, so you stop." ) ); } else { p->add_msg_if_player( _( "You finish waiting and feel refreshed." ) ); diff --git a/src/avatar.cpp b/src/avatar.cpp index 447754e1d5538..eb37cfe83d0f7 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -613,7 +613,7 @@ bool avatar::read( int inventory_position, const bool continuous ) // push an indentifier of martial art book to the action handling if( it.type->use_methods.count( "MA_MANUAL" ) ) { - if( g->u.stamina < g->u.get_stamina_max() / 10 ) { + if( g->u.get_stamina() < g->u.get_stamina_max() / 10 ) { add_msg( m_info, _( "You are too exhausted to train martial arts." ) ); return false; } diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 8f53a1d7ee30c..0ef6be815f5bb 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -750,7 +750,7 @@ bool avatar_action::fire( avatar &you, map &m ) you.mod_stat( "stamina", gun->get_min_str() * static_cast( 0.002f * get_option( "PLAYER_MAX_STAMINA" ) ) ); // At low stamina levels, firing starts getting slow. - int sta_percent = ( 100 * you.stamina ) / you.get_stamina_max(); + int sta_percent = ( 100 * you.get_stamina() ) / you.get_stamina_max(); reload_time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0; // Update targeting data to include ammo's range bonus diff --git a/src/character.cpp b/src/character.cpp index 7567a8b412bd2..76a655abc392d 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -70,6 +70,7 @@ static const bionic_id bio_armor_head( "bio_armor_head" ); static const bionic_id bio_armor_legs( "bio_armor_legs" ); static const bionic_id bio_armor_torso( "bio_armor_torso" ); static const bionic_id bio_carbon( "bio_carbon" ); +static const bionic_id bio_gills( "bio_gills" ); static const bionic_id bio_laser( "bio_laser" ); static const bionic_id bio_lighter( "bio_lighter" ); static const bionic_id bio_tools( "bio_tools" ); @@ -120,6 +121,7 @@ const skill_id skill_throw( "throw" ); static const trait_id trait_ACIDBLOOD( "ACIDBLOOD" ); static const trait_id trait_ADRENALINE( "ADRENALINE" ); +static const trait_id trait_BADBACK( "BADBACK" ); 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" ); @@ -193,6 +195,7 @@ Character::Character() : fatigue = 0; sleep_deprivation = 0; set_stim( 0 ); + set_stamina( 10000 ); //Temporary value for stamina. It will be reset later from external json option. pkill = 0; // 45 days to starve to death healthy_calories = 55000; @@ -716,7 +719,7 @@ bool Character::is_limb_broken( hp_part limb ) const bool Character::can_run() { - return stamina > 0 && !has_effect( effect_winded ) && get_working_leg_count() >= 2; + return get_stamina() > 0 && !has_effect( effect_winded ) && get_working_leg_count() >= 2; } bool Character::move_effects( bool attacking ) @@ -4222,6 +4225,109 @@ void Character::mod_stim( int mod ) stim += mod; } +int Character::get_stamina() const +{ + return stamina; +} + +int Character::get_stamina_max() const +{ + int maxStamina = get_option< int >( "PLAYER_MAX_STAMINA" ); + maxStamina *= Character::mutation_value( "max_stamina_modifier" ); + return maxStamina; +} + +void Character::set_stamina( int new_stamina ) +{ + stamina = new_stamina; +} + +void Character::mod_stamina( int mod ) +{ + stamina += mod; +} + +void Character::burn_move_stamina( int moves ) +{ + int overburden_percentage = 0; + units::mass current_weight = weight_carried(); + units::mass max_weight = weight_capacity(); + if( current_weight > max_weight ) { + overburden_percentage = ( current_weight - max_weight ) * 100 / max_weight; + } + + int burn_ratio = get_option( "PLAYER_BASE_STAMINA_BURN_RATE" ); + for( const bionic_id &bid : get_bionic_fueled_with( item( "muscle" ) ) ) { + if( has_active_bionic( bid ) ) { + burn_ratio = burn_ratio * 2 - 3; + } + } + burn_ratio += overburden_percentage; + if( move_mode == CMM_RUN ) { + burn_ratio = burn_ratio * 7; + } + mod_stat( "stamina", -( ( moves * burn_ratio ) / 100.0 ) ); + add_msg( m_debug, "Stamina burn: %d", -( ( moves * burn_ratio ) / 100 ) ); + // Chance to suffer pain if overburden and stamina runs out or has trait BADBACK + // Starts at 1 in 25, goes down by 5 for every 50% more carried + if( ( current_weight > max_weight ) && ( has_trait( trait_BADBACK ) || get_stamina() == 0 ) && + one_in( 35 - 5 * current_weight / ( max_weight / 2 ) ) ) { + add_msg_if_player( m_bad, _( "Your body strains under the weight!" ) ); + // 1 more pain for every 800 grams more (5 per extra STR needed) + if( ( ( current_weight - max_weight ) / 800_gram > get_pain() && get_pain() < 100 ) ) { + mod_pain( 1 ); + } + } +} + +void Character::update_stamina( int turns ) +{ + const int current_stim = get_stim(); + float stamina_recovery = 0.0f; + // Recover some stamina every turn. + // Mutated stamina works even when winded + float stamina_multiplier = ( !has_effect( effect_winded ) ? 1.0f : 0.1f ) + + mutation_value( "stamina_regen_modifier" ); + // But mouth encumbrance interferes, even with mutated stamina. + stamina_recovery += stamina_multiplier * std::max( 1.0f, + get_option( "PLAYER_BASE_STAMINA_REGEN_RATE" ) - ( encumb( bp_mouth ) / 5.0f ) ); + // TODO: recovering stamina causes hunger/thirst/fatigue. + // TODO: Tiredness slowing recovery + + // stim recovers stamina (or impairs recovery) + if( current_stim > 0 ) { + // TODO: Make stamina recovery with stims cost health + stamina_recovery += std::min( 5.0f, current_stim / 15.0f ); + } else if( current_stim < 0 ) { + // Affect it less near 0 and more near full + // Negative stim kill at -200 + // At -100 stim it inflicts -20 malus to regen at 100% stamina, + // effectivly countering stamina gain of default 20, + // at 50% stamina its -10 (50%), cuts by 25% at 25% stamina + stamina_recovery += current_stim / 5.0f * get_stamina() / get_stamina_max(); + } + + const int max_stam = get_stamina_max(); + if( get_power_level() >= 3_kJ && has_active_bionic( bio_gills ) ) { + int bonus = std::min( units::to_kilojoule( get_power_level() ) / 3, + max_stam - get_stamina() - stamina_recovery * turns ); + // so the effective recovery is up to 5x default + bonus = std::min( bonus, 4 * static_cast + ( get_option( "PLAYER_BASE_STAMINA_REGEN_RATE" ) ) ); + if( bonus > 0 ) { + stamina_recovery += bonus; + bonus /= 10; + bonus = std::max( bonus, 1 ); + mod_power_level( units::from_kilojoule( -bonus ) ); + } + } + + mod_stamina( roll_remainder( stamina_recovery * turns ) ); + add_msg( m_debug, "Stamina recovery: %d", roll_remainder( stamina_recovery * turns ) ); + // Cap at max + set_stamina( std::min( std::max( get_stamina(), 0 ), max_stam ) ); +} + int Character::item_handling_cost( const item &it, bool penalties, int base_cost ) const { int mv = base_cost; diff --git a/src/character.h b/src/character.h index e202d3529ffc0..8fd60f7e3a6b5 100644 --- a/src/character.h +++ b/src/character.h @@ -1092,7 +1092,6 @@ class Character : public Creature, public visitable stomach_contents guts; int oxygen; - int stamina; int radiation; std::shared_ptr mounted_creature; @@ -1166,6 +1165,14 @@ class Character : public Creature, public visitable void set_stim( int new_stim ); void mod_stim( int mod ); + int get_stamina() const; + int get_stamina_max() const; + void set_stamina( int new_stamina ); + void mod_stamina( int mod ); + void burn_move_stamina( int moves ); + /** Regenerates stamina */ + void update_stamina( int turns ); + protected: void on_stat_change( const std::string &, int ) override {} void on_damage_of_type( int adjusted_damage, damage_type type, body_part bp ) override; @@ -1378,6 +1385,7 @@ class Character : public Creature, public visitable int hunger; int thirst; + int stamina; int fatigue; int sleep_deprivation; diff --git a/src/debug_menu.cpp b/src/debug_menu.cpp index 62309ca9d1e8c..709cc6c3400c0 100644 --- a/src/debug_menu.cpp +++ b/src/debug_menu.cpp @@ -566,10 +566,10 @@ void character_edit_menu() break; case D_STAMINA: int value; - if( query_int( value, _( "Set stamina to? Current: %d. Max: %d." ), p.stamina, + if( query_int( value, _( "Set stamina to? Current: %d. Max: %d." ), p.get_stamina(), p.get_stamina_max() ) ) { if( value >= 0 && value <= p.get_stamina_max() ) { - p.stamina = value; + p.set_stamina( value ); } else { add_msg( m_bad, _( "Target stamina value out of bounds!" ) ); } diff --git a/src/game.cpp b/src/game.cpp index 907c8571af8f6..9ac248f65ecc2 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -747,7 +747,7 @@ bool game::start_game() u.moves = 0; u.process_turn(); // process_turn adds the initial move points - u.stamina = u.get_stamina_max(); + u.set_stamina( u.get_stamina_max() ); weather.temperature = SPRING_TEMPERATURE; weather.update_weather(); u.next_climate_control_check = calendar::before_time_starts; // Force recheck at startup @@ -9974,7 +9974,7 @@ void game::on_move_effects() if( !u.can_run() ) { u.toggle_run_mode(); } - if( u.stamina < u.get_stamina_max() / 5 && one_in( u.stamina ) ) { + if( u.get_stamina() < u.get_stamina_max() / 5 && one_in( u.get_stamina() ) ) { u.add_effect( effect_winded, 10_turns ); } } diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 666ec430cbd0f..a312e61bce858 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -798,7 +798,7 @@ static void wait() } } else { - if( g->u.stamina < g->u.get_stamina_max() ) { + if( g->u.get_stamina() < g->u.get_stamina_max() ) { as_m.addentry( 12, true, 'w', _( "Wait until you catch your breath" ) ); durations.emplace( 12, 15_minutes ); // to hide it from showing } diff --git a/src/iuse.cpp b/src/iuse.cpp index 1cc86a60c260b..92560f08eb009 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -5400,7 +5400,7 @@ int iuse::artifact( player *p, item *it, bool, const tripoint & ) case AEA_STAMINA_EMPTY: p->add_msg_if_player( m_bad, _( "Your body feels like jelly." ) ); - p->stamina = p->stamina * 1 / ( rng( 3, 8 ) ); + p->set_stamina( p->get_stamina() * 1 / ( rng( 3, 8 ) ) ); break; case AEA_FUN: @@ -5744,7 +5744,7 @@ int iuse::stimpack( player *p, item *it, bool, const tripoint & ) p->mod_painkiller( 2 ); p->mod_stim( 20 ); p->mod_fatigue( -100 ); - p->stamina = p->get_stamina_max(); + p->set_stamina( p->get_stamina_max() ); } return it->type->charges_to_use(); } diff --git a/src/magic.cpp b/src/magic.cpp index d4ab175a21e07..f407e3ad7f4a1 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -621,7 +621,7 @@ bool spell::can_cast( const player &p ) const case mana_energy: return p.magic.available_mana() >= energy_cost( p ); case stamina_energy: - return p.stamina >= energy_cost( p ); + return p.get_stamina() >= energy_cost( p ); case hp_energy: { for( int i = 0; i < num_hp_parts; i++ ) { if( energy_cost( p ) < p.hp_cur[i] ) { @@ -812,7 +812,7 @@ std::string spell::energy_cur_string( const player &p ) const return colorize( to_string( p.magic.available_mana() ), c_light_blue ); } if( energy_source() == stamina_energy ) { - auto pair = get_hp_bar( p.stamina, p.get_stamina_max() ); + auto pair = get_hp_bar( p.get_stamina(), p.get_stamina_max() ); return colorize( pair.first, pair.second ); } if( energy_source() == hp_energy ) { @@ -1388,7 +1388,7 @@ bool known_magic::has_enough_energy( const player &p, spell &sp ) const case bionic_energy: return p.get_power_level() >= units::from_kilojoule( cost ); case stamina_energy: - return p.stamina >= cost; + return p.get_stamina() >= cost; case hp_energy: for( int i = 0; i < num_hp_parts; i++ ) { if( p.hp_cur[i] > cost ) { diff --git a/src/melee.cpp b/src/melee.cpp index f082272e3935c..c95de74e4d874 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -1405,7 +1405,7 @@ void player::perform_technique( const ma_technique &technique, Creature &t, dama // Remember out moves and stamina // We don't want to consume them for every attack! const int temp_moves = moves; - const int temp_stamina = stamina; + const int temp_stamina = get_stamina(); std::vector targets; @@ -1428,7 +1428,7 @@ void player::perform_technique( const ma_technique &technique, Creature &t, dama t.add_msg_if_player( m_good, ngettext( "%d enemy hit!", "%d enemies hit!", count_hit ), count_hit ); // Extra attacks are free of charge (otherwise AoE attacks would SUCK) moves = temp_moves; - stamina = temp_stamina; + set_stamina( temp_stamina ); } //player has a very small chance, based on their intelligence, to learn a style whilst using the CQB bionic @@ -1646,7 +1646,7 @@ bool player::block_hit( Creature *source, body_part &bp_hit, damage_instance &da matec_id tec = pick_technique( *source, shield, false, false, true ); if( tec != tec_none && !is_dead_state() ) { - if( stamina < get_stamina_max() / 3 ) { + if( get_stamina() < get_stamina_max() / 3 ) { add_msg( m_bad, _( "You try to counterattack but you are too exhausted!" ) ); } else { melee_attack( *source, false, tec ); @@ -2114,7 +2114,8 @@ int player::attack_speed( const item &weap ) const const int encumbrance_penalty = encumb( bp_torso ) + ( encumb( bp_hand_l ) + encumb( bp_hand_r ) ) / 2; const int ma_move_cost = mabuff_attack_cost_penalty(); - const float stamina_ratio = static_cast( stamina ) / static_cast( get_stamina_max() ); + const float stamina_ratio = static_cast( get_stamina() ) / static_cast + ( get_stamina_max() ); // Increase cost multiplier linearly from 1.0 to 2.0 as stamina goes from 25% to 0%. const float stamina_penalty = 1.0 + std::max( ( 0.25f - stamina_ratio ) * 4.0f, 0.0f ); const float ma_mult = mabuff_attack_cost_mult(); diff --git a/src/panels.cpp b/src/panels.cpp index 07eb039530ca5..4f0747236562a 100644 --- a/src/panels.cpp +++ b/src/panels.cpp @@ -954,7 +954,7 @@ static void draw_limb2( avatar &u, const catacurses::window &w ) mvwprintz( w, point( 27, 2 ), morale_pair.first, smiley ); // print stamina - const auto &stamina = get_hp_bar( u.stamina, u.get_stamina_max() ); + const auto &stamina = get_hp_bar( u.get_stamina(), u.get_stamina_max() ); mvwprintz( w, point( 22, 0 ), c_light_gray, _( "STM" ) ); mvwprintz( w, point( 26, 0 ), stamina.second, stamina.first ); @@ -1214,8 +1214,8 @@ static void draw_char_narrow( avatar &u, const catacurses::window &w ) mvwprintz( w, point( 8, 0 ), c_light_gray, "%s", u.volume ); // print stamina - auto needs_pair = std::make_pair( get_hp_bar( u.stamina, u.get_stamina_max() ).second, - get_hp_bar( u.stamina, u.get_stamina_max() ).first ); + auto needs_pair = std::make_pair( get_hp_bar( u.get_stamina(), u.get_stamina_max() ).second, + get_hp_bar( u.get_stamina(), u.get_stamina_max() ).first ); mvwprintz( w, point( 8, 1 ), needs_pair.first, needs_pair.second ); const int width = utf8_width( needs_pair.second ); for( int i = 0; i < 5 - width; i++ ) { @@ -1258,8 +1258,8 @@ static void draw_char_wide( avatar &u, const catacurses::window &w ) mvwprintz( w, point( 38, 0 ), focus_color( u.focus_pool ), "%s", u.focus_pool ); // print stamina - auto needs_pair = std::make_pair( get_hp_bar( u.stamina, u.get_stamina_max() ).second, - get_hp_bar( u.stamina, u.get_stamina_max() ).first ); + auto needs_pair = std::make_pair( get_hp_bar( u.get_stamina(), u.get_stamina_max() ).second, + get_hp_bar( u.get_stamina(), u.get_stamina_max() ).first ); mvwprintz( w, point( 8, 1 ), needs_pair.first, needs_pair.second ); const int width = utf8_width( needs_pair.second ); for( int i = 0; i < 5 - width; i++ ) { @@ -1591,8 +1591,8 @@ static void draw_health_classic( avatar &u, const catacurses::window &w ) mvwprintz( w, point( 40, 4 ), safe_color(), safe_str ); // print stamina - auto pair = std::make_pair( get_hp_bar( u.stamina, u.get_stamina_max() ).second, - get_hp_bar( u.stamina, u.get_stamina_max() ).first ); + auto pair = std::make_pair( get_hp_bar( u.get_stamina(), u.get_stamina_max() ).second, + get_hp_bar( u.get_stamina(), u.get_stamina_max() ).first ); mvwprintz( w, point( 35, 5 ), c_light_gray, _( "Stm" ) ); mvwprintz( w, point( 39, 5 ), pair.first, pair.second ); const int width = utf8_width( pair.second ); diff --git a/src/player.cpp b/src/player.cpp index 2df3b05742639..88b0d48ff4e9a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -262,7 +262,6 @@ static const trait_id trait_AMPHIBIAN( "AMPHIBIAN" ); static const trait_id trait_ANTENNAE( "ANTENNAE" ); static const trait_id trait_ANTLERS( "ANTLERS" ); static const trait_id trait_ASTHMA( "ASTHMA" ); -static const trait_id trait_BADBACK( "BADBACK" ); static const trait_id trait_BARK( "BARK" ); static const trait_id trait_CANNIBAL( "CANNIBAL" ); static const trait_id trait_CENOBITE( "CENOBITE" ); @@ -474,7 +473,6 @@ player::player() : blocks_left = 1; set_power_level( 0_kJ ); set_max_power_level( 0_kJ ); - stamina = 10000; //Temporary value for stamina. It will be reset later from external json option. radiation = 0; tank_plut = 0; reactor_plut = 0; @@ -554,7 +552,7 @@ void player::normalize() recalc_hp(); temp_conv.fill( BODYTEMP_NORM ); - stamina = get_stamina_max(); + set_stamina( get_stamina_max() ); } void player::process_turn() @@ -1568,8 +1566,8 @@ int player::run_cost( int base_cost, bool diag ) const } // Both walk and run speed drop to half their maximums as stamina approaches 0. // Convert stamina to a float first to allow for decimal place carrying - float stamina_modifier = ( static_cast( stamina ) / get_stamina_max() + 1 ) / 2; - if( move_mode == CMM_RUN && stamina > 0 ) { + float stamina_modifier = ( static_cast( get_stamina() ) / get_stamina_max() + 1 ) / 2; + if( move_mode == CMM_RUN && get_stamina() > 0 ) { // Rationale: Average running speed is 2x walking speed. (NOT sprinting) stamina_modifier *= 2.0; } @@ -1842,12 +1840,12 @@ void player::mod_stat( const std::string &stat, float modifier ) } else if( stat == "oxygen" ) { oxygen += modifier; } else if( stat == "stamina" ) { - if( stamina + modifier < 0 ) { + if( get_stamina() + modifier < 0 ) { add_effect( effect_winded, 10_turns ); } - stamina += modifier; - stamina = std::min( stamina, get_stamina_max() ); - stamina = std::max( 0, stamina ); + mod_stamina( modifier ); + set_stamina( std::min( get_stamina(), get_stamina_max() ) ); + set_stamina( std::max( 0, get_stamina() ) ); } else { // Fall through to the creature method. Character::mod_stat( stat, modifier ); @@ -2467,7 +2465,7 @@ void player::on_dodge( Creature *source, float difficulty ) matec_id tec = pick_technique( *source, used_weapon(), false, true, false ); if( tec != tec_none && !is_dead_state() ) { - if( stamina < get_stamina_max() / 3 ) { + if( get_stamina() < get_stamina_max() / 3 ) { add_msg( m_bad, _( "You try to counterattack but you are too exhausted!" ) ); } else { melee_attack( *source, false, tec ); @@ -3814,54 +3812,6 @@ void player::regen( int rate_multiplier ) } } -void player::update_stamina( int turns ) -{ - const int current_stim = get_stim(); - float stamina_recovery = 0.0f; - // Recover some stamina every turn. - // Mutated stamina works even when winded - float stamina_multiplier = ( !has_effect( effect_winded ) ? 1.0f : 0.1f ) + - mutation_value( "stamina_regen_modifier" ); - // But mouth encumbrance interferes, even with mutated stamina. - stamina_recovery += stamina_multiplier * std::max( 1.0f, - get_option( "PLAYER_BASE_STAMINA_REGEN_RATE" ) - ( encumb( bp_mouth ) / 5.0f ) ); - // TODO: recovering stamina causes hunger/thirst/fatigue. - // TODO: Tiredness slowing recovery - - // stim recovers stamina (or impairs recovery) - if( current_stim > 0 ) { - // TODO: Make stamina recovery with stims cost health - stamina_recovery += std::min( 5.0f, current_stim / 15.0f ); - } else if( current_stim < 0 ) { - // Affect it less near 0 and more near full - // Negative stim kill at -200 - // At -100 stim it inflicts -20 malus to regen at 100% stamina, - // effectivly countering stamina gain of default 20, - // at 50% stamina its -10 (50%), cuts by 25% at 25% stamina - stamina_recovery += current_stim / 5.0f * stamina / get_stamina_max() ; - } - - const int max_stam = get_stamina_max(); - if( get_power_level() >= 3_kJ && has_active_bionic( bio_gills ) ) { - int bonus = std::min( units::to_kilojoule( get_power_level() ) / 3, - max_stam - stamina - stamina_recovery * turns ); - // so the effective recovery is up to 5x default - bonus = std::min( bonus, 4 * static_cast - ( get_option( "PLAYER_BASE_STAMINA_REGEN_RATE" ) ) ); - if( bonus > 0 ) { - stamina_recovery += bonus; - bonus /= 10; - bonus = std::max( bonus, 1 ); - mod_power_level( units::from_kilojoule( -bonus ) ); - } - } - - stamina += roll_remainder( stamina_recovery * turns ); - add_msg( m_debug, "Stamina recovery: %d", roll_remainder( stamina_recovery * turns ) ); - // Cap at max - stamina = std::min( std::max( stamina, 0 ), max_stam ); -} - bool player::is_hibernating() const { // Hibernating only kicks in whilst Engorged; separate tracking for hunger/thirst here @@ -3972,7 +3922,7 @@ void player::siphon( vehicle &veh, const itype_id &desired_liquid ) void player::cough( bool harmful, int loudness ) { if( harmful ) { - const int stam = stamina; + const int stam = get_stamina(); const int malus = get_stamina_max() * 0.05; // 5% max stamina mod_stat( "stamina", -malus ); if( stam < malus && x_in_y( malus - stam, malus ) && one_in( 6 ) ) { @@ -4267,7 +4217,7 @@ void player::process_one_effect( effect &it, bool is_new ) if( val != 0 ) { mod = 1; if( is_new || it.activated( calendar::turn, "STAMINA", val, reduced, mod ) ) { - mod_stat( "stamina", bound_mod_to_vals( stamina, val, + mod_stat( "stamina", bound_mod_to_vals( get_stamina(), val, it.get_max_val( "STAMINA", reduced ), it.get_min_val( "STAMINA", reduced ) ) ); } @@ -9886,46 +9836,6 @@ int player::get_hp_max( hp_part bp ) const return hp_total; } -int player::get_stamina_max() const -{ - int maxStamina = get_option< int >( "PLAYER_MAX_STAMINA" ); - maxStamina *= Character::mutation_value( "max_stamina_modifier" ); - return maxStamina; -} - -void player::burn_move_stamina( int moves ) -{ - int overburden_percentage = 0; - units::mass current_weight = weight_carried(); - units::mass max_weight = weight_capacity(); - if( current_weight > max_weight ) { - overburden_percentage = ( current_weight - max_weight ) * 100 / max_weight; - } - - int burn_ratio = get_option( "PLAYER_BASE_STAMINA_BURN_RATE" ); - for( const bionic_id &bid : get_bionic_fueled_with( item( "muscle" ) ) ) { - if( has_active_bionic( bid ) ) { - burn_ratio = burn_ratio * 2 - 3; - } - } - burn_ratio += overburden_percentage; - if( move_mode == CMM_RUN ) { - burn_ratio = burn_ratio * 7; - } - mod_stat( "stamina", -( ( moves * burn_ratio ) / 100.0 ) ); - add_msg( m_debug, "Stamina burn: %d", -( ( moves * burn_ratio ) / 100 ) ); - // Chance to suffer pain if overburden and stamina runs out or has trait BADBACK - // Starts at 1 in 25, goes down by 5 for every 50% more carried - if( ( current_weight > max_weight ) && ( has_trait( trait_BADBACK ) || stamina == 0 ) && - one_in( 35 - 5 * current_weight / ( max_weight / 2 ) ) ) { - add_msg_if_player( m_bad, _( "Your body strains under the weight!" ) ); - // 1 more pain for every 800 grams more (5 per extra STR needed) - if( ( ( current_weight - max_weight ) / 800_gram > get_pain() && get_pain() < 100 ) ) { - mod_pain( 1 ); - } - } -} - Creature::Attitude player::attitude_to( const Creature &other ) const { const auto m = dynamic_cast( &other ); @@ -10349,7 +10259,7 @@ float player::speed_rating() const ret *= 100.0f / run_cost( 100, false ); // Adjustment for player being able to run, but not doing so at the moment if( move_mode != CMM_RUN ) { - ret *= 1.0f + ( static_cast( stamina ) / static_cast( get_stamina_max() ) ); + ret *= 1.0f + ( static_cast( get_stamina() ) / static_cast( get_stamina_max() ) ); } return ret; } diff --git a/src/player.h b/src/player.h index d5d118d98f7ae..18c8d4e2b7934 100644 --- a/src/player.h +++ b/src/player.h @@ -252,8 +252,7 @@ class player : public Character // 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(); - /** Regenerates stamina */ - void update_stamina( int turns ); + /** Kills the player if too hungry, stimmed up etc., forces tired player to sleep and prints warnings. */ void check_needs_extremes(); @@ -1424,8 +1423,6 @@ class player : public Character int get_hp() const override; int get_hp_max( hp_part bp ) const override; int get_hp_max() const override; - int get_stamina_max() const; - void burn_move_stamina( int moves ); //message related stuff using Character::add_msg_if_player; diff --git a/src/player_activity.cpp b/src/player_activity.cpp index b502a77eeec3e..933dd0ee24d6d 100644 --- a/src/player_activity.cpp +++ b/src/player_activity.cpp @@ -118,12 +118,12 @@ void player_activity::do_turn( player &p ) moves_left = 0; } } - int previous_stamina = p.stamina; + int previous_stamina = p.get_stamina(); // This might finish the activity (set it to null) type->call_do_turn( this, &p ); // Activities should never excessively drain stamina. - if( p.stamina < previous_stamina && p.stamina < p.get_stamina_max() / 3 ) { + if( p.get_stamina() < previous_stamina && p.get_stamina() < p.get_stamina_max() / 3 ) { if( one_in( 50 ) ) { p.add_msg_if_player( _( "You pause for a moment to catch your breath." ) ); } diff --git a/src/ranged.cpp b/src/ranged.cpp index 470cfa3d55185..8d60656440e49 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -463,7 +463,7 @@ static int throw_cost( const player &c, const item &to_throw ) const int dexbonus = c.get_dex(); const int encumbrance_penalty = c.encumb( bp_torso ) + ( c.encumb( bp_hand_l ) + c.encumb( bp_hand_r ) ) / 2; - const float stamina_ratio = static_cast( c.stamina ) / c.get_stamina_max(); + const float stamina_ratio = static_cast( c.get_stamina() ) / c.get_stamina_max(); const float stamina_penalty = 1.0 + std::max( ( 0.25f - stamina_ratio ) * 4.0f, 0.0f ); int move_cost = base_move_cost; diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 8de6cfbbed50b..c60ca0f46c53e 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -464,6 +464,7 @@ void Character::load( JsonObject &data ) //energy data.read( "stim", stim ); + data.read( "stamina", stamina ); data.read( "damage_bandaged", damage_bandaged ); data.read( "damage_disinfected", damage_disinfected ); @@ -1058,7 +1059,7 @@ void avatar::load( JsonObject &data ) per_upgrade = -per_upgrade; } - data.read( "stamina", stamina ); + data.read( "magic", magic ); set_highest_cat_level(); drench_mut_calc(); diff --git a/src/sounds.cpp b/src/sounds.cpp index 9dcbc8eb1d3b3..fe045360cf201 100644 --- a/src/sounds.cpp +++ b/src/sounds.cpp @@ -1177,39 +1177,39 @@ void sfx::do_fatigue() /*15: Stamina 75% 16: Stamina 50% 17: Stamina 25%*/ - if( g->u.stamina >= g->u.get_stamina_max() * .75 ) { + if( g->u.get_stamina() >= g->u.get_stamina_max() * .75 ) { fade_audio_group( group::fatigue, 2000 ); return; - } else if( g->u.stamina <= g->u.get_stamina_max() * .74 - && g->u.stamina >= g->u.get_stamina_max() * .5 && + } else if( g->u.get_stamina() <= g->u.get_stamina_max() * .74 + && g->u.get_stamina() >= g->u.get_stamina_max() * .5 && g->u.male && !is_channel_playing( channel::stamina_75 ) ) { fade_audio_group( group::fatigue, 1000 ); play_ambient_variant_sound( "plmove", "fatigue_m_low", 100, channel::stamina_75, 1000 ); return; - } else if( g->u.stamina <= g->u.get_stamina_max() * .49 - && g->u.stamina >= g->u.get_stamina_max() * .25 && + } else if( g->u.get_stamina() <= g->u.get_stamina_max() * .49 + && g->u.get_stamina() >= g->u.get_stamina_max() * .25 && g->u.male && !is_channel_playing( channel::stamina_50 ) ) { fade_audio_group( group::fatigue, 1000 ); play_ambient_variant_sound( "plmove", "fatigue_m_med", 100, channel::stamina_50, 1000 ); return; - } else if( g->u.stamina <= g->u.get_stamina_max() * .24 && g->u.stamina >= 0 && + } else if( g->u.get_stamina() <= g->u.get_stamina_max() * .24 && g->u.get_stamina() >= 0 && g->u.male && !is_channel_playing( channel::stamina_35 ) ) { fade_audio_group( group::fatigue, 1000 ); play_ambient_variant_sound( "plmove", "fatigue_m_high", 100, channel::stamina_35, 1000 ); return; - } else if( g->u.stamina <= g->u.get_stamina_max() * .74 - && g->u.stamina >= g->u.get_stamina_max() * .5 && + } else if( g->u.get_stamina() <= g->u.get_stamina_max() * .74 + && g->u.get_stamina() >= g->u.get_stamina_max() * .5 && !g->u.male && !is_channel_playing( channel::stamina_75 ) ) { fade_audio_group( group::fatigue, 1000 ); play_ambient_variant_sound( "plmove", "fatigue_f_low", 100, channel::stamina_75, 1000 ); return; - } else if( g->u.stamina <= g->u.get_stamina_max() * .49 - && g->u.stamina >= g->u.get_stamina_max() * .25 && + } else if( g->u.get_stamina() <= g->u.get_stamina_max() * .49 + && g->u.get_stamina() >= g->u.get_stamina_max() * .25 && !g->u.male && !is_channel_playing( channel::stamina_50 ) ) { fade_audio_group( group::fatigue, 1000 ); play_ambient_variant_sound( "plmove", "fatigue_f_med", 100, channel::stamina_50, 1000 ); return; - } else if( g->u.stamina <= g->u.get_stamina_max() * .24 && g->u.stamina >= 0 && + } else if( g->u.get_stamina() <= g->u.get_stamina_max() * .24 && g->u.get_stamina() >= 0 && !g->u.male && !is_channel_playing( channel::stamina_35 ) ) { fade_audio_group( group::fatigue, 1000 ); play_ambient_variant_sound( "plmove", "fatigue_f_high", 100, channel::stamina_35, 1000 ); diff --git a/tests/throwing_test.cpp b/tests/throwing_test.cpp index d6bcfd474b6ae..183e18f19ccc4 100644 --- a/tests/throwing_test.cpp +++ b/tests/throwing_test.cpp @@ -56,7 +56,7 @@ static const skill_id skill_throw = skill_id( "throw" ); static void reset_player( player &p, const throw_test_pstats &pstats, const tripoint &pos ) { p.reset(); - p.stamina = p.get_stamina_max(); + p.set_stamina( p.get_stamina_max() ); CHECK( !p.in_vehicle ); p.setpos( pos ); p.str_max = pstats.str; @@ -101,7 +101,7 @@ static void test_throwing_player_versus( do { reset_player( p, pstats, player_start ); p.set_moves( 1000 ); - p.stamina = p.get_stamina_max(); + p.set_stamina( p.get_stamina_max() ); p.wield( it ); monster &mon = spawn_test_monster( mon_id, monster_start );