Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Create cardio #50339

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1689652
Create cardio
KorGgenT Jul 30, 2021
45fd624
Add cap to cardio
I-am-Erk Aug 17, 2021
9761db7
Apply suggestions from code review
I-am-Erk Sep 8, 2021
9fc09bc
Merge remote-tracking branch 'upstream/master' into pr/50339
I-am-Erk Sep 8, 2021
ee3fa54
Update game_balance.json
I-am-Erk Sep 8, 2021
d6dfe29
astyle and shut off some NPC functions
I-am-Erk Sep 8, 2021
b569fc4
Merge branch 'cardio-stuff' of https://github.com/KorGgenT/Cataclysm-…
I-am-Erk Sep 8, 2021
aa4aae0
variable name consistency
I-am-Erk Sep 8, 2021
5faa42f
Apply suggestions from code review
I-am-Erk Sep 9, 2021
0b05641
Apply suggestions from code review
I-am-Erk Sep 9, 2021
6435ec9
Update src/character.cpp
I-am-Erk Sep 9, 2021
1730a0f
Update src/character.cpp
I-am-Erk Sep 9, 2021
3394a5d
Merge remote-tracking branch 'upstream/master' into pr/50339
I-am-Erk Sep 17, 2021
1bd40b9
Merge branch 'cardio-stuff' of https://github.com/KorGgenT/Cataclysm-…
I-am-Erk Sep 17, 2021
e951cfe
don't load cardio_acc until game is initialized
I-am-Erk Sep 17, 2021
52ee3b2
rename global variables
I-am-Erk Sep 17, 2021
6ee0725
adjust stamina balance a little, fix? bug in regen
I-am-Erk Sep 17, 2021
4980121
redo how we calculate stamina regen
I-am-Erk Sep 17, 2021
95f36e9
missed a spot
I-am-Erk Sep 17, 2021
6c11ee9
Apply suggestions from code review
I-am-Erk Sep 17, 2021
5d3ae6a
astyle
I-am-Erk Sep 17, 2021
ce61cd5
Update data/core/game_balance.json
I-am-Erk Sep 18, 2021
6a25317
Update src/avatar.cpp
I-am-Erk Sep 18, 2021
9563840
per wapcaplet recommendation
I-am-Erk Sep 18, 2021
4eec05f
Merge branch 'cardio-stuff' of https://github.com/KorGgenT/Cataclysm-…
I-am-Erk Sep 18, 2021
287623c
fix an error with stamina calculations in archery
I-am-Erk Sep 18, 2021
0bd5a9d
use a flat stamina calculation for muscle powered weapons
I-am-Erk Sep 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions data/core/game_balance.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@
},
{
"type": "EXTERNAL_OPTION",
"name": "PLAYER_MAX_STAMINA",
"info": "Sets max stamina value of the player.",
"name": "PLAYER_MAX_STAMINA_BASE",
"info": "Sets the base max stamina value of the player, before cardio modifiers.",
"stype": "int",
"value": 10000
"value": 3500
},
{
"type": "EXTERNAL_OPTION",
"name": "PLAYER_CARDIOFIT_STAMINA_SCALING",
"info": "Sets the effect of cardio on maximum stamina.",
"stype": "int",
"value": 3
},
{
"type": "EXTERNAL_OPTION",
"name": "PLAYER_BASE_STAMINA_REGEN_RATE",
"info": "Sets base stamina regeneration per turn of the player. May be used as an offset in stamina draining effects.",
"info": "Sets base stamina regeneration per turn of the player, before cardio modifiers. May be used as an offset in stamina draining effects.",
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
"stype": "float",
"value": 20
},
Expand Down
6 changes: 3 additions & 3 deletions data/json/mutations/mutations.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@
"id": "GOODCARDIO",
"name": { "str": "Indefatigable" },
"points": 2,
"description": "Whether due to exercise and good diet, or due to a natural abundance of physical endurance, you tire due to physical exertion much less readily than others. Your maximum stamina is 25% higher than usual.",
"description": "Whether due to exercise and good diet, or due to a natural propensity to physical endurance, you tire due to physical exertion much less readily than others. Your maximum stamina is higher than usual.",
"starting_trait": true,
"valid": false,
"cancels": [ "BADCARDIO" ],
Expand All @@ -227,7 +227,7 @@
"id": "GOODCARDIO2",
"name": { "str": "Hyperactive" },
"points": 4,
"description": "Your body's efficiency is like that of a tiny furnace, increasing your maximum stamina by 40%.",
"description": "Your body's efficiency is like that of a tiny furnace, greatly increasing your maximum stamina",
"valid": false,
"prereqs": [ "GOODCARDIO" ],
"cancels": [ "BADCARDIO" ],
Expand Down Expand Up @@ -872,7 +872,7 @@
"id": "BADCARDIO",
"name": { "str": "Languorous" },
"points": -2,
"description": "Whether due to lack of exercise and poor diet, or due to a natural lack of physical endurance, you tire from physical exertion much more readily than others. Your maximum stamina is 25% lower than usual.",
"description": "Whether due to lack of exercise and poor diet, or due to a natural disinclination to physical endurance, you tire due to physical exertion much more readily than others. Your maximum stamina is lower than usual.",
"starting_trait": true,
"valid": false,
"cancels": [ "GOODCARDIO" ],
Expand Down
2 changes: 1 addition & 1 deletion src/activity_actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ bool aim_activity_actor::load_RAS_weapon()

// Burn 0.2% max base stamina x the strength required to fire.
you.mod_stamina( gun->get_min_str() * static_cast<int>( 0.002f *
get_option<int>( "PLAYER_MAX_STAMINA" ) ) );
you.get_stamina_max() ) );
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
// At low stamina levels, firing starts getting slow.
int sta_percent = ( 100 * you.get_stamina() ) / you.get_stamina_max();
reload_time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0;
Expand Down
13 changes: 13 additions & 0 deletions src/avatar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,19 @@ bool avatar::invoke_item( item *used, const std::string &method )
return Character::invoke_item( used, method );
}

void avatar::update_cardio_acc()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function ever called? I don't see any invocations; this looks like dead code.

Copy link
Member

@I-am-Erk I-am-Erk Sep 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Afaict that's an error if so, I wonder if it got missed during one of the conflict merges

As I recall the first implementation wasn't going to include the accumulator so I wonder if it's not fully done. Seems like it would be easy to fix

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now the comment from gkarfakis19 on the previous unfinished PR (repeated several times):

I do NOT plan on adding the exercise (cardio_acc) aspect of Cardio in this PR. It will come later.

It looks like it was implemented as requested, just not hooked up to anything. As noted in my other comment on this function, I think it would make sense to invoke it from Character::update_body just before the advance_daily_calories() call; that way it ought to include the previous 24 hours of activity from the diary.

{
const int prev_cardio_acc = get_cardio_acc();
const int last_24h_kcal = calorie_diary.begin()->spent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const int last_24h_kcal = calorie_diary.begin()->spent;
const int last_24h_kcal = calorie_diary.front().spent;

Since you don't need an iterator here, front() would be more appropriate I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if I'm not mistaken, the front of this array is only the spent calories since midnight - not the previous 24 hours.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so:

    if( is_avatar() && ticks_between( from, to, 24_hours ) > 0 ) {
        as_avatar()->advance_daily_calories();
    }

in character_body.cpp and:

void avatar::advance_daily_calories()
{
    calorie_diary.push_front( daily_calories{} );
    if( calorie_diary.size() > 30 ) {
        calorie_diary.pop_back();
    }
}

in avatar.cpp.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I mean - every 24 hours, the front of this list gets a new (empty) calorie log. In order for this update_cardio_acc to work properly, I think it would need to be invoked from Character::update_body just before advance_daily_calories(), to capture the previous day's cardio level.

int adjustment = 0;
if( prev_cardio_acc > last_24h_kcal ) {
adjustment = std::sqrt( prev_cardio_acc - last_24h_kcal );
} else if( last_24h_kcal > prev_cardio_acc ) {
adjustment = -std::sqrt( last_24h_kcal - prev_cardio_acc );
}
set_cardio_acc( prev_cardio_acc + adjustment );
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
}

void avatar::advance_daily_calories()
{
calorie_diary.push_front( daily_calories{} );
Expand Down
1 change: 1 addition & 0 deletions src/avatar.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ class avatar : public Character
// called once a day; adds a new daily_calories to the
// front of the list and pops off the back if there are more than 30
void advance_daily_calories();
void update_cardio_acc() override;
void add_spent_calories( int cal ) override;
void add_gained_calories( int cal ) override;
void log_activity_level( float level ) override;
Expand Down
74 changes: 64 additions & 10 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ Character::Character() :
}
// Only call these if game is initialized
if( !!g && json_flag::is_ready() ) {
set_cardio_acc( base_bmr() / 2 );
Copy link
Contributor

@wapcaplet wapcaplet Sep 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on my comment:

I believe this large random value may be because cardio_acc is uninitialized in the Character constructor.

Here is where the cardio_acc initialization should occur - but it is gated by json_flag::is_ready(), which for whatever reason is false when the Character constructor is called. I believe the reason for stipulating this condition is that base_bmr() needs metabolic_rate_base(), which reads from game_balance.json the setting for PLAYER_HUNGER_RATE (default 1.0) - so it needs this is_ready() flag to be true in order to read that balance setting.

If we can find a way to initialize cardio_acc to a reasonable value without depending on base_bmr(), that should resolve the problem with new characters having wildly random cardio values. Or, find a way to ensure json_flag::is_ready() is true before we reach this constructor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will figure out a solution for this in the new PR.

recalc_sight_limits();
calc_encumbrance();
}
Expand Down Expand Up @@ -3607,15 +3608,16 @@ std::string Character::debug_weary_info() const

int bmr = base_bmr();
std::string weary_internals = activity_history.debug_weary_info();
int cardio_mult = get_cardiofit();
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
int thresh = weary_threshold();
int current = weariness_level();
int morale = get_morale_level();
int weight = units::to_gram<int>( bodyweight() );
float bmi = get_bmi();

return string_format( "Weariness: %s Max Full Exert: %s Mult: %g\nBMR: %d %s Thresh: %d At: %d\nCal: %d/%d Fatigue: %d Morale: %d Wgt: %d (BMI %.1f)",
amt, max_act, move_mult, bmr, weary_internals, thresh, current, get_stored_kcal(),
get_healthy_kcal(), fatigue, morale, weight, bmi );
return string_format( "Weariness: %s Max Full Exert: %s Mult: %g\n BMR: %d CARDIO FITNESS: %d\n %s Thresh: %d At: %d\n Kcal: %d/%d Fatigue: %d Morale: %d Wgt: %d (BMI %.1f)",
amt, max_act, move_mult, bmr, cardio_mult, weary_internals, thresh, current,
get_stored_kcal(), get_healthy_kcal(), fatigue, morale, weight, bmi );
}

void Character::mod_stored_kcal( int nkcal, const bool ignore_weariness )
Expand Down Expand Up @@ -5740,17 +5742,31 @@ void Character::mod_rad( int mod )

int Character::get_stamina() const
{
if( is_npc() ) {
// No point in doing a bunch of checks on NPCs for now since they can't use stamina.
return get_stamina_max();
}
return stamina;
}

int Character::get_stamina_max() const
{
static const std::string player_max_stamina( "PLAYER_MAX_STAMINA" );
if( is_npc() ) {
// No point in doing a bunch of checks on NPCs for now since they can't use stamina.
return 10000;
}
// Since adding cardio, 'player_max_stamina' is really 'base max stamina' and gets further modified
// by your CV fitness. Name has been kept this way to avoid needing to change the code.
// Default base maximum stamina and cardio scaling are defined in data/core/game_balance.json
static const std::string player_max_stamina( "PLAYER_MAX_STAMINA_BASE" );
static const std::string player_cardiofit_stamina_scale( "PLAYER_CARDIOFIT_STAMINA_SCALING" );
static const std::string max_stamina_modifier( "max_stamina_modifier" );
int maxStamina = get_option< int >( player_max_stamina );
maxStamina *= Character::mutation_value( max_stamina_modifier );
maxStamina = enchantment_cache->modify_value( enchant_vals::mod::MAX_STAMINA, maxStamina );
return maxStamina;
// Cardiofit stamina mod defaults to 3, and get_cardiofit() should return a value in the vicinity
// of 1000-4000, so this should add somewhere between 3000 to 12000 stamina.
int max_stamina = get_option<int>( player_max_stamina ) +
get_option<int>( player_cardiofit_stamina_scale ) * get_cardiofit();
max_stamina = enchantment_cache->modify_value( enchant_vals::mod::MAX_STAMINA, max_stamina );
return max_stamina;
}

void Character::set_stamina( int new_stamina )
Expand Down Expand Up @@ -5808,6 +5824,9 @@ void Character::update_stamina( int turns )
static const std::string player_base_stamina_regen_rate( "PLAYER_BASE_STAMINA_REGEN_RATE" );
static const std::string stamina_regen_modifier( "stamina_regen_modifier" );
const float base_regen_rate = get_option<float>( player_base_stamina_regen_rate );
// Your stamina regen rate works as a function of how fit you are compared to your body size. This allows it to scale more quickly
// than your stamina, so that at higher fitness levels you recover stamina faster.
const float effective_regen_rate = base_regen_rate * get_cardiofit() / base_bmr();
const int current_stim = get_stim();
float stamina_recovery = 0.0f;
// Recover some stamina every turn.
Expand All @@ -5817,7 +5836,7 @@ void Character::update_stamina( int turns )
mutation_value( stamina_regen_modifier ) + ( mutation_value( "max_stamina_modifier" ) - 1.0f ) );
// But mouth encumbrance interferes, even with mutated stamina.
stamina_recovery += stamina_multiplier * std::max( 1.0f,
base_regen_rate * stamina_recovery_breathing_modifier() );
effective_regen_rate * stamina_recovery_breathing_modifier() );
stamina_recovery = enchantment_cache->modify_value( enchant_vals::mod::REGEN_STAMINA,
stamina_recovery );
// TODO: recovering stamina causes hunger/thirst/fatigue.
Expand All @@ -5841,7 +5860,7 @@ void Character::update_stamina( int turns )
int bonus = std::min<int>( 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<int>( base_regen_rate ) );
bonus = std::min( bonus, 4 * static_cast<int>( effective_regen_rate ) );
if( bonus > 0 ) {
stamina_recovery += bonus;
bonus /= 10;
Expand All @@ -5857,6 +5876,41 @@ void Character::update_stamina( int turns )
set_stamina( std::min( std::max( get_stamina(), 0 ), max_stam ) );
}

int Character::get_cardiofit() const
{
if( is_npc() ) {
// No point in doing a bunch of checks on NPCs for now since they can't use cardio.
return 2 * base_bmr();
}
const int bmr = base_bmr();
const int athletics_mod = get_skill_level( skill_swimming ) * 10;
const int health_effect = get_healthy();
// Traits now exclusively affect cardio, NOT max_stamina directly. In the future, make cardio_acc also be affected by cardio traits so that they don't become less impactful.
const int trait_mod = mutation_value( "max_stamina_modifier" );
// At some point we might have proficiencies that affect this.
const int prof_mod = 0;
const int cardio_acc_mod = get_cardio_acc();
int final_cardio_fitness = ( bmr / 2 + athletics_mod + health_effect + trait_mod + prof_mod +
cardio_acc_mod );
if( final_cardio_fitness > 3 * ( bmr + trait_mod ) ) {
// Set a large sane upper limit to cardio fitness. This could be done asymptotically instead of as a sharp cutoff, but the gradual
// growth rate of cardio_acc_mod should accomplish that naturally. The BMR will mostly determine this as it is based on the
// size of the character, but mutations might push it up.
final_cardio_fitness = 3 * ( bmr + trait_mod );
}
return final_cardio_fitness;
}

int Character::get_cardio_acc() const
{
return cardio_acc;
}

void Character::set_cardio_acc( int ncardio_acc )
{
cardio_acc = ncardio_acc;
}

bool Character::invoke_item( item *used )
{
return invoke_item( used, pos() );
Expand Down
8 changes: 8 additions & 0 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -2469,6 +2469,12 @@ class Character : public Creature, public visitable
/** Regenerates stamina */
void update_stamina( int turns );

int get_cardiofit() const;

int get_cardio_acc() const;
void set_cardio_acc( int ncardio_acc );
virtual void update_cardio_acc() = 0;

/** Returns true if a gun misfires, jams, or has other problems, else returns false */
bool handle_gun_damage( item &it );

Expand Down Expand Up @@ -3271,6 +3277,8 @@ class Character : public Creature, public visitable
int thirst;
int stamina;

int cardio_acc;

int fatigue;
int sleep_deprivation;
bool check_encumbrance = true;
Expand Down
2 changes: 1 addition & 1 deletion src/iuse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9532,7 +9532,7 @@ cata::optional<int> iuse::wash_items( Character *p, bool soft_items, bool hard_i
cata::optional<int> iuse::break_stick( Character *p, item *it, bool, const tripoint & )
{
p->moves -= to_moves<int>( 2_seconds );
p->mod_stamina( static_cast<int>( 0.05f * get_option<int>( "PLAYER_MAX_STAMINA" ) ) );
p->mod_stamina( static_cast<int>( 0.05f * p->get_stamina_max() ) );

if( p->get_str() < 5 ) {
p->add_msg_if_player( _( "You are too weak to even try." ) );
Expand Down
2 changes: 2 additions & 0 deletions src/npc.h
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,8 @@ class npc : public Character

bool dispose_item( item_location &&obj, const std::string &prompt = std::string() ) override;

void update_cardio_acc() override {};

void aim();
void do_reload( const item &it );

Expand Down
7 changes: 5 additions & 2 deletions src/vehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4598,8 +4598,11 @@ void vehicle::consume_fuel( int load, bool idling )
// But only if the player is actually there!
int eff_load = load / 10;
int mod = 4 * st; // strain
int base_burn = static_cast<int>( get_option<float>( "PLAYER_BASE_STAMINA_REGEN_RATE" ) ) -
3;
const int base_staminaRegen = static_cast<int>
( get_option<float>( "PLAYER_BASE_STAMINA_REGEN_RATE" ) );
const int actual_staminaRegen = static_cast<int>( base_staminaRegen *
player_character.get_cardiofit() / player_character.base_bmr() );
int base_burn = actual_staminaRegen - 3;
base_burn = std::max( eff_load / 3, base_burn );
//charge bionics when using muscle engine
const item muscle( "muscle" );
Expand Down