From 852466bc55571f332d25ae58c1d071bde9e5047a Mon Sep 17 00:00:00 2001 From: osuphobia Date: Thu, 28 Nov 2024 00:22:07 +0800 Subject: [PATCH 1/7] Modify `masticated_volume()`. --- src/character.h | 4 ++-- src/consumption.cpp | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/character.h b/src/character.h index 1f44e1f35fc95..ec6080606b147 100644 --- a/src/character.h +++ b/src/character.h @@ -3401,8 +3401,8 @@ class Character : public Creature, public visitable void modify_health( const islot_comestible &comest ); /** Used to compute how filling a food is.*/ double compute_effective_food_volume_ratio( const item &food ) const; - /** Used to calculate dry volume of a chewed food **/ - units::volume masticated_volume( const item &food ) const; + /** Used to calculate water and dry volume of a chewed food **/ + std::pair masticated_volume( const item &food ) const; /** Used to to display how filling a food is. */ int compute_calories_per_effective_volume( const item &food, const nutrients *nutrient = nullptr ) const; diff --git a/src/consumption.cpp b/src/consumption.cpp index e75db3be0547d..4c43b744ff40f 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -1519,10 +1519,10 @@ double Character::compute_effective_food_volume_ratio( const item &food ) const return ratio; } -// Remove the water volume from the food, as that gets absorbed and used as water. +// Seperate the water volume from the food, as that gets absorbed and used as water. // If the remaining dry volume of the food is less dense than water, crunch it down to a density equal to water. // These maths are made easier by the fact that 1 g = 1 mL. Thanks, metric system. -units::volume Character::masticated_volume( const item &food ) const +std::pair Character::masticated_volume( const item &food ) const { units::volume water_vol = ( food.get_comestible()->quench > 0 ) ? food.get_comestible()->quench * 5_ml : 0_ml; @@ -1531,12 +1531,17 @@ units::volume Character::masticated_volume( const item &food ) const units::mass food_dry_weight = food.weight() / std::max( 1, food.count() ) - water_weight; units::volume food_dry_volume = food.volume() / std::max( 1, food.count() ) - water_vol; - if( units::to_milliliter( food_dry_volume ) != 0 && + // Should not return minus volume. + if( units::to_milliliter( food_dry_volume ) <= 0 ) { + return { water_vol, 0_ml }; + } + + if( units::to_gram( food_dry_weight ) > 0 && units::to_gram( food_dry_weight ) < units::to_milliliter( food_dry_volume ) ) { food_dry_volume = units::from_milliliter( units::to_gram( food_dry_weight ) ); } - return food_dry_volume; + return { water_vol, food_dry_volume }; } // Used when displaying effective food satiation values. @@ -1552,7 +1557,7 @@ int Character::compute_calories_per_effective_volume( const item &food, } else { kcalories = compute_effective_nutrients( food ).kcal(); } - double food_vol = round_up( units::to_liter( masticated_volume( food ) ), 2 ); + double food_vol = round_up( units::to_liter( masticated_volume( food ).second ), 2 ); const double energy_density_ratio = compute_effective_food_volume_ratio( food ); const double effective_volume = food_vol * energy_density_ratio; if( kcalories == 0 && effective_volume == 0.0 ) { @@ -1674,10 +1679,8 @@ bool Character::consume_effects( item &food ) } nutrients food_nutrients = compute_effective_nutrients( food ); - const units::volume water_vol = ( food.get_comestible()->quench > 0 ) ? - food.get_comestible()->quench * - 5_ml : 0_ml; - units::volume food_vol = masticated_volume( food ); + const units::volume water_vol = masticated_volume( food ).first; + units::volume food_vol = masticated_volume( food ).second; if( food.count() == 0 ) { debugmsg( "Tried to eat food with count of zero." ); return false; From 1f12753c2f66e36af87a62249af087ba49811aeb Mon Sep 17 00:00:00 2001 From: osuphobia Date: Thu, 28 Nov 2024 19:23:11 +0800 Subject: [PATCH 2/7] Audit `satiety. --- src/consumption.cpp | 20 +++++++++++++++----- src/output.cpp | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/consumption.cpp b/src/consumption.cpp index 4c43b744ff40f..0cc0a47d14611 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -1557,13 +1557,23 @@ int Character::compute_calories_per_effective_volume( const item &food, } else { kcalories = compute_effective_nutrients( food ).kcal(); } - double food_vol = round_up( units::to_liter( masticated_volume( food ).second ), 2 ); - const double energy_density_ratio = compute_effective_food_volume_ratio( food ); - const double effective_volume = food_vol * energy_density_ratio; - if( kcalories == 0 && effective_volume == 0.0 ) { + if( kcalories == 0 ) { + // Quick bail out if it does not cotain any energy. return 0; } - return std::round( kcalories / effective_volume ); + units::volume water_volume = masticated_volume( food ).first; + units::volume dry_volume = masticated_volume( food ).second; + // Water is digested more quickly than solid mass, see get_digest_rates(). + // stomach_ratio is 0.278 for a default character, will be higher for mutants. + const double stomach_ratio = stomach.capacity( *this ) / ( 36.000 * 250_ml ); + water_volume *= stomach_ratio; + const double energy_density_ratio = compute_effective_food_volume_ratio( food ); + dry_volume *= energy_density_ratio; + const int effective_volume = dry_volume.value() + water_volume.value(); + if( effective_volume == 0 ) { + return 2000; + } + return std::round( kcalories * 1000 / effective_volume ); } static void activate_consume_eocs( Character &you, item &target ) diff --git a/src/output.cpp b/src/output.cpp index 8ce4cb3209757..a2d18871e039b 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -2979,7 +2979,7 @@ void insert_table( const catacurses::window &w, int pad, int line, int columns, std::string satiety_bar( const int calpereffv ) { // Arbitrary max value we will cap our vague display to. Will be lower than the actual max value, but scaling fixes that. - constexpr float max_cal_per_effective_vol = 1500.0f; + constexpr float max_cal_per_effective_vol = 2000.0f; // Scaling the values. const float scaled_max = std::sqrt( max_cal_per_effective_vol ); const float scaled_cal = std::sqrt( calpereffv ); From 3a0dfeb524268376266d9c264514ef7e238c9acf Mon Sep 17 00:00:00 2001 From: osuphobia Date: Thu, 28 Nov 2024 19:27:33 +0800 Subject: [PATCH 3/7] Update `effective_food_volume_and_satiety` test. --- tests/comestible_test.cpp | 55 +++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/tests/comestible_test.cpp b/tests/comestible_test.cpp index 8d4b0b4cc1775..6910214aa5f10 100644 --- a/tests/comestible_test.cpp +++ b/tests/comestible_test.cpp @@ -32,6 +32,8 @@ static const itype_id itype_marloss_seed( "marloss_seed" ); static const recipe_id recipe_veggy_wild_cooked( "veggy_wild_cooked" ); +static const trait_id trait_GOURMAND( "GOURMAND" ); + static const vitamin_id vitamin_mutagen( "mutagen" ); static const vitamin_id vitamin_mutagen_alpha( "mutagen_alpha" ); static const vitamin_id vitamin_mutagen_batrachian( "mutagen_batrachian" ); @@ -269,11 +271,13 @@ TEST_CASE( "cooked_veggies_get_correct_calorie_prediction", "[recipe]" ) // // The Character::compute_calories_per_effective_volume function returns a dimensionless integer // representing the "satiety" of the food, with higher numbers being more calorie-dense, and lower -// numbers being less so. +// numbers being less so. Water in food can be digested more quickly, but for a character with a +// bigger stomach, the difference will be smaller. // TEST_CASE( "effective_food_volume_and_satiety", "[character][food][satiety]" ) { - const Character &u = get_player_character(); + Character &u = get_player_character(); + u.unset_all_mutations(); double expect_ratio; // Apple: 95 kcal / 200 g (1 serving) @@ -283,9 +287,10 @@ TEST_CASE( "effective_food_volume_and_satiety", "[character][food][satiety]" ) REQUIRE( apple.weight() == 200_gram ); REQUIRE( apple.volume() == 250_ml ); REQUIRE( apple_nutr.kcal() == 95 ); + REQUIRE( apple.get_comestible()->quench == 3 ); // If kcal per gram < 1.0, return 1.0 CHECK( u.compute_effective_food_volume_ratio( apple ) == Approx( 1.0f ).margin( 0.01f ) ); - CHECK( u.compute_calories_per_effective_volume( apple ) == 500 ); + CHECK( u.compute_calories_per_effective_volume( apple ) == 502 ); CHECK( satiety_bar( 500 ) == "||\\.." ); // Egg: 80 kcal / 40 g (1 serving) @@ -295,10 +300,11 @@ TEST_CASE( "effective_food_volume_and_satiety", "[character][food][satiety]" ) REQUIRE( egg.weight() == 40_gram ); REQUIRE( egg.volume() == 50_ml ); REQUIRE( egg_nutr.kcal() == 80 ); + REQUIRE( egg.get_comestible()->quench == 4 ); // If kcal per gram > 1.0 but less than 3.0, return ( kcal / gram ) CHECK( u.compute_effective_food_volume_ratio( egg ) == Approx( 2.0f ).margin( 0.01f ) ); - CHECK( u.compute_calories_per_effective_volume( egg ) == 2000 ); - CHECK( satiety_bar( 2000 ) == "|||||" ); + CHECK( u.compute_calories_per_effective_volume( egg ) == 1777 ); + CHECK( satiety_bar( 1777 ) == "||||\\" ); // Pine nuts: 202 kcal / 30 g (4 servings) const item nuts( "test_pine_nuts" ); @@ -308,11 +314,17 @@ TEST_CASE( "effective_food_volume_and_satiety", "[character][food][satiety]" ) REQUIRE( nuts.weight() == 120_gram ); REQUIRE( nuts.volume() == 250_ml ); REQUIRE( nuts_nutr.kcal() == 202 ); + REQUIRE( nuts.get_comestible()->quench == -2 ); // If kcal per gram > 3.0, return sqrt( 3 * kcal / gram ) expect_ratio = std::sqrt( 3.0f * 202 / 30 ); CHECK( u.compute_effective_food_volume_ratio( nuts ) == Approx( expect_ratio ).margin( 0.01f ) ); - CHECK( u.compute_calories_per_effective_volume( nuts ) == 1498 ); - CHECK( satiety_bar( 1498 ) == "||||\\" ); + CHECK( u.compute_calories_per_effective_volume( nuts ) == 1507 ); + CHECK( satiety_bar( 1507 ) == "||||." ); + + REQUIRE( u.mutate_towards( trait_GOURMAND ) ); + CHECK( u.compute_effective_food_volume_ratio( egg ) == Approx( 2.0f ).margin( 0.01f ) ); + CHECK( u.compute_calories_per_effective_volume( egg ) == 1568 ); + CHECK( satiety_bar( 1568 ) == "||||." ); } // satiety_bar returns a colorized string indicating a satiety level, similar to hit point bars @@ -331,17 +343,22 @@ TEST_CASE( "food_satiety_bar", "[character][food][satiety]" ) // NOLINTNEXTLINE(cata-text-style): verbatim ellipses necessary for validation CHECK( satiety_bar( 200 ) == "|\\..." ); // NOLINTNEXTLINE(cata-text-style): verbatim ellipses necessary for validation - CHECK( satiety_bar( 300 ) == "||..." ); - CHECK( satiety_bar( 400 ) == "||\\.." ); + CHECK( satiety_bar( 300 ) == "|\\..." ); + CHECK( satiety_bar( 400 ) == "||..." ); CHECK( satiety_bar( 500 ) == "||\\.." ); - CHECK( satiety_bar( 600 ) == "|||.." ); - CHECK( satiety_bar( 700 ) == "|||.." ); - CHECK( satiety_bar( 800 ) == "|||\\." ); - CHECK( satiety_bar( 900 ) == "|||\\." ); - CHECK( satiety_bar( 1000 ) == "||||." ); - CHECK( satiety_bar( 1100 ) == "||||." ); - CHECK( satiety_bar( 1200 ) == "||||." ); - CHECK( satiety_bar( 1300 ) == "||||\\" ); - CHECK( satiety_bar( 1400 ) == "||||\\" ); - CHECK( satiety_bar( 1500 ) == "|||||" ); + CHECK( satiety_bar( 600 ) == "||\\.." ); + CHECK( satiety_bar( 700 ) == "||\\.." ); + CHECK( satiety_bar( 800 ) == "|||.." ); + CHECK( satiety_bar( 900 ) == "|||.." ); + CHECK( satiety_bar( 1000 ) == "|||\\." ); + CHECK( satiety_bar( 1100 ) == "|||\\." ); + CHECK( satiety_bar( 1200 ) == "|||\\." ); + CHECK( satiety_bar( 1300 ) == "||||." ); + CHECK( satiety_bar( 1400 ) == "||||." ); + CHECK( satiety_bar( 1500 ) == "||||." ); + CHECK( satiety_bar( 1600 ) == "||||." ); + CHECK( satiety_bar( 1700 ) == "||||\\" ); + CHECK( satiety_bar( 1800 ) == "||||\\" ); + CHECK( satiety_bar( 1900 ) == "||||\\" ); + CHECK( satiety_bar( 2000 ) == "|||||" ); } From 01b5fc492737fd2879e9200d3079d46e2df01df2 Mon Sep 17 00:00:00 2001 From: osuphobia <78858975+osuphobia@users.noreply.github.com> Date: Thu, 28 Nov 2024 19:58:26 +0800 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/consumption.cpp | 2 +- tests/comestible_test.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/consumption.cpp b/src/consumption.cpp index 0cc0a47d14611..445fcdcc8764b 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -1562,7 +1562,7 @@ int Character::compute_calories_per_effective_volume( const item &food, return 0; } units::volume water_volume = masticated_volume( food ).first; - units::volume dry_volume = masticated_volume( food ).second; + units::volume dry_volume = masticated_volume( food ).second; // Water is digested more quickly than solid mass, see get_digest_rates(). // stomach_ratio is 0.278 for a default character, will be higher for mutants. const double stomach_ratio = stomach.capacity( *this ) / ( 36.000 * 250_ml ); diff --git a/tests/comestible_test.cpp b/tests/comestible_test.cpp index 6910214aa5f10..d28b60c2073cc 100644 --- a/tests/comestible_test.cpp +++ b/tests/comestible_test.cpp @@ -320,7 +320,6 @@ TEST_CASE( "effective_food_volume_and_satiety", "[character][food][satiety]" ) CHECK( u.compute_effective_food_volume_ratio( nuts ) == Approx( expect_ratio ).margin( 0.01f ) ); CHECK( u.compute_calories_per_effective_volume( nuts ) == 1507 ); CHECK( satiety_bar( 1507 ) == "||||." ); - REQUIRE( u.mutate_towards( trait_GOURMAND ) ); CHECK( u.compute_effective_food_volume_ratio( egg ) == Approx( 2.0f ).margin( 0.01f ) ); CHECK( u.compute_calories_per_effective_volume( egg ) == 1568 ); From e0e19edfcc829f21ac571343b8f01ef4e5b6aeb7 Mon Sep 17 00:00:00 2001 From: osuphobia Date: Thu, 28 Nov 2024 20:16:06 +0800 Subject: [PATCH 5/7] Fix an oversight, add comments to test case. --- tests/comestible_test.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/comestible_test.cpp b/tests/comestible_test.cpp index d28b60c2073cc..969dcb4aaa6bc 100644 --- a/tests/comestible_test.cpp +++ b/tests/comestible_test.cpp @@ -291,7 +291,7 @@ TEST_CASE( "effective_food_volume_and_satiety", "[character][food][satiety]" ) // If kcal per gram < 1.0, return 1.0 CHECK( u.compute_effective_food_volume_ratio( apple ) == Approx( 1.0f ).margin( 0.01f ) ); CHECK( u.compute_calories_per_effective_volume( apple ) == 502 ); - CHECK( satiety_bar( 500 ) == "||\\.." ); + CHECK( satiety_bar( 502 ) == "||\\.." ); // Egg: 80 kcal / 40 g (1 serving) const item egg( "test_egg" ); @@ -321,9 +321,14 @@ TEST_CASE( "effective_food_volume_and_satiety", "[character][food][satiety]" ) CHECK( u.compute_calories_per_effective_volume( nuts ) == 1507 ); CHECK( satiety_bar( 1507 ) == "||||." ); REQUIRE( u.mutate_towards( trait_GOURMAND ) ); + // stomach size affects food with water... CHECK( u.compute_effective_food_volume_ratio( egg ) == Approx( 2.0f ).margin( 0.01f ) ); CHECK( u.compute_calories_per_effective_volume( egg ) == 1568 ); CHECK( satiety_bar( 1568 ) == "||||." ); + // but not food without water. + CHECK( u.compute_effective_food_volume_ratio( nuts ) == Approx( expect_ratio ).margin( 0.01f ) ); + CHECK( u.compute_calories_per_effective_volume( nuts ) == 1507 ); + CHECK( satiety_bar( 1507 ) == "||||." ); } // satiety_bar returns a colorized string indicating a satiety level, similar to hit point bars From c10d2733a14c87553df5f8f8fc24150469d74c22 Mon Sep 17 00:00:00 2001 From: osuphobia Date: Thu, 28 Nov 2024 22:31:20 +0800 Subject: [PATCH 6/7] satisfy clang --- tests/comestible_test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/comestible_test.cpp b/tests/comestible_test.cpp index 969dcb4aaa6bc..af3df5cebb99d 100644 --- a/tests/comestible_test.cpp +++ b/tests/comestible_test.cpp @@ -348,6 +348,7 @@ TEST_CASE( "food_satiety_bar", "[character][food][satiety]" ) CHECK( satiety_bar( 200 ) == "|\\..." ); // NOLINTNEXTLINE(cata-text-style): verbatim ellipses necessary for validation CHECK( satiety_bar( 300 ) == "|\\..." ); + // NOLINTNEXTLINE(cata-text-style): verbatim ellipses necessary for validation CHECK( satiety_bar( 400 ) == "||..." ); CHECK( satiety_bar( 500 ) == "||\\.." ); CHECK( satiety_bar( 600 ) == "||\\.." ); From 9310afd607c2cb47aab0982ce99fb366dd4ab466 Mon Sep 17 00:00:00 2001 From: Anton Burmistrov Date: Fri, 29 Nov 2024 07:04:05 +0400 Subject: [PATCH 7/7] Update src/consumption.cpp --- src/consumption.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/consumption.cpp b/src/consumption.cpp index 445fcdcc8764b..3bd4d698c16e7 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -1519,7 +1519,7 @@ double Character::compute_effective_food_volume_ratio( const item &food ) const return ratio; } -// Seperate the water volume from the food, as that gets absorbed and used as water. +// Separate the water volume from the food, as that gets absorbed and used as water. // If the remaining dry volume of the food is less dense than water, crunch it down to a density equal to water. // These maths are made easier by the fact that 1 g = 1 mL. Thanks, metric system. std::pair Character::masticated_volume( const item &food ) const