diff --git a/data/json/bionics.json b/data/json/bionics.json index 5f1fcc4507c65..df6a22cb393c8 100644 --- a/data/json/bionics.json +++ b/data/json/bionics.json @@ -622,9 +622,12 @@ "id": "bio_nanobots", "type": "bionic", "name": "Repair Nanobots", - "description": "Inside your body is a fleet of tiny dormant robots. While activated they will flit about your body, repairing damage at 1 HP/s and stopping bleeding at the cost of power.", + "description": "Inside your body is a fleet of tiny dormant robots. While activated they will flit about your body, repairing damage at 1 HP per minute and stopping bleeding at the cost of extra power and stored calories.", "occupied_bodyparts": [ [ "TORSO", 10 ] ], - "flags": [ "BIONIC_TOGGLED", "BIONIC_NPC_USABLE" ] + "flags": [ "BIONIC_TOGGLED", "BIONIC_NPC_USABLE" ], + "act_cost": "80 J", + "react_cost": "4 J", + "time": 1 }, { "id": "bio_night", diff --git a/data/json/items/melee/swords_and_blades.json b/data/json/items/melee/swords_and_blades.json index 35925bc185edc..e686a778f4da9 100644 --- a/data/json/items/melee/swords_and_blades.json +++ b/data/json/items/melee/swords_and_blades.json @@ -666,6 +666,7 @@ "type": "GENERIC", "symbol": "/", "color": "light_gray", + "looks_like": "arming_sword", "name": "arming sword", "description": "This is a dull, cheaply made replica of a classic medieval sword, just the right size to use one-handed.", "price": 10000, @@ -685,6 +686,7 @@ "type": "TOOL", "symbol": "/", "color": "light_gray", + "looks_like": "arming_sword", "name": "arming sword", "description": "This is a classic medieval sword, just the right size to use one-handed. This one doesn't seem to have been made right.", "price": 100000, diff --git a/data/json/player_activities.json b/data/json/player_activities.json index 43ac8f1e1eed8..4e1193064e54f 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -284,6 +284,13 @@ "rooted": true, "based_on": "time" }, + { + "id": "ACT_HEATING", + "type": "activity_type", + "activity_level": "NO_EXERCISE", + "verb": "heating", + "based_on": "time" + }, { "id": "ACT_VIBE", "type": "activity_type", diff --git a/data/mods/Magiclysm/monsters/monsters.json b/data/mods/Magiclysm/monsters/monsters.json index 624e4f373bbbf..b9779f1648a81 100644 --- a/data/mods/Magiclysm/monsters/monsters.json +++ b/data/mods/Magiclysm/monsters/monsters.json @@ -71,10 +71,10 @@ "flags": [ "HEARS", "GOODHEARING", "NOHEAD", "POISON", "NO_BREATHE", "ACIDPROOF" ] }, { - "id": "mon_nothic", - "name": "nothic", - "description": "A baleful eye peers out from the darkness, its gleam hinting at a weird intelligence and unnerving malevolence.", - "symbol": "N", + "id": "mon_krabgek", + "name": "krabgek", + "description": "A large baleful eye peers out from the darkness, its gleam hinting at a weird intelligence and unnerving malevolence. The eye oozes some pinkish liquid, and the weirdly humanoid figure is covered in sharp blue-black triangular plates.", + "symbol": "K", "color": "dark_gray", "type": "MONSTER", "flags": [ "SEES", "HEARS", "SMELLS", "KEENNOSE", "PATH_AVOID_DANGER_1", "WARM" ], @@ -105,7 +105,7 @@ "cooldown": 1, "damage_max_instance": [ { "damage_type": "cut", "amount": 25, "armor_multiplier": 1.2 } ] }, - { "type": "spell", "spell_id": "necrotic_gaze", "cooldown": 5, "monster_message": "The nothic gazes at %3$s!" } + { "type": "spell", "spell_id": "necrotic_gaze", "cooldown": 5, "monster_message": "The krabgek gazes at %3$s!" } ] }, { diff --git a/data/mods/Magiclysm/worldgen/used_bookstore.json b/data/mods/Magiclysm/worldgen/used_bookstore.json index 898a95674e319..18d592699575e 100644 --- a/data/mods/Magiclysm/worldgen/used_bookstore.json +++ b/data/mods/Magiclysm/worldgen/used_bookstore.json @@ -51,7 +51,7 @@ "d": "f_dumpster", "C": "f_counter" }, - "monster": { "O": { "monster": "mon_nothic", "chance": 100 }, "z": { "monster": "mon_nothic", "chance": 33 } }, + "monster": { "O": { "monster": "mon_krabgek", "chance": 100 }, "z": { "monster": "mon_krabgek", "chance": 33 } }, "toilets": { "&": { } }, "items": { "M": { "item": "spellbook_loot_1", "chance": 60 }, "N": { "item": "novels", "chance": 60, "repeat": [ 1, 3 ] } } } diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 7bb34c35be4e5..1d914276f3cca 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -217,6 +217,7 @@ activity_handlers::finish_functions = { { activity_id( "ACT_CRACKING" ), cracking_finish }, { activity_id( "ACT_OPEN_GATE" ), open_gate_finish }, { activity_id( "ACT_REPAIR_ITEM" ), repair_item_finish }, + { activity_id( "ACT_HEATING" ), heat_item_finish }, { activity_id( "ACT_MEND_ITEM" ), mend_item_finish }, { activity_id( "ACT_GUNMOD_ADD" ), gunmod_add_finish }, { activity_id( "ACT_TOOLMOD_ADD" ), toolmod_add_finish }, @@ -1769,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 ); @@ -2483,6 +2484,35 @@ void activity_handlers::repair_item_finish( player_activity *act, player *p ) act->moves_left = actor->move_cost; } +void activity_handlers::heat_item_finish( player_activity *act, player *p ) +{ + act->set_to_null(); + if( act->targets.size() != 1 ) { + debugmsg( "invalid arguments to ACT_HEATING" ); + return; + } + item_location &loc = act->targets[ 0 ]; + item *heat = loc.get_item(); + if( heat == nullptr ) { + return; + } + item &target = heat->is_food_container() ? heat->contents.front() : *heat; + if( target.item_tags.count( "FROZEN" ) ) { + target.apply_freezerburn(); + if( target.has_flag( "EATEN_COLD" ) ) { + target.cold_up(); + p->add_msg_if_player( m_info, + _( "You defrost the food, but don't heat it up, since you enjoy it cold." ) ); + } else { + target.heat_up(); + p->add_msg_if_player( m_info, _( "You defrost and heat up the food." ) ); + } + } else { + target.heat_up(); + p->add_msg_if_player( m_info, _( "You heat up the food." ) ); + } +} + void activity_handlers::mend_item_finish( player_activity *act, player *p ) { act->set_to_null(); @@ -2854,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; @@ -2949,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 ); } } @@ -2961,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/activity_handlers.h b/src/activity_handlers.h index 34daba668d514..56dca804cd385 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -202,6 +202,7 @@ void mend_item_finish( player_activity *act, player *p ); void gunmod_add_finish( player_activity *act, player *p ); void toolmod_add_finish( player_activity *act, player *p ); void clear_rubble_finish( player_activity *act, player *p ); +void heat_item_finish( player_activity *act, player *p ); void meditate_finish( player_activity *act, player *p ); void read_finish( player_activity *act, player *p ); void wait_finish( player_activity *act, player *p ); 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/bionics.cpp b/src/bionics.cpp index e51d4eb647902..0b6cfd3b03299 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "action.h" #include "avatar.h" @@ -987,15 +988,44 @@ void player::process_bionic( int b ) sounds::sound( pos(), 19, sounds::sound_t::activity, _( "HISISSS!" ), false, "bionic", "bio_hydraulics" ); } else if( bio.id == "bio_nanobots" ) { - for( int i = 0; i < num_hp_parts; i++ ) { - if( get_power_level() >= 5_kJ && hp_cur[i] > 0 && hp_cur[i] < hp_max[i] ) { - heal( static_cast( i ), 1 ); - mod_power_level( -5_kJ ); + if( get_power_level() >= 40_J ) { + std::forward_list bleeding_bp_parts; + for( const body_part bp : all_body_parts ) { + if( has_effect( effect_bleed, bp ) ) { + bleeding_bp_parts.push_front( static_cast( bp ) ); + } } - } - for( const body_part bp : all_body_parts ) { - if( get_power_level() >= 2_kJ && remove_effect( effect_bleed, bp ) ) { - mod_power_level( -2_kJ ); + std::vector damaged_hp_parts; + for( int i = 0; i < num_hp_parts; i++ ) { + if( hp_cur[i] > 0 && hp_cur[i] < hp_max[i] ) { + damaged_hp_parts.push_back( i ); + // only healed and non-hp parts will have a chance of bleeding removal + bleeding_bp_parts.remove( static_cast( hp_to_bp( static_cast( i ) ) ) ); + } + } + if( calendar::once_every( 60_turns ) ) { + bool try_to_heal_bleeding = true; + if( get_stored_kcal() >= 5 && damaged_hp_parts.size() > 0 ) { + const hp_part part_to_heal = static_cast( damaged_hp_parts[ rng( 0, + damaged_hp_parts.size() - 1 ) ] ); + heal( part_to_heal, 1 ); + mod_stored_kcal( -5 ); + const body_part bp_healed = hp_to_bp( part_to_heal ); + int hp_percent = float( hp_cur[part_to_heal] ) / hp_max[part_to_heal] * 100; + if( has_effect( effect_bleed, bp_healed ) && rng( 0, 100 ) < hp_percent ) { + remove_effect( effect_bleed, bp_healed ); + try_to_heal_bleeding = false; + } + } + + // if no bleed was removed, try to remove it on some other part + if( try_to_heal_bleeding && !bleeding_bp_parts.empty() && rng( 0, 1 ) == 1 ) { + remove_effect( effect_bleed, static_cast( bleeding_bp_parts.front() ) ); + } + + } + if( !damaged_hp_parts.empty() || !bleeding_bp_parts.empty() ) { + mod_power_level( -40_J ); } } } else if( bio.id == "bio_painkiller" ) { @@ -1252,7 +1282,8 @@ float player::bionics_adjusted_skill( const skill_id &most_important_skill, return adjusted_skill; } -int player::bionics_pl_skill( const skill_id &most_important_skill, const skill_id &important_skill, +int player::bionics_pl_skill( const skill_id &most_important_skill, + const skill_id &important_skill, const skill_id &least_important_skill, int skill_level ) { int pl_skill; @@ -1634,7 +1665,8 @@ bool player::can_install_bionics( const itype &type, player &installer, bool aut return true; } -bool player::install_bionics( const itype &type, player &installer, bool autodoc, int skill_level ) +bool player::install_bionics( const itype &type, player &installer, bool autodoc, + int skill_level ) { if( !type.bionic ) { debugmsg( "Tried to install NULL bionic" ); @@ -2057,7 +2089,8 @@ void reset_bionics() faulty_bionics.clear(); } -static bool get_bool_or_flag( JsonObject &jsobj, const std::string &name, const std::string &flag, +static bool get_bool_or_flag( JsonObject &jsobj, const std::string &name, + const std::string &flag, const bool fallback, const std::string &flags_node = "flags" ) { bool value = fallback; @@ -2264,7 +2297,7 @@ void bionic::deserialize( JsonIn &jsin ) } void player::introduce_into_anesthesia( const time_duration &duration, player &installer, - bool needs_anesthesia ) //used by the Autodoc + bool needs_anesthesia ) //used by the Autodoc { installer.add_msg_player_or_npc( m_info, _( "You set up the operation step-by-step, configuring the Autodoc to manipulate a CBM." ), 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..b6d05d2e8c5b9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -215,6 +215,7 @@ const efftype_id effect_tetanus( "tetanus" ); const efftype_id effect_visuals( "visuals" ); const efftype_id effect_winded( "winded" ); const efftype_id effect_ridden( "ridden" ); +const efftype_id effect_tied( "tied" ); const efftype_id effect_riding( "riding" ); const efftype_id effect_has_bag( "has_bag" ); const efftype_id effect_harnessed( "harnessed" ); @@ -747,7 +748,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 +9975,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 ); } } @@ -10356,7 +10357,7 @@ void game::vertical_move( int movez, bool force ) if( !m.has_zlevels() ) { const tripoint to = u.pos(); for( monster &critter : all_monsters() ) { - if( critter.has_effect( effect_ridden ) ) { + if( critter.has_effect( effect_ridden ) || critter.has_effect( effect_tied ) ) { continue; } int turns = critter.turns_to_reach( to.xy() ); @@ -10380,14 +10381,16 @@ void game::vertical_move( int movez, bool force ) if( !m.has_zlevels() && abs( movez ) == 1 ) { std::copy_if( active_npc.begin(), active_npc.end(), back_inserter( npcs_to_bring ), [this]( const std::shared_ptr &np ) { - return np->is_walking_with() && !np->is_mounted() && rl_dist( np->pos(), u.pos() ) < 2; + return np->is_walking_with() && !np->is_mounted() && !np->in_sleep_state() && + rl_dist( np->pos(), u.pos() ) < 2; } ); } if( m.has_zlevels() && abs( movez ) == 1 ) { for( monster &critter : all_monsters() ) { if( critter.attack_target() == &g->u || ( !critter.has_effect( effect_ridden ) && - critter.has_effect( effect_pet ) && critter.friendly == -1 ) ) { + critter.has_effect( effect_pet ) && critter.friendly == -1 && + !critter.has_effect( effect_tied ) ) ) { monsters_following.push_back( &critter ); } } diff --git a/src/game_inventory.cpp b/src/game_inventory.cpp index 94dde70ce30a0..e561361e7ad1d 100644 --- a/src/game_inventory.cpp +++ b/src/game_inventory.cpp @@ -485,7 +485,8 @@ class comestible_inventory_preset : public inventory_selector_preset const item &it = get_consumable_item( loc ); int converted_volume_scale = 0; - const double converted_volume = round_up( convert_volume( it.volume().value() / it.charges, + const int charges = std::max( it.charges, 1 ); + const double converted_volume = round_up( convert_volume( it.volume().value() / charges, &converted_volume_scale ), 2 ); //~ Eat menu Volume: 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 f3a79e9ee1925..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: @@ -5487,29 +5487,16 @@ static bool heat_item( player &p ) return false; } item &target = heat->is_food_container() ? heat->contents.front() : *heat; - p.mod_moves( -to_turns( 3_seconds ) ); //initial preparations // simulates heat capacity of food, more weight = longer heating time // this is x2 to simulate larger delta temperature of frozen food in relation to // heating non-frozen food (x1); no real life physics here, only aproximations - int move_mod = to_turns( time_duration::from_seconds( to_gram( target.weight() ) ) ); - if( target.item_tags.count( "FROZEN" ) ) { - target.apply_freezerburn(); - - if( target.has_flag( "EATEN_COLD" ) ) { - target.cold_up(); - p.add_msg_if_player( m_info, - _( "You defrost the food, but don't heat it up, since you enjoy it cold." ) ); - } else { - p.add_msg_if_player( m_info, _( "You defrost and heat up the food." ) ); - target.heat_up(); - // x2 because we have to defrost and heat - move_mod *= 2; - } - } else { - p.add_msg_if_player( m_info, _( "You heat up the food." ) ); - target.heat_up(); + int duration = to_turns( time_duration::from_seconds( to_gram( target.weight() ) ) ) * 10; + if( target.item_tags.count( "FROZEN" ) && !target.has_flag( "EATEN_COLD" ) ) { + duration *= 2; } - p.mod_moves( -move_mod ); // time needed to actually heat up + p.add_msg_if_player( m_info, _( "You start heating up the food." ) ); + p.assign_activity( activity_id( "ACT_HEATING" ), duration ); + p.activity.targets.push_back( item_location( p, &target ) ); return true; } @@ -5757,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 365682faa5bbe..f407e3ad7f4a1 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -16,6 +16,7 @@ #include "field.h" #include "game.h" #include "generic_factory.h" +#include "inventory.h" #include "json.h" #include "map.h" #include "messages.h" @@ -620,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] ) { @@ -811,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 ) { @@ -1190,6 +1191,7 @@ void known_magic::serialize( JsonOut &json ) const json.end_object(); } json.end_array(); + json.member( "invlets", invlets ); json.end_object(); } @@ -1211,6 +1213,7 @@ void known_magic::deserialize( JsonIn &jsin ) spellbook.emplace( sp, spell( sp, xp ) ); } } + data.read( "invlets", invlets ); } bool known_magic::knows_spell( const std::string &sp ) const @@ -1385,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 ) { @@ -1429,17 +1432,37 @@ class spellcasting_callback : public uilist_callback std::vector known_spells; void draw_spell_info( const spell &sp, const uilist *menu ); public: + // invlets reserved for special functions + const std::set reserved_invlets{ 'I', '=' }; bool casting_ignore; spellcasting_callback( std::vector &spells, bool casting_ignore ) : known_spells( spells ), casting_ignore( casting_ignore ) {} - bool key( const input_context &, const input_event &event, int /*entnum*/, + bool key( const input_context &, const input_event &event, int entnum, uilist * /*menu*/ ) override { if( event.get_first_input() == 'I' ) { casting_ignore = !casting_ignore; return true; } + if( event.get_first_input() == '=' ) { + int invlet = 0; + invlet = popup_getkey( _( "Choose a new hotkey for this spell." ) ); + if( inv_chars.valid( invlet ) ) { + const bool invlet_set = g->u.magic.set_invlet( known_spells[entnum]->id(), invlet, + reserved_invlets ); + if( !invlet_set ) { + popup( _( "Hotkey already used." ) ); + } else { + popup( _( "%c set. Close and reopen spell menu to refresh list with changes." ), + invlet ); + } + } else { + popup( _( "Hotkey removed." ) ); + g->u.magic.rem_invlet( known_spells[entnum]->id() ); + } + return true; + } return false; } @@ -1454,6 +1477,9 @@ class spellcasting_callback : public uilist_callback _( "Popup Distractions" ); mvwprintz( menu->window, point( menu->w_width - menu->pad_right + 2, 0 ), casting_ignore ? c_red : c_light_green, string_format( "%s %s", "[I]", ignore_string ) ); + const std::string assign_letter = _( "Assign Hotkey [=]" ); + mvwprintz( menu->window, point( menu->w_width - assign_letter.length() - 1, 0 ), c_yellow, + assign_letter ); draw_spell_info( *known_spells[entnum], menu ); } }; @@ -1513,6 +1539,7 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu const int h_offset = menu->w_width - menu->pad_right + 1; // includes spaces on either side for readability const int info_width = menu->pad_right - 4; + const int win_height = menu->w_height; const int h_col1 = h_offset + 1; const int h_col2 = h_offset + ( info_width / 2 ); const catacurses::window w_menu = menu->window; @@ -1532,8 +1559,9 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu line += fold_and_print( w_menu, point( h_col1, line ), info_width, gray, enumerate_spell_data( sp ) ); - - line++; + if( line <= win_height / 3 ) { + line++; + } print_colored_text( w_menu, point( h_col1, line ), gray, gray, string_format( "%s: %d %s", _( "Spell Level" ), sp.get_level(), @@ -1552,21 +1580,30 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu string_format( "%s: %s", _( "to Next Level" ), colorize( to_string( sp.exp_to_next_level() ), light_green ) ) ); - line++; + if( line <= win_height / 2 ) { + line++; + } const bool cost_encumb = energy_cost_encumbered( sp, g->u ); + std::string cost_string = cost_encumb ? _( "Casting Cost (impeded)" ) : _( "Casting Cost" ); + std::string energy_cur = sp.energy_source() == hp_energy ? "" : string_format( " (%s current)", + sp.energy_cur_string( g->u ) ); + if( !sp.can_cast( g->u ) ) { + cost_string = colorize( _( "Not Enough Energy" ), c_red ); + energy_cur = ""; + } print_colored_text( w_menu, point( h_col1, line++ ), gray, gray, - string_format( "%s: %s %s%s", cost_encumb ? _( "Casting Cost (impeded)" ) : _( "Casting Cost" ), - sp.energy_cost_string( g->u ), sp.energy_string(), - sp.energy_source() == hp_energy ? "" : string_format( " ( %s current )", - sp.energy_cur_string( g->u ) ) ) ); + string_format( "%s: %s %s%s", cost_string, + sp.energy_cost_string( g->u ), sp.energy_string(), energy_cur ) ); const bool c_t_encumb = casting_time_encumbered( sp, g->u ); print_colored_text( w_menu, point( h_col1, line++ ), gray, gray, colorize( string_format( "%s: %s", c_t_encumb ? _( "Casting Time (impeded)" ) : _( "Casting Time" ), moves_to_string( sp.casting_time( g->u ) ) ), c_t_encumb ? c_red : c_light_gray ) ); - line++; + if( line <= win_height * 3 / 4 ) { + line++; + } std::string targets; if( sp.is_valid_target( target_none ) ) { @@ -1577,7 +1614,9 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu print_colored_text( w_menu, point( h_col1, line++ ), gray, gray, string_format( "%s: %s", _( "Valid Targets" ), _( targets ) ) ); - line++; + if( line <= win_height * 3 / 4 ) { + line++; + } const int damage = sp.damage(); std::string damage_string; @@ -1631,6 +1670,20 @@ void spellcasting_callback::draw_spell_info( const spell &sp, const uilist *menu string_format( "%s: %s", _( "Duration" ), sp.duration_string() ) ); } +bool known_magic::set_invlet( const spell_id &sp, int invlet, const std::set &used_invlets ) +{ + if( used_invlets.count( invlet ) > 0 ) { + return false; + } + invlets[sp] = invlet; + return true; +} + +void known_magic::rem_invlet( const spell_id &sp ) +{ + invlets.erase( sp ); +} + int known_magic::get_invlet( const spell_id &sp, std::set &used_invlets ) { auto found = invlets.find( sp ); @@ -1668,8 +1721,8 @@ int known_magic::select_spell( const player &p ) std::vector known_spells = get_spells(); uilist spell_menu; - spell_menu.w_height = 24; - spell_menu.w_width = 80; + spell_menu.w_height = clamp( static_cast( known_spells.size() ), 24, TERMY * 9 / 10 ); + spell_menu.w_width = std::max( 80, TERMX * 3 / 8 ); spell_menu.w_x = ( TERMX - spell_menu.w_width ) / 2; spell_menu.w_y = ( TERMY - spell_menu.w_height ) / 2; spell_menu.pad_right = spell_menu.w_width - max_spell_name_length - 5; @@ -1678,8 +1731,7 @@ int known_magic::select_spell( const player &p ) spellcasting_callback cb( known_spells, casting_ignore ); spell_menu.callback = &cb; - std::set used_invlets; - used_invlets.emplace( 'I' ); + std::set used_invlets{ cb.reserved_invlets }; for( size_t i = 0; i < known_spells.size(); i++ ) { spell_menu.addentry( static_cast( i ), known_spells[i]->can_cast( p ), diff --git a/src/magic.h b/src/magic.h index b7b8d9d6909a9..943a8397679fa 100644 --- a/src/magic.h +++ b/src/magic.h @@ -459,6 +459,10 @@ class known_magic void serialize( JsonOut &json ) const; void deserialize( JsonIn &jsin ); + + // returns false if invlet is already used + bool set_invlet( const spell_id &sp, int invlet, const std::set &used_invlets ); + void rem_invlet( const spell_id &sp ); private: // gets length of longest spell name int get_spellname_max_width(); 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 );