From 1d980f1281bf49cf86e36efe77ffd35d3f9e3806 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Fri, 20 Mar 2020 18:00:46 -0600 Subject: [PATCH 01/15] First draft with 1 passing test (24 assertions) --- tests/char_stamina_test.cpp | 131 ++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tests/char_stamina_test.cpp diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp new file mode 100644 index 0000000000000..d38a0bdffbdb8 --- /dev/null +++ b/tests/char_stamina_test.cpp @@ -0,0 +1,131 @@ +#include "avatar.h" +#include "character.h" +#include "game.h" + +#include "catch/catch.hpp" +#include "player_helpers.h" + + +// options: +// PLAYER_MAX_STAMINA (10000) +// PLAYER_BASE_STAMINA_REGEN_RATE (20) +// PLAYER_BASE_STAMINA_BURN_RATE (15) + +// Functions in character.cpp to cover: +// +// get_stamina_max +// - Start with PLAYER_MAX_STAMINA +// - Multiply by Character::mutation_value( "max_stamina_modifier" ) +// +// mod_stamina +// - Modifies stamina by positive or negative amount +// - Adds effect_winded for 10 turns if stamina < 0 +// +// stamina_move_cost_modifier +// - Both walk and run speed drop to half their maximums as stamina approaches 0 +// - Modifier is 2x for running +// - Modifier is 1/2x for crouching +// +// burn_move_stamina (MODIFIES stamina) +// - Scaled by overburden percentage +// - Modified by bionic muscles +// - Running scales by 7x +// - Modifies stamina based on stamina_move_cost_modifier +// - Applies pain if overburdened with no stamina or BADBACK trait +// +// update_stamina (REFRESHES stamina status) +// - Considers PLAYER_BASE_STAMINA_REGEN_RATE +// - Multiplies by mutation_value( "stamina_regen_modifier" ) +// (from comments): +// - Mouth encumbrance interferes, even with mutated stamina +// - Stim recovers stamina +// - "Affect(sic) 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, effectively countering stamina gain of default 20 (???) +// - at 50% stamina it is -10 (50%), cuts by 25% at 25% stamina (???) +// - If "bio_gills" and enough power, "the effective recovery is up to 5x default" (???) +// !!! calls mod_stamina with roll_remainder (RNG) for final result, then caps it at max +// +// +// effect_winded +// +TEST_CASE( "stamina movement cost", "[stamina][move_cost]" ) +{ + player &dummy = g->u; + clear_character( dummy ); + + GIVEN( "character is walking" ) { + dummy.set_movement_mode( CMM_WALK ); + REQUIRE( dummy.movement_mode_is( CMM_WALK ) ); + + THEN( "100%% movement speed at full stamina" ) { + dummy.set_stamina( dummy.get_stamina_max() ); + REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() ); + + CHECK( dummy.stamina_move_cost_modifier() == 1.0 ); + } + + WHEN( "75%% movement speed at half stamina" ) { + dummy.set_stamina( dummy.get_stamina_max() / 2 ); + REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() / 2 ); + + CHECK( dummy.stamina_move_cost_modifier() == 0.75 ); + } + + WHEN( "50%% movement speed at zero stamina" ) { + dummy.set_stamina( 0 ); + REQUIRE( dummy.get_stamina() == 0 ); + + CHECK( dummy.stamina_move_cost_modifier() == 0.5 ); + } + } + + WHEN( "character is running" ) { + // Set max stamina to ensure they can run + dummy.set_stamina( dummy.get_stamina_max() ); + dummy.set_movement_mode( CMM_RUN ); + REQUIRE( dummy.movement_mode_is( CMM_RUN ) ); + + THEN( "200%% movement speed at full stamina" ) { + dummy.set_stamina( dummy.get_stamina_max() ); + REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() ); + + CHECK( dummy.stamina_move_cost_modifier() == 2.0 ); + } + + THEN( "150%% movement speed at half stamina" ) { + dummy.set_stamina( dummy.get_stamina_max() / 2 ); + REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() / 2 ); + + CHECK( dummy.stamina_move_cost_modifier() == 1.5 ); + } + + THEN( "50%% movement speed at zero stamina" ) { + dummy.set_stamina( 0 ); + REQUIRE( dummy.get_stamina() == 0 ); + + CHECK( dummy.stamina_move_cost_modifier() == 0.50 ); + } + } + + WHEN( "character is crouching" ) { + dummy.set_movement_mode( CMM_CROUCH ); + REQUIRE( dummy.movement_mode_is( CMM_CROUCH ) ); + + THEN( "50%% movement speed at full stamina" ) { + dummy.set_stamina( dummy.get_stamina_max() ); + REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() ); + + CHECK( dummy.stamina_move_cost_modifier() == 0.5 ); + } + + THEN( "25%% movement speed at zero stamina" ) { + dummy.set_stamina( 0 ); + REQUIRE( dummy.get_stamina() == 0 ); + + CHECK( dummy.stamina_move_cost_modifier() == 0.25 ); + } + } +} + From 5f98cba1febb736d06eb976edfcf54a54010e9af Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Fri, 20 Mar 2020 18:50:56 -0600 Subject: [PATCH 02/15] Refactor into helper function, broader coverage Unexpected failure for running speed at zero stamina; speed drops to 0.5 instead of the expected 1.0 --- tests/char_stamina_test.cpp | 112 +++++++++++++----------------------- 1 file changed, 39 insertions(+), 73 deletions(-) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index d38a0bdffbdb8..26f92fbaf37ab 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -5,7 +5,6 @@ #include "catch/catch.hpp" #include "player_helpers.h" - // options: // PLAYER_MAX_STAMINA (10000) // PLAYER_BASE_STAMINA_REGEN_RATE (20) @@ -21,11 +20,6 @@ // - Modifies stamina by positive or negative amount // - Adds effect_winded for 10 turns if stamina < 0 // -// stamina_move_cost_modifier -// - Both walk and run speed drop to half their maximums as stamina approaches 0 -// - Modifier is 2x for running -// - Modifier is 1/2x for crouching -// // burn_move_stamina (MODIFIES stamina) // - Scaled by overburden percentage // - Modified by bionic muscles @@ -46,86 +40,58 @@ // - at 50% stamina it is -10 (50%), cuts by 25% at 25% stamina (???) // - If "bio_gills" and enough power, "the effective recovery is up to 5x default" (???) // !!! calls mod_stamina with roll_remainder (RNG) for final result, then caps it at max -// -// -// effect_winded -// -TEST_CASE( "stamina movement cost", "[stamina][move_cost]" ) -{ - player &dummy = g->u; - clear_character( dummy ); - GIVEN( "character is walking" ) { - dummy.set_movement_mode( CMM_WALK ); - REQUIRE( dummy.movement_mode_is( CMM_WALK ) ); - THEN( "100%% movement speed at full stamina" ) { - dummy.set_stamina( dummy.get_stamina_max() ); - REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() ); +// Return `stamina_move_cost_modifier` in the given move_mode with [0,1] stamina remaining +float speed_w_stamina( player &dummy, character_movemode move_mode, float stamina_proportion = 1.0 ) +{ + clear_character( dummy ); + dummy.set_movement_mode( move_mode ); + REQUIRE( dummy.movement_mode_is( move_mode ) ); - CHECK( dummy.stamina_move_cost_modifier() == 1.0 ); - } + int new_stamina = static_cast( stamina_proportion * dummy.get_stamina_max() ); - WHEN( "75%% movement speed at half stamina" ) { - dummy.set_stamina( dummy.get_stamina_max() / 2 ); - REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() / 2 ); + dummy.set_stamina( new_stamina ); + REQUIRE( dummy.get_stamina() == new_stamina ); - CHECK( dummy.stamina_move_cost_modifier() == 0.75 ); - } + return dummy.stamina_move_cost_modifier(); +} - WHEN( "50%% movement speed at zero stamina" ) { - dummy.set_stamina( 0 ); - REQUIRE( dummy.get_stamina() == 0 ); +TEST_CASE( "stamina movement cost", "[stamina][move_cost]" ) +{ + player &dummy = g->u; - CHECK( dummy.stamina_move_cost_modifier() == 0.5 ); - } + SECTION( "running is twice as fast as walking" ) { + CHECK( speed_w_stamina( dummy, CMM_RUN ) == 2 * speed_w_stamina( dummy, CMM_WALK ) ); } - WHEN( "character is running" ) { - // Set max stamina to ensure they can run - dummy.set_stamina( dummy.get_stamina_max() ); - dummy.set_movement_mode( CMM_RUN ); - REQUIRE( dummy.movement_mode_is( CMM_RUN ) ); - - THEN( "200%% movement speed at full stamina" ) { - dummy.set_stamina( dummy.get_stamina_max() ); - REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() ); - - CHECK( dummy.stamina_move_cost_modifier() == 2.0 ); - } - - THEN( "150%% movement speed at half stamina" ) { - dummy.set_stamina( dummy.get_stamina_max() / 2 ); - REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() / 2 ); - - CHECK( dummy.stamina_move_cost_modifier() == 1.5 ); - } - - THEN( "50%% movement speed at zero stamina" ) { - dummy.set_stamina( 0 ); - REQUIRE( dummy.get_stamina() == 0 ); - - CHECK( dummy.stamina_move_cost_modifier() == 0.50 ); - } + SECTION( "walking is twice as fast as crouching" ) { + CHECK( speed_w_stamina( dummy, CMM_WALK ) == 2 * speed_w_stamina( dummy, CMM_CROUCH ) ); } - WHEN( "character is crouching" ) { - dummy.set_movement_mode( CMM_CROUCH ); - REQUIRE( dummy.movement_mode_is( CMM_CROUCH ) ); - - THEN( "50%% movement speed at full stamina" ) { - dummy.set_stamina( dummy.get_stamina_max() ); - REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() ); - - CHECK( dummy.stamina_move_cost_modifier() == 0.5 ); - } + SECTION( "running speed diminishes with stamina" ) { + CHECK( speed_w_stamina( dummy, CMM_RUN, 1.00 ) == 2.00 ); + CHECK( speed_w_stamina( dummy, CMM_RUN, 0.75 ) == 1.75 ); + CHECK( speed_w_stamina( dummy, CMM_RUN, 0.50 ) == 1.50 ); + CHECK( speed_w_stamina( dummy, CMM_RUN, 0.25 ) == 1.25 ); + //CHECK( speed_w_stamina( dummy, CMM_RUN, 0.00 ) == 1.00 ); // Expected + CHECK( speed_w_stamina( dummy, CMM_RUN, 0.00 ) == 0.50 ); // Actual + } - THEN( "25%% movement speed at zero stamina" ) { - dummy.set_stamina( 0 ); - REQUIRE( dummy.get_stamina() == 0 ); + SECTION( "walking speed diminishes with stamina" ) { + CHECK( speed_w_stamina( dummy, CMM_WALK, 1.00 ) == 1.000 ); + CHECK( speed_w_stamina( dummy, CMM_WALK, 0.75 ) == 0.875 ); + CHECK( speed_w_stamina( dummy, CMM_WALK, 0.50 ) == 0.750 ); + CHECK( speed_w_stamina( dummy, CMM_WALK, 0.25 ) == 0.625 ); + CHECK( speed_w_stamina( dummy, CMM_WALK, 0.00 ) == 0.500 ); + } - CHECK( dummy.stamina_move_cost_modifier() == 0.25 ); - } + SECTION( "crouching speed diminishes with stamina" ) { + CHECK( speed_w_stamina( dummy, CMM_CROUCH, 1.00 ) == 0.5000 ); + CHECK( speed_w_stamina( dummy, CMM_CROUCH, 0.75 ) == 0.4375 ); + CHECK( speed_w_stamina( dummy, CMM_CROUCH, 0.50 ) == 0.3750 ); + CHECK( speed_w_stamina( dummy, CMM_CROUCH, 0.25 ) == 0.3125 ); + CHECK( speed_w_stamina( dummy, CMM_CROUCH, 0.00 ) == 0.2500 ); } } From 3c556d92602dec901423b3e69b8dddffbfe82f1b Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Fri, 20 Mar 2020 19:54:23 -0600 Subject: [PATCH 03/15] Clean up stamina test; adjust run condition by 1 Using `get_stamina() > 0` in the `src/character.cpp` condition for running causes a discontinuity in return value, where the running cost modifier drops sharply from: stamina 0.001 => cost modifier 1.001 stamina 0.000 => cost modifier 0.500 Making this condition `>= 0` eliminates the discontinuity: stamina 0.001 => cost modifier 1.001 stamina 0.000 => cost modifier 1.000 --- src/character.cpp | 2 +- tests/char_stamina_test.cpp | 105 ++++++++++++++++++++---------------- tests/player_helpers.cpp | 2 + 3 files changed, 62 insertions(+), 47 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index b8877c12b4c9c..3d6d8f1184666 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -6659,7 +6659,7 @@ float Character::stamina_move_cost_modifier() 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( get_stamina() ) / get_stamina_max() + 1 ) / 2; - if( move_mode == CMM_RUN && get_stamina() > 0 ) { + if( move_mode == CMM_RUN && get_stamina() >= 0 ) { // Rationale: Average running speed is 2x walking speed. (NOT sprinting) stamina_modifier *= 2.0; } diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index 26f92fbaf37ab..03b6fdc942a3d 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -19,33 +19,14 @@ // mod_stamina // - Modifies stamina by positive or negative amount // - Adds effect_winded for 10 turns if stamina < 0 -// -// burn_move_stamina (MODIFIES stamina) -// - Scaled by overburden percentage -// - Modified by bionic muscles -// - Running scales by 7x -// - Modifies stamina based on stamina_move_cost_modifier -// - Applies pain if overburdened with no stamina or BADBACK trait -// -// update_stamina (REFRESHES stamina status) -// - Considers PLAYER_BASE_STAMINA_REGEN_RATE -// - Multiplies by mutation_value( "stamina_regen_modifier" ) -// (from comments): -// - Mouth encumbrance interferes, even with mutated stamina -// - Stim recovers stamina -// - "Affect(sic) 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, effectively countering stamina gain of default 20 (???) -// - at 50% stamina it is -10 (50%), cuts by 25% at 25% stamina (???) -// - If "bio_gills" and enough power, "the effective recovery is up to 5x default" (???) -// !!! calls mod_stamina with roll_remainder (RNG) for final result, then caps it at max + // Return `stamina_move_cost_modifier` in the given move_mode with [0,1] stamina remaining -float speed_w_stamina( player &dummy, character_movemode move_mode, float stamina_proportion = 1.0 ) +float move_cost_mod( player &dummy, character_movemode move_mode, float stamina_proportion = 1.0 ) { clear_character( dummy ); + dummy.set_movement_mode( move_mode ); REQUIRE( dummy.movement_mode_is( move_mode ) ); @@ -57,41 +38,73 @@ float speed_w_stamina( player &dummy, character_movemode move_mode, float stamin return dummy.stamina_move_cost_modifier(); } -TEST_CASE( "stamina movement cost", "[stamina][move_cost]" ) +TEST_CASE( "stamina movement cost modifier", "[stamina][cost]" ) { player &dummy = g->u; - SECTION( "running is twice as fast as walking" ) { - CHECK( speed_w_stamina( dummy, CMM_RUN ) == 2 * speed_w_stamina( dummy, CMM_WALK ) ); + SECTION( "running cost is double walking cost for the same stamina level" ) { + CHECK( move_cost_mod( dummy, CMM_RUN, 1.0 ) == 2 * move_cost_mod( dummy, CMM_WALK, 1.0 ) ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.5 ) == 2 * move_cost_mod( dummy, CMM_WALK, 0.5 ) ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.0 ) == 2 * move_cost_mod( dummy, CMM_WALK, 0.0 ) ); } - SECTION( "walking is twice as fast as crouching" ) { - CHECK( speed_w_stamina( dummy, CMM_WALK ) == 2 * speed_w_stamina( dummy, CMM_CROUCH ) ); + SECTION( "walking cost is double crouching cost for the same stamina level" ) { + CHECK( move_cost_mod( dummy, CMM_WALK, 1.0 ) == 2 * move_cost_mod( dummy, CMM_CROUCH, 1.0 ) ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.5 ) == 2 * move_cost_mod( dummy, CMM_CROUCH, 0.5 ) ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.0 ) == 2 * move_cost_mod( dummy, CMM_CROUCH, 0.0 ) ); } - SECTION( "running speed diminishes with stamina" ) { - CHECK( speed_w_stamina( dummy, CMM_RUN, 1.00 ) == 2.00 ); - CHECK( speed_w_stamina( dummy, CMM_RUN, 0.75 ) == 1.75 ); - CHECK( speed_w_stamina( dummy, CMM_RUN, 0.50 ) == 1.50 ); - CHECK( speed_w_stamina( dummy, CMM_RUN, 0.25 ) == 1.25 ); - //CHECK( speed_w_stamina( dummy, CMM_RUN, 0.00 ) == 1.00 ); // Expected - CHECK( speed_w_stamina( dummy, CMM_RUN, 0.00 ) == 0.50 ); // Actual + SECTION( "running cost goes from 2.0 to 1.0 as stamina goes to zero" ) { + CHECK( move_cost_mod( dummy, CMM_RUN, 1.00 ) == 2.00 ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.75 ) == 1.75 ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.50 ) == 1.50 ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.25 ) == 1.25 ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.00 ) == 1.00 ); } - SECTION( "walking speed diminishes with stamina" ) { - CHECK( speed_w_stamina( dummy, CMM_WALK, 1.00 ) == 1.000 ); - CHECK( speed_w_stamina( dummy, CMM_WALK, 0.75 ) == 0.875 ); - CHECK( speed_w_stamina( dummy, CMM_WALK, 0.50 ) == 0.750 ); - CHECK( speed_w_stamina( dummy, CMM_WALK, 0.25 ) == 0.625 ); - CHECK( speed_w_stamina( dummy, CMM_WALK, 0.00 ) == 0.500 ); + SECTION( "walking cost goes from 1.0 to 0.5 as stamina goes to zero" ) { + CHECK( move_cost_mod( dummy, CMM_WALK, 1.00 ) == 1.000 ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.75 ) == 0.875 ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.50 ) == 0.750 ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.25 ) == 0.625 ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.00 ) == 0.500 ); } - SECTION( "crouching speed diminishes with stamina" ) { - CHECK( speed_w_stamina( dummy, CMM_CROUCH, 1.00 ) == 0.5000 ); - CHECK( speed_w_stamina( dummy, CMM_CROUCH, 0.75 ) == 0.4375 ); - CHECK( speed_w_stamina( dummy, CMM_CROUCH, 0.50 ) == 0.3750 ); - CHECK( speed_w_stamina( dummy, CMM_CROUCH, 0.25 ) == 0.3125 ); - CHECK( speed_w_stamina( dummy, CMM_CROUCH, 0.00 ) == 0.2500 ); + SECTION( "crouching cost goes from 0.5 to 0.25 as stamina goes to zero" ) { + CHECK( move_cost_mod( dummy, CMM_CROUCH, 1.00 ) == 0.5000 ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.75 ) == 0.4375 ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.50 ) == 0.3750 ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.25 ) == 0.3125 ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.00 ) == 0.2500 ); } } +// burn_move_stamina (MODIFIES stamina) +// - Scaled by overburden percentage +// - Modified by bionic muscles +// - Running scales by 7x +// - Modifies stamina based on stamina_move_cost_modifier +// - Applies pain if overburdened with no stamina or BADBACK trait +// +TEST_CASE( "burning stamina", "[stamina][burn]" ) +{ +} + + +// update_stamina (REFRESHES stamina status) +// - Considers PLAYER_BASE_STAMINA_REGEN_RATE +// - Multiplies by mutation_value( "stamina_regen_modifier" ) +// (from comments): +// - Mouth encumbrance interferes, even with mutated stamina +// - Stim recovers stamina +// - "Affect(sic) 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, effectively countering stamina gain of default 20 (???) +// - at 50% stamina it is -10 (50%), cuts by 25% at 25% stamina (???) +// - If "bio_gills" and enough power, "the effective recovery is up to 5x default" (???) +// !!! calls mod_stamina with roll_remainder (RNG) for final result, then caps it at max +// +TEST_CASE( "update stamina", "[stamina][update]" ) +{ +} diff --git a/tests/player_helpers.cpp b/tests/player_helpers.cpp index f32fbf2453ea4..282a469b2d440 100644 --- a/tests/player_helpers.cpp +++ b/tests/player_helpers.cpp @@ -66,6 +66,8 @@ void clear_character( player &dummy ) dummy.activity.set_to_null(); + dummy.set_stamina( dummy.get_stamina_max() ); + // Make stats nominal. dummy.str_cur = 8; dummy.dex_cur = 8; From 721087e6a9003673ea7d7b6a117e66b36de0b2bb Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Fri, 20 Mar 2020 20:39:04 -0600 Subject: [PATCH 04/15] Stamina burning tests --- tests/char_stamina_test.cpp | 54 +++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index 03b6fdc942a3d..c83dffbb3cc97 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -1,6 +1,7 @@ #include "avatar.h" #include "character.h" #include "game.h" +#include "options.h" #include "catch/catch.hpp" #include "player_helpers.h" @@ -88,6 +89,59 @@ TEST_CASE( "stamina movement cost modifier", "[stamina][cost]" ) // TEST_CASE( "burning stamina", "[stamina][burn]" ) { + player &dummy = g->u; + clear_character( dummy ); + + // Game-balance configured rate of stamina burned per move + int burn_rate = get_option( "PLAYER_BASE_STAMINA_BURN_RATE" ); + int before_stam = 0; + int after_stam = 0; + + GIVEN( "player is not overburdened" ) { + REQUIRE( dummy.weight_carried() < dummy.weight_capacity() ); + + WHEN( "walking" ) { + dummy.set_movement_mode( CMM_WALK ); + REQUIRE( dummy.movement_mode_is( CMM_WALK ) ); + + before_stam = dummy.get_stamina(); + REQUIRE( before_stam == dummy.get_stamina_max() ); + + THEN( "stamina cost is the normal rate per turn" ) { + dummy.burn_move_stamina( to_moves( 10_turns ) ); + after_stam = dummy.get_stamina(); + CHECK( after_stam == before_stam - 10 * burn_rate ); + } + } + + WHEN( "crouching" ) { + dummy.set_movement_mode( CMM_CROUCH ); + REQUIRE( dummy.movement_mode_is( CMM_CROUCH ) ); + + before_stam = dummy.get_stamina(); + REQUIRE( before_stam == dummy.get_stamina_max() ); + + THEN( "stamina cost is 1/2 the normal rate per turn" ) { + dummy.burn_move_stamina( to_moves( 10_turns ) ); + after_stam = dummy.get_stamina(); + CHECK( after_stam == before_stam - 5 * burn_rate ); + } + } + + WHEN( "running" ) { + dummy.set_movement_mode( CMM_RUN ); + REQUIRE( dummy.movement_mode_is( CMM_RUN ) ); + + before_stam = dummy.get_stamina(); + REQUIRE( before_stam == dummy.get_stamina_max() ); + + THEN( "stamina cost is 14 times the normal rate per turn" ) { + dummy.burn_move_stamina( to_moves( 10_turns ) ); + after_stam = dummy.get_stamina(); + CHECK( after_stam == before_stam - 140 * burn_rate ); + } + } + } } From 5a3bed5cc65f5353b6744d2d6fae1a977fc60b56 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 21 Mar 2020 07:28:26 -0600 Subject: [PATCH 05/15] clear_character goes to walk mode --- tests/player_helpers.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/player_helpers.cpp b/tests/player_helpers.cpp index 282a469b2d440..cb5ee9b71cd92 100644 --- a/tests/player_helpers.cpp +++ b/tests/player_helpers.cpp @@ -6,6 +6,7 @@ #include "avatar.h" #include "bionics.h" +#include "character.h" #include "game.h" #include "item.h" #include "itype.h" @@ -66,7 +67,9 @@ void clear_character( player &dummy ) dummy.activity.set_to_null(); + // Restore all stamina and go to walk mode dummy.set_stamina( dummy.get_stamina_max() ); + dummy.set_movement_mode( CMM_WALK ); // Make stats nominal. dummy.str_cur = 8; From 328e0f08b87884a21b94c04a2237f6113129a54f Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 21 Mar 2020 07:28:43 -0600 Subject: [PATCH 06/15] Add mod_stamina unit tests --- tests/char_stamina_test.cpp | 82 ++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index c83dffbb3cc97..926c5cb663787 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -16,10 +16,6 @@ // get_stamina_max // - Start with PLAYER_MAX_STAMINA // - Multiply by Character::mutation_value( "max_stamina_modifier" ) -// -// mod_stamina -// - Modifies stamina by positive or negative amount -// - Adds effect_winded for 10 turns if stamina < 0 @@ -80,6 +76,80 @@ TEST_CASE( "stamina movement cost modifier", "[stamina][cost]" ) } } +TEST_CASE( "modify character stamina", "[stamina][modify]" ) +{ + player &dummy = g->u; + clear_character( dummy ); + REQUIRE_FALSE( dummy.is_npc() ); + REQUIRE_FALSE( dummy.has_effect( efftype_id( "winded" ) ) ); + + GIVEN( "character has less than full stamina" ) { + int lost_stamina = dummy.get_stamina_max() / 2; + dummy.set_stamina( dummy.get_stamina_max() - lost_stamina ); + REQUIRE( dummy.get_stamina() + lost_stamina == dummy.get_stamina_max() ); + + WHEN( "they regain only part of their lost stamina" ) { + dummy.mod_stamina( lost_stamina / 2 ); + + THEN( "stamina is less than maximum" ) { + CHECK( dummy.get_stamina() < dummy.get_stamina_max() ); + } + } + + WHEN( "they regain all of their lost stamina" ) { + dummy.mod_stamina( lost_stamina ); + + THEN( "stamina is at maximum" ) { + CHECK( dummy.get_stamina() == dummy.get_stamina_max() ); + } + } + + WHEN( "they regain more stamina than they lost" ) { + dummy.mod_stamina( lost_stamina + 1 ); + + THEN( "stamina is at maximum" ) { + CHECK( dummy.get_stamina() == dummy.get_stamina_max() ); + } + } + + WHEN( "they lose only part of their remaining stamina" ) { + dummy.mod_stamina( -( dummy.get_stamina() / 2 ) ); + + THEN( "stamina is above zero" ) { + CHECK( dummy.get_stamina() > 0 ); + + AND_THEN( "they do not become winded" ) { + REQUIRE_FALSE( dummy.has_effect( efftype_id( "winded" ) ) ); + } + } + } + + WHEN( "they lose all of their remaining stamina" ) { + dummy.mod_stamina( -( dummy.get_stamina() ) ); + + THEN( "stamina is at zero" ) { + CHECK( dummy.get_stamina() == 0 ); + + AND_THEN( "they do not become winded" ) { + REQUIRE_FALSE( dummy.has_effect( efftype_id( "winded" ) ) ); + } + } + } + + WHEN( "they lose more stamina than they have remaining" ) { + dummy.mod_stamina( -( dummy.get_stamina() + 1 ) ); + + THEN( "stamina is at zero" ) { + CHECK( dummy.get_stamina() == 0 ); + + AND_THEN( "they become winded" ) { + REQUIRE( dummy.has_effect( efftype_id( "winded" ) ) ); + } + } + } + } +} + // burn_move_stamina (MODIFIES stamina) // - Scaled by overburden percentage // - Modified by bionic muscles @@ -87,10 +157,9 @@ TEST_CASE( "stamina movement cost modifier", "[stamina][cost]" ) // - Modifies stamina based on stamina_move_cost_modifier // - Applies pain if overburdened with no stamina or BADBACK trait // -TEST_CASE( "burning stamina", "[stamina][burn]" ) +TEST_CASE( "burn stamina for movement", "[stamina][burn][move]" ) { player &dummy = g->u; - clear_character( dummy ); // Game-balance configured rate of stamina burned per move int burn_rate = get_option( "PLAYER_BASE_STAMINA_BURN_RATE" ); @@ -98,6 +167,7 @@ TEST_CASE( "burning stamina", "[stamina][burn]" ) int after_stam = 0; GIVEN( "player is not overburdened" ) { + clear_character( dummy ); REQUIRE( dummy.weight_carried() < dummy.weight_capacity() ); WHEN( "walking" ) { From fcfe8323b338b3ff9bc206a3b278324bc2447b15 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 21 Mar 2020 15:02:19 -0600 Subject: [PATCH 07/15] Use flag for DEBUG_STORAGE Since the DEBUG_STORAGE mutation gives the character many billions of metric tonnes of weight capacity, it was making it difficult to test being overburdened for stamina (no backpack is big enough, nor any material in the game dense enough, to make such a character overburdened). So now it's a parameter to the helper function, defaulting to true. --- tests/player_helpers.cpp | 8 +++----- tests/player_helpers.h | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/player_helpers.cpp b/tests/player_helpers.cpp index cb5ee9b71cd92..8c04373692761 100644 --- a/tests/player_helpers.cpp +++ b/tests/player_helpers.cpp @@ -44,7 +44,7 @@ bool player_has_item_of_type( const std::string &type ) return !matching_items.empty(); } -void clear_character( player &dummy ) +void clear_character( player &dummy, bool debug_storage ) { // Remove first worn item until there are none left. std::list temp; @@ -54,17 +54,15 @@ void clear_character( player &dummy ) for( const trait_id &tr : dummy.get_mutations() ) { dummy.unset_mutation( tr ); } + // Prevent spilling, but don't cause encumbrance - if( !dummy.has_trait( trait_id( "DEBUG_STORAGE" ) ) ) { + if( debug_storage && !dummy.has_trait( trait_id( "DEBUG_STORAGE" ) ) ) { dummy.set_mutation( trait_id( "DEBUG_STORAGE" ) ); } dummy.empty_skills(); - dummy.clear_morale(); - dummy.clear_bionics(); - dummy.activity.set_to_null(); // Restore all stamina and go to walk mode diff --git a/tests/player_helpers.h b/tests/player_helpers.h index b930943ec9440..b15c431ef2c6f 100644 --- a/tests/player_helpers.h +++ b/tests/player_helpers.h @@ -11,7 +11,7 @@ struct point; int get_remaining_charges( const std::string &tool_id ); bool player_has_item_of_type( const std::string & ); -void clear_character( player & ); +void clear_character( player &, bool debug_storage = true ); void clear_avatar(); void process_activity( player &dummy ); From cb50567928a219cd99cbacd30554e73feba24f60 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 21 Mar 2020 15:04:00 -0600 Subject: [PATCH 08/15] Hammer stamina burn test case into good shape --- tests/char_stamina_test.cpp | 116 +++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 41 deletions(-) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index 926c5cb663787..f480e6f914293 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -35,6 +35,41 @@ float move_cost_mod( player &dummy, character_movemode move_mode, float stamina_ return dummy.stamina_move_cost_modifier(); } +// Return amount of stamina burned per turn by `burn_move_stamina` in the given movement mode. +int actual_burn_rate( player &dummy, character_movemode move_mode ) +{ + // Set starting stamina to max to ensure enough left for 10 turns + dummy.set_stamina( dummy.get_stamina_max() ); + + dummy.set_movement_mode( move_mode ); + REQUIRE( dummy.movement_mode_is( move_mode ) ); + + int before_stam = dummy.get_stamina(); + dummy.burn_move_stamina( to_moves( 1_turns ) ); + int after_stam = dummy.get_stamina(); + REQUIRE( before_stam > after_stam ); + + return before_stam - after_stam; +} + +// Return amount of stamina burned per turn by `burn_move_stamina` in the given movement mode, +// while carrying the given proportion [0.0, inf) of maximum weight capacity. +int burdened_burn_rate( player &dummy, character_movemode move_mode, float burden_proportion = 0.0 ) +{ + clear_character( dummy, false ); + units::mass capacity = dummy.weight_capacity(); + + // Add gold (5g/unit) to reach the desired weight capacity + if( burden_proportion > 0.0 ) { + int gold_units = static_cast( capacity * burden_proportion / 5_gram ); + dummy.i_add( item( "gold_small", calendar::turn, gold_units ) ); + REQUIRE( dummy.weight_carried() == capacity * burden_proportion ); + } + + return actual_burn_rate( dummy, move_mode ); +} + + TEST_CASE( "stamina movement cost modifier", "[stamina][cost]" ) { player &dummy = g->u; @@ -151,65 +186,64 @@ TEST_CASE( "modify character stamina", "[stamina][modify]" ) } // burn_move_stamina (MODIFIES stamina) -// - Scaled by overburden percentage // - Modified by bionic muscles -// - Running scales by 7x -// - Modifies stamina based on stamina_move_cost_modifier // - Applies pain if overburdened with no stamina or BADBACK trait -// -TEST_CASE( "burn stamina for movement", "[stamina][burn][move]" ) + +TEST_CASE( "stamina burn for movement", "[stamina][burn][move]" ) { player &dummy = g->u; // Game-balance configured rate of stamina burned per move int burn_rate = get_option( "PLAYER_BASE_STAMINA_BURN_RATE" ); - int before_stam = 0; - int after_stam = 0; - GIVEN( "player is not overburdened" ) { - clear_character( dummy ); - REQUIRE( dummy.weight_carried() < dummy.weight_capacity() ); - - WHEN( "walking" ) { - dummy.set_movement_mode( CMM_WALK ); - REQUIRE( dummy.movement_mode_is( CMM_WALK ) ); + GIVEN( "player is naked and unburdened" ) { + THEN( "walking burns the normal amount of stamina per turn" ) { + CHECK( burdened_burn_rate( dummy, CMM_WALK, 0.0 ) == burn_rate ); + } - before_stam = dummy.get_stamina(); - REQUIRE( before_stam == dummy.get_stamina_max() ); + THEN( "running burns 14x the normal amount of stamina per turn" ) { + CHECK( burdened_burn_rate( dummy, CMM_RUN, 0.0 ) == burn_rate * 14 ); + } - THEN( "stamina cost is the normal rate per turn" ) { - dummy.burn_move_stamina( to_moves( 10_turns ) ); - after_stam = dummy.get_stamina(); - CHECK( after_stam == before_stam - 10 * burn_rate ); - } + THEN( "crouching burns 1/2 the normal amount of stamina per turn" ) { + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 0.0 ) == burn_rate / 2 ); } + } - WHEN( "crouching" ) { - dummy.set_movement_mode( CMM_CROUCH ); - REQUIRE( dummy.movement_mode_is( CMM_CROUCH ) ); + GIVEN( "player is at their maximum weight capacity" ) { + THEN( "walking burns the normal amount of stamina per turn" ) { + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.0 ) == burn_rate ); + } - before_stam = dummy.get_stamina(); - REQUIRE( before_stam == dummy.get_stamina_max() ); + THEN( "running burns 14x the normal amount of stamina per turn" ) { + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.0 ) == burn_rate * 14 ); + } - THEN( "stamina cost is 1/2 the normal rate per turn" ) { - dummy.burn_move_stamina( to_moves( 10_turns ) ); - after_stam = dummy.get_stamina(); - CHECK( after_stam == before_stam - 5 * burn_rate ); - } + THEN( "crouching burns 1/2 the normal amount of stamina per turn" ) { + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.0 ) == burn_rate / 2 ); } + } - WHEN( "running" ) { - dummy.set_movement_mode( CMM_RUN ); - REQUIRE( dummy.movement_mode_is( CMM_RUN ) ); + GIVEN( "player is overburdened" ) { + THEN( "walking burn rate increases by 1 for each percent overburdened" ) { + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.01 ) == burn_rate + 1 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.02 ) == burn_rate + 2 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.50 ) == burn_rate + 50 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 2.00 ) == burn_rate + 100 ); + } - before_stam = dummy.get_stamina(); - REQUIRE( before_stam == dummy.get_stamina_max() ); + THEN( "running burn rate increases by 14 for each percent overburdened" ) { + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.01 ) == ( burn_rate + 1 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.02 ) == ( burn_rate + 2 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.50 ) == ( burn_rate + 50 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 2.00 ) == ( burn_rate + 100 ) * 14 ); + } - THEN( "stamina cost is 14 times the normal rate per turn" ) { - dummy.burn_move_stamina( to_moves( 10_turns ) ); - after_stam = dummy.get_stamina(); - CHECK( after_stam == before_stam - 140 * burn_rate ); - } + THEN( "crouching burn rate increases by 1/2 for each percent overburdened" ) { + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.01 ) == ( burn_rate + 1 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.02 ) == ( burn_rate + 2 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.50 ) == ( burn_rate + 50 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 2.00 ) == ( burn_rate + 100 ) / 2 ); } } } From 5b39d19e423db8d7aab73b0f17601607286eae4e Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 21 Mar 2020 17:37:11 -0600 Subject: [PATCH 09/15] Tests for pain when burning stamina Also force-remove "winded" status in helper functions to ensure `can_run()` succeeds. --- tests/char_stamina_test.cpp | 90 +++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index f480e6f914293..4b87bbc7dfba0 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -11,18 +11,16 @@ // PLAYER_BASE_STAMINA_REGEN_RATE (20) // PLAYER_BASE_STAMINA_BURN_RATE (15) -// Functions in character.cpp to cover: -// -// get_stamina_max -// - Start with PLAYER_MAX_STAMINA -// - Multiply by Character::mutation_value( "max_stamina_modifier" ) - - // Return `stamina_move_cost_modifier` in the given move_mode with [0,1] stamina remaining float move_cost_mod( player &dummy, character_movemode move_mode, float stamina_proportion = 1.0 ) { clear_character( dummy ); + dummy.remove_effect( efftype_id( "winded" ) ); + + if( move_mode == CMM_RUN ) { + REQUIRE( dummy.can_run() ); + } dummy.set_movement_mode( move_mode ); REQUIRE( dummy.movement_mode_is( move_mode ) ); @@ -40,6 +38,11 @@ int actual_burn_rate( player &dummy, character_movemode move_mode ) { // Set starting stamina to max to ensure enough left for 10 turns dummy.set_stamina( dummy.get_stamina_max() ); + dummy.remove_effect( efftype_id( "winded" ) ); + + if( move_mode == CMM_RUN ) { + REQUIRE( dummy.can_run() ); + } dummy.set_movement_mode( move_mode ); REQUIRE( dummy.movement_mode_is( move_mode ) ); @@ -52,9 +55,8 @@ int actual_burn_rate( player &dummy, character_movemode move_mode ) return before_stam - after_stam; } -// Return amount of stamina burned per turn by `burn_move_stamina` in the given movement mode, -// while carrying the given proportion [0.0, inf) of maximum weight capacity. -int burdened_burn_rate( player &dummy, character_movemode move_mode, float burden_proportion = 0.0 ) +// Burden the player with a given proportion [0.0 .. inf) of weight +void burden_player( player &dummy, float burden_proportion ) { clear_character( dummy, false ); units::mass capacity = dummy.weight_capacity(); @@ -63,9 +65,18 @@ int burdened_burn_rate( player &dummy, character_movemode move_mode, float burde if( burden_proportion > 0.0 ) { int gold_units = static_cast( capacity * burden_proportion / 5_gram ); dummy.i_add( item( "gold_small", calendar::turn, gold_units ) ); - REQUIRE( dummy.weight_carried() == capacity * burden_proportion ); } + // Might be off by a few grams + REQUIRE( dummy.weight_carried() >= 0.999 * capacity * burden_proportion ); + REQUIRE( dummy.weight_carried() <= 1.001 * capacity * burden_proportion ); +} + +// Return amount of stamina burned per turn by `burn_move_stamina` in the given movement mode, +// while carrying the given proportion [0.0, inf) of maximum weight capacity. +int burdened_burn_rate( player &dummy, character_movemode move_mode, float burden_proportion = 0.0 ) +{ + burden_player( dummy, burden_proportion ); return actual_burn_rate( dummy, move_mode ); } @@ -185,15 +196,12 @@ TEST_CASE( "modify character stamina", "[stamina][modify]" ) } } -// burn_move_stamina (MODIFIES stamina) -// - Modified by bionic muscles -// - Applies pain if overburdened with no stamina or BADBACK trait TEST_CASE( "stamina burn for movement", "[stamina][burn][move]" ) { player &dummy = g->u; - // Game-balance configured rate of stamina burned per move + // Game-balance configured "normal" rate of stamina burned per move int burn_rate = get_option( "PLAYER_BASE_STAMINA_BURN_RATE" ); GIVEN( "player is naked and unburdened" ) { @@ -201,7 +209,7 @@ TEST_CASE( "stamina burn for movement", "[stamina][burn][move]" ) CHECK( burdened_burn_rate( dummy, CMM_WALK, 0.0 ) == burn_rate ); } - THEN( "running burns 14x the normal amount of stamina per turn" ) { + THEN( "running burns 14 times the normal amount of stamina per turn" ) { CHECK( burdened_burn_rate( dummy, CMM_RUN, 0.0 ) == burn_rate * 14 ); } @@ -215,7 +223,7 @@ TEST_CASE( "stamina burn for movement", "[stamina][burn][move]" ) CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.0 ) == burn_rate ); } - THEN( "running burns 14x the normal amount of stamina per turn" ) { + THEN( "running burns 14 times the normal amount of stamina per turn" ) { CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.0 ) == burn_rate * 14 ); } @@ -228,14 +236,16 @@ TEST_CASE( "stamina burn for movement", "[stamina][burn][move]" ) THEN( "walking burn rate increases by 1 for each percent overburdened" ) { CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.01 ) == burn_rate + 1 ); CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.02 ) == burn_rate + 2 ); - CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.50 ) == burn_rate + 50 ); + //CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.50 ) == burn_rate + 50 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.99 ) == burn_rate + 99 ); CHECK( burdened_burn_rate( dummy, CMM_WALK, 2.00 ) == burn_rate + 100 ); } THEN( "running burn rate increases by 14 for each percent overburdened" ) { CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.01 ) == ( burn_rate + 1 ) * 14 ); CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.02 ) == ( burn_rate + 2 ) * 14 ); - CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.50 ) == ( burn_rate + 50 ) * 14 ); + //CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.50 ) == ( burn_rate + 50 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.99 ) == ( burn_rate + 99 ) * 14 ); CHECK( burdened_burn_rate( dummy, CMM_RUN, 2.00 ) == ( burn_rate + 100 ) * 14 ); } @@ -243,11 +253,53 @@ TEST_CASE( "stamina burn for movement", "[stamina][burn][move]" ) CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.01 ) == ( burn_rate + 1 ) / 2 ); CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.02 ) == ( burn_rate + 2 ) / 2 ); CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.50 ) == ( burn_rate + 50 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.99 ) == ( burn_rate + 99 ) / 2 ); CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 2.00 ) == ( burn_rate + 100 ) / 2 ); } } } +TEST_CASE( "burning stamina when overburdened may cause pain", "[stamina][burn][pain]" ) +{ + player &dummy = g->u; + int pain_before; + int pain_after; + + GIVEN( "character is overburdened" ) { + // As overburden percentage goes from (100% .. 350%), + // chance of pain goes from (1/25 .. 1/1) + // + // To guarantee pain when moving and ensure consistent test results, + // set to 350% burden. + burden_player( dummy, 3.5 ); + + WHEN( "they have zero stamina left" ) { + dummy.set_stamina( 0 ); + REQUIRE( dummy.get_stamina() == 0 ); + + THEN( "they feel pain when carrying too much weight" ) { + pain_before = dummy.get_pain(); + dummy.burn_move_stamina( to_moves( 1_turns ) ); + pain_after = dummy.get_pain(); + CHECK( pain_after > pain_before ); + } + } + + WHEN( "they have a bad back" ) { + dummy.toggle_trait( trait_id( "BADBACK" ) ); + REQUIRE( dummy.has_trait( trait_id( "BADBACK" ) ) ); + + THEN( "they feel pain when carrying too much weight" ) { + pain_before = dummy.get_pain(); + dummy.burn_move_stamina( to_moves( 1_turns ) ); + pain_after = dummy.get_pain(); + CHECK( pain_after > pain_before ); + } + } + } +} + +// TODO: stamina burn is modified by bionic muscles // update_stamina (REFRESHES stamina status) // - Considers PLAYER_BASE_STAMINA_REGEN_RATE From 48cbd7643dd6650e358a09cbe5221d70a0ba373a Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 21 Mar 2020 19:28:39 -0600 Subject: [PATCH 10/15] Use static helper functions --- tests/char_stamina_test.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index 4b87bbc7dfba0..9cc63bbddf654 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -13,7 +13,8 @@ // Return `stamina_move_cost_modifier` in the given move_mode with [0,1] stamina remaining -float move_cost_mod( player &dummy, character_movemode move_mode, float stamina_proportion = 1.0 ) +static float move_cost_mod( player &dummy, character_movemode move_mode, + float stamina_proportion = 1.0 ) { clear_character( dummy ); dummy.remove_effect( efftype_id( "winded" ) ); @@ -34,7 +35,7 @@ float move_cost_mod( player &dummy, character_movemode move_mode, float stamina_ } // Return amount of stamina burned per turn by `burn_move_stamina` in the given movement mode. -int actual_burn_rate( player &dummy, character_movemode move_mode ) +static int actual_burn_rate( player &dummy, character_movemode move_mode ) { // Set starting stamina to max to ensure enough left for 10 turns dummy.set_stamina( dummy.get_stamina_max() ); @@ -56,7 +57,7 @@ int actual_burn_rate( player &dummy, character_movemode move_mode ) } // Burden the player with a given proportion [0.0 .. inf) of weight -void burden_player( player &dummy, float burden_proportion ) +static void burden_player( player &dummy, float burden_proportion ) { clear_character( dummy, false ); units::mass capacity = dummy.weight_capacity(); @@ -74,7 +75,8 @@ void burden_player( player &dummy, float burden_proportion ) // Return amount of stamina burned per turn by `burn_move_stamina` in the given movement mode, // while carrying the given proportion [0.0, inf) of maximum weight capacity. -int burdened_burn_rate( player &dummy, character_movemode move_mode, float burden_proportion = 0.0 ) +static int burdened_burn_rate( player &dummy, character_movemode move_mode, + float burden_proportion = 0.0 ) { burden_player( dummy, burden_proportion ); return actual_burn_rate( dummy, move_mode ); From a311c39bfc051ba7fc6e5853de4a73d34bcf55c7 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 21 Mar 2020 22:29:40 -0600 Subject: [PATCH 11/15] Use Approx; gold to tin; tidy up and comment --- tests/char_stamina_test.cpp | 75 +++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index 9cc63bbddf654..cd6ce0a1bc882 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -12,72 +12,72 @@ // PLAYER_BASE_STAMINA_BURN_RATE (15) -// Return `stamina_move_cost_modifier` in the given move_mode with [0,1] stamina remaining +// Return `stamina_move_cost_modifier` in the given move_mode with [0.0 .. 1.0] stamina remaining static float move_cost_mod( player &dummy, character_movemode move_mode, float stamina_proportion = 1.0 ) { + // Reset and be able to run clear_character( dummy ); dummy.remove_effect( efftype_id( "winded" ) ); + REQUIRE( dummy.can_run() ); - if( move_mode == CMM_RUN ) { - REQUIRE( dummy.can_run() ); - } - + // Walk, run, or crouch dummy.set_movement_mode( move_mode ); REQUIRE( dummy.movement_mode_is( move_mode ) ); + // Adjust stamina to desired proportion and ensure it was set correctly int new_stamina = static_cast( stamina_proportion * dummy.get_stamina_max() ); - dummy.set_stamina( new_stamina ); REQUIRE( dummy.get_stamina() == new_stamina ); + // The point of it all: move cost modifier return dummy.stamina_move_cost_modifier(); } // Return amount of stamina burned per turn by `burn_move_stamina` in the given movement mode. static int actual_burn_rate( player &dummy, character_movemode move_mode ) { - // Set starting stamina to max to ensure enough left for 10 turns + // Ensure we can run if necessary (aaaa zombies!) dummy.set_stamina( dummy.get_stamina_max() ); dummy.remove_effect( efftype_id( "winded" ) ); + REQUIRE( dummy.can_run() ); - if( move_mode == CMM_RUN ) { - REQUIRE( dummy.can_run() ); - } - + // Walk, run, or crouch dummy.set_movement_mode( move_mode ); REQUIRE( dummy.movement_mode_is( move_mode ) ); + // Measure stamina burned, and ensure it is nonzero int before_stam = dummy.get_stamina(); dummy.burn_move_stamina( to_moves( 1_turns ) ); int after_stam = dummy.get_stamina(); REQUIRE( before_stam > after_stam ); + // How much stamina was actually burned? return before_stam - after_stam; } -// Burden the player with a given proportion [0.0 .. inf) of weight +// Burden the player with a given proportion [0.0 .. inf) of their maximum weight capacity static void burden_player( player &dummy, float burden_proportion ) { - clear_character( dummy, false ); units::mass capacity = dummy.weight_capacity(); - // Add gold (5g/unit) to reach the desired weight capacity + // Add tin (2g/unit) to reach the desired weight capacity if( burden_proportion > 0.0 ) { - int gold_units = static_cast( capacity * burden_proportion / 5_gram ); - dummy.i_add( item( "gold_small", calendar::turn, gold_units ) ); + int tin_units = static_cast( capacity * burden_proportion / 2_gram ); + item pile_of_tin( "tin", calendar::turn, tin_units ); + dummy.i_add( pile_of_tin ); } - // Might be off by a few grams - REQUIRE( dummy.weight_carried() >= 0.999 * capacity * burden_proportion ); - REQUIRE( dummy.weight_carried() <= 1.001 * capacity * burden_proportion ); + // Ensure we are carrying the expected amount of weight + REQUIRE( dummy.weight_carried() == capacity * burden_proportion ); } // Return amount of stamina burned per turn by `burn_move_stamina` in the given movement mode, -// while carrying the given proportion [0.0, inf) of maximum weight capacity. +// while carrying the given proportion [0.0, inf) of their maximum weight capacity. static int burdened_burn_rate( player &dummy, character_movemode move_mode, float burden_proportion = 0.0 ) { + clear_character( dummy, false ); burden_player( dummy, burden_proportion ); return actual_burn_rate( dummy, move_mode ); } @@ -100,27 +100,27 @@ TEST_CASE( "stamina movement cost modifier", "[stamina][cost]" ) } SECTION( "running cost goes from 2.0 to 1.0 as stamina goes to zero" ) { - CHECK( move_cost_mod( dummy, CMM_RUN, 1.00 ) == 2.00 ); - CHECK( move_cost_mod( dummy, CMM_RUN, 0.75 ) == 1.75 ); - CHECK( move_cost_mod( dummy, CMM_RUN, 0.50 ) == 1.50 ); - CHECK( move_cost_mod( dummy, CMM_RUN, 0.25 ) == 1.25 ); - CHECK( move_cost_mod( dummy, CMM_RUN, 0.00 ) == 1.00 ); + CHECK( move_cost_mod( dummy, CMM_RUN, 1.00 ) == Approx( 2.00 ) ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.75 ) == Approx( 1.75 ) ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.50 ) == Approx( 1.50 ) ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.25 ) == Approx( 1.25 ) ); + CHECK( move_cost_mod( dummy, CMM_RUN, 0.00 ) == Approx( 1.00 ) ); } SECTION( "walking cost goes from 1.0 to 0.5 as stamina goes to zero" ) { - CHECK( move_cost_mod( dummy, CMM_WALK, 1.00 ) == 1.000 ); - CHECK( move_cost_mod( dummy, CMM_WALK, 0.75 ) == 0.875 ); - CHECK( move_cost_mod( dummy, CMM_WALK, 0.50 ) == 0.750 ); - CHECK( move_cost_mod( dummy, CMM_WALK, 0.25 ) == 0.625 ); - CHECK( move_cost_mod( dummy, CMM_WALK, 0.00 ) == 0.500 ); + CHECK( move_cost_mod( dummy, CMM_WALK, 1.00 ) == Approx( 1.000 ) ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.75 ) == Approx( 0.875 ) ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.50 ) == Approx( 0.750 ) ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.25 ) == Approx( 0.625 ) ); + CHECK( move_cost_mod( dummy, CMM_WALK, 0.00 ) == Approx( 0.500 ) ); } SECTION( "crouching cost goes from 0.5 to 0.25 as stamina goes to zero" ) { - CHECK( move_cost_mod( dummy, CMM_CROUCH, 1.00 ) == 0.5000 ); - CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.75 ) == 0.4375 ); - CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.50 ) == 0.3750 ); - CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.25 ) == 0.3125 ); - CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.00 ) == 0.2500 ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 1.00 ) == Approx( 0.5000 ) ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.75 ) == Approx( 0.4375 ) ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.50 ) == Approx( 0.3750 ) ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.25 ) == Approx( 0.3125 ) ); + CHECK( move_cost_mod( dummy, CMM_CROUCH, 0.00 ) == Approx( 0.2500 ) ); } } @@ -267,12 +267,15 @@ TEST_CASE( "burning stamina when overburdened may cause pain", "[stamina][burn][ int pain_before; int pain_after; - GIVEN( "character is overburdened" ) { + GIVEN( "character is severely overburdened" ) { + // As overburden percentage goes from (100% .. 350%), // chance of pain goes from (1/25 .. 1/1) // // To guarantee pain when moving and ensure consistent test results, // set to 350% burden. + + clear_character( dummy, false ); burden_player( dummy, 3.5 ); WHEN( "they have zero stamina left" ) { From 99e4da5d3728673f411065478dfe881b8ee93dde Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 22 Mar 2020 08:31:46 -0600 Subject: [PATCH 12/15] Add TEST_DATA pseudo-mod and auto-load it Stub in a `data/mods/TEST_DATA` folder for fake/mockup test items and other non-game data for use by test cases. --- data/mods/TEST_DATA/README.md | 11 +++++++++++ data/mods/TEST_DATA/modinfo.json | 12 ++++++++++++ tests/test_main.cpp | 3 +++ 3 files changed, 26 insertions(+) create mode 100644 data/mods/TEST_DATA/README.md create mode 100644 data/mods/TEST_DATA/modinfo.json diff --git a/data/mods/TEST_DATA/README.md b/data/mods/TEST_DATA/README.md new file mode 100644 index 0000000000000..451cc55e4d734 --- /dev/null +++ b/data/mods/TEST_DATA/README.md @@ -0,0 +1,11 @@ +# Test Data pseudo-mod # + +This mod is purely for loading data to be used by `tests/cata_test`. It is +automatically loaded by `tests/test_main.cpp`, so any items, recipes, or other +content defined in the mod will be available to everything in `tests/`. + +The benefit of using this mod for test data is that it allows a clean +separation of tests from in-game content. Instead of testing with content in +the main `data/json` directory, functional tests can use `TEST_DATA` content +to ensure a more stable and controllable set of example data. + diff --git a/data/mods/TEST_DATA/modinfo.json b/data/mods/TEST_DATA/modinfo.json new file mode 100644 index 0000000000000..30ee50eab60b8 --- /dev/null +++ b/data/mods/TEST_DATA/modinfo.json @@ -0,0 +1,12 @@ +[ + { + "type": "MOD_INFO", + "ident": "test_data", + "name": "TESTING DATA", + "description": "Adds mockup items, recipes, and other content for use by automated tests.", + "category": "content", + "//": "Not really obsolete! Marked as such to prevent it from showing in the main list", + "obsolete": true, + "dependencies": [ "dda" ] + } +] diff --git a/tests/test_main.cpp b/tests/test_main.cpp index dd6134cb879c3..8b2ef84b7be8d 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -78,6 +78,9 @@ static std::vector extract_mod_selection( std::vector &arg ret.emplace_back( mod_name ); } } + // Always load test data mod + ret.emplace_back( "test_data" ); + return ret; } From b1089cc93aa87571169dc056f92516cc5d87ee97 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 22 Mar 2020 09:11:40 -0600 Subject: [PATCH 13/15] Use test platinum bit for weight capacity test By using tin powder at increments of 2g, some tests were failing because player capacity could not be exactly reached with 2g increments. Cross-compilation on minGW (according to Travis CI) seems even more susceptible to this, leading to some tests giving more off-by-1 results there than they do on my Ubuntu box. There appear to be no high-density, stackable in-game items with a unit size of only 1g, so I created a new `test_platinum_bit` in the `TEST_DATA` mod folder, and used it in the `burden_player` helper instead of 2g tin powder. --- data/mods/TEST_DATA/metal.json | 20 ++++++++++++++++++++ tests/char_stamina_test.cpp | 8 ++++---- 2 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 data/mods/TEST_DATA/metal.json diff --git a/data/mods/TEST_DATA/metal.json b/data/mods/TEST_DATA/metal.json new file mode 100644 index 0000000000000..c5b43e29f3f46 --- /dev/null +++ b/data/mods/TEST_DATA/metal.json @@ -0,0 +1,20 @@ +[ + { + "id": "test_platinum_bit", + "type": "AMMO", + "category": "spare_parts", + "name": { "str": "TEST platinum bit" }, + "description": "A soft shiny metal. Before the apocalypse this would've been worth a small fortune but now its value is greatly diminished.", + "weight": "1 g", + "//": "Density 21.45g/cm³ ~ 5.4kg/250ml @ stack 1000 = 5g/unit", + "volume": "10ml", + "price": 100000, + "price_postapoc": 100, + "count": 100, + "stack_size": 40, + "material": [ "platinum" ], + "symbol": "/", + "color": "light_gray", + "ammo_type": "components" + } +] diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index cd6ce0a1bc882..47c4e6de1f3a7 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -61,11 +61,11 @@ static void burden_player( player &dummy, float burden_proportion ) { units::mass capacity = dummy.weight_capacity(); - // Add tin (2g/unit) to reach the desired weight capacity + // Add a pile of test platinum bits (1g/unit) to reach the desired weight capacity if( burden_proportion > 0.0 ) { - int tin_units = static_cast( capacity * burden_proportion / 2_gram ); - item pile_of_tin( "tin", calendar::turn, tin_units ); - dummy.i_add( pile_of_tin ); + int units = static_cast( capacity * burden_proportion / 1_gram ); + item pile( "test_platinum_bit", calendar::turn, units ); + dummy.i_add( pile ); } // Ensure we are carrying the expected amount of weight From fc6ce1fac94cb777d7882f806d5526b6879d0212 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 22 Mar 2020 15:32:07 -0600 Subject: [PATCH 14/15] Add stamina regen rate tests; clean up and finish --- tests/char_stamina_test.cpp | 220 +++++++++++++++++++++++++++--------- 1 file changed, 169 insertions(+), 51 deletions(-) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index 47c4e6de1f3a7..ac44f8327043a 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -1,4 +1,5 @@ #include "avatar.h" +#include "bodypart.h" #include "character.h" #include "game.h" #include "options.h" @@ -6,11 +7,39 @@ #include "catch/catch.hpp" #include "player_helpers.h" -// options: -// PLAYER_MAX_STAMINA (10000) -// PLAYER_BASE_STAMINA_REGEN_RATE (20) -// PLAYER_BASE_STAMINA_BURN_RATE (15) +static const efftype_id effect_winded( "winded" ); +// These test cases cover stamina-related functions in the `Character` class, including: +// +// - stamina_move_cost_modifier +// - burn_move_stamina +// - mod_stamina +// - update_stamina +// +// To run all tests in this file: +// +// tests/cata_test [stamina] +// +// Other tags used include: [cost], [move], [burn], [update], [regen]. [encumbrance] + + +// TODO: cover additional aspects of `burn_move_stamina` and `update_stamina`: +// - stamina burn is modified by bionic muscles +// - stamina recovery is modified by "bio_gills" +// - stimulants (positive or negative) affect stamina recovery in mysterious ways + + +// Helpers +// ------- + +// See also `clear_character` in `tests/player_helpers.cpp` + +// Remove "winded" effect from the player (but do not change stamina) +static void catch_breath( player &dummy ) +{ + dummy.remove_effect( effect_winded ); + REQUIRE_FALSE( dummy.has_effect( effect_winded ) ); +} // Return `stamina_move_cost_modifier` in the given move_mode with [0.0 .. 1.0] stamina remaining static float move_cost_mod( player &dummy, character_movemode move_mode, @@ -18,7 +47,7 @@ static float move_cost_mod( player &dummy, character_movemode move_mode, { // Reset and be able to run clear_character( dummy ); - dummy.remove_effect( efftype_id( "winded" ) ); + catch_breath( dummy ); REQUIRE( dummy.can_run() ); // Walk, run, or crouch @@ -39,7 +68,7 @@ static int actual_burn_rate( player &dummy, character_movemode move_mode ) { // Ensure we can run if necessary (aaaa zombies!) dummy.set_stamina( dummy.get_stamina_max() ); - dummy.remove_effect( efftype_id( "winded" ) ); + catch_breath( dummy ); REQUIRE( dummy.can_run() ); // Walk, run, or crouch @@ -82,6 +111,23 @@ static int burdened_burn_rate( player &dummy, character_movemode move_mode, return actual_burn_rate( dummy, move_mode ); } +// Return the actual amount of stamina regenerated by `update_stamina` in the given number of moves +static float actual_regen_rate( player &dummy, int moves ) +{ + // Start at 10% stamina, plenty of space for regen + dummy.set_stamina( dummy.get_stamina_max() / 10 ); + REQUIRE( dummy.get_stamina() == dummy.get_stamina_max() / 10 ); + + int before_stam = dummy.get_stamina(); + dummy.update_stamina( moves ); + int after_stam = dummy.get_stamina(); + + return after_stam - before_stam; +} + + +// Test cases +// ---------- TEST_CASE( "stamina movement cost modifier", "[stamina][cost]" ) { @@ -128,8 +174,9 @@ TEST_CASE( "modify character stamina", "[stamina][modify]" ) { player &dummy = g->u; clear_character( dummy ); + catch_breath( dummy ); REQUIRE_FALSE( dummy.is_npc() ); - REQUIRE_FALSE( dummy.has_effect( efftype_id( "winded" ) ) ); + REQUIRE_FALSE( dummy.has_effect( effect_winded ) ); GIVEN( "character has less than full stamina" ) { int lost_stamina = dummy.get_stamina_max() / 2; @@ -167,7 +214,7 @@ TEST_CASE( "modify character stamina", "[stamina][modify]" ) CHECK( dummy.get_stamina() > 0 ); AND_THEN( "they do not become winded" ) { - REQUIRE_FALSE( dummy.has_effect( efftype_id( "winded" ) ) ); + REQUIRE_FALSE( dummy.has_effect( effect_winded ) ); } } } @@ -179,7 +226,7 @@ TEST_CASE( "modify character stamina", "[stamina][modify]" ) CHECK( dummy.get_stamina() == 0 ); AND_THEN( "they do not become winded" ) { - REQUIRE_FALSE( dummy.has_effect( efftype_id( "winded" ) ) ); + REQUIRE_FALSE( dummy.has_effect( effect_winded ) ); } } } @@ -191,72 +238,72 @@ TEST_CASE( "modify character stamina", "[stamina][modify]" ) CHECK( dummy.get_stamina() == 0 ); AND_THEN( "they become winded" ) { - REQUIRE( dummy.has_effect( efftype_id( "winded" ) ) ); + REQUIRE( dummy.has_effect( effect_winded ) ); } } } } } - TEST_CASE( "stamina burn for movement", "[stamina][burn][move]" ) { player &dummy = g->u; - // Game-balance configured "normal" rate of stamina burned per move - int burn_rate = get_option( "PLAYER_BASE_STAMINA_BURN_RATE" ); + // Defined in game_balance.json + const int normal_burn_rate = get_option( "PLAYER_BASE_STAMINA_BURN_RATE" ); + REQUIRE( normal_burn_rate > 0 ); GIVEN( "player is naked and unburdened" ) { THEN( "walking burns the normal amount of stamina per turn" ) { - CHECK( burdened_burn_rate( dummy, CMM_WALK, 0.0 ) == burn_rate ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 0.0 ) == normal_burn_rate ); } THEN( "running burns 14 times the normal amount of stamina per turn" ) { - CHECK( burdened_burn_rate( dummy, CMM_RUN, 0.0 ) == burn_rate * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 0.0 ) == normal_burn_rate * 14 ); } THEN( "crouching burns 1/2 the normal amount of stamina per turn" ) { - CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 0.0 ) == burn_rate / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 0.0 ) == normal_burn_rate / 2 ); } } GIVEN( "player is at their maximum weight capacity" ) { THEN( "walking burns the normal amount of stamina per turn" ) { - CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.0 ) == burn_rate ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.0 ) == normal_burn_rate ); } THEN( "running burns 14 times the normal amount of stamina per turn" ) { - CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.0 ) == burn_rate * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.0 ) == normal_burn_rate * 14 ); } THEN( "crouching burns 1/2 the normal amount of stamina per turn" ) { - CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.0 ) == burn_rate / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.0 ) == normal_burn_rate / 2 ); } } GIVEN( "player is overburdened" ) { THEN( "walking burn rate increases by 1 for each percent overburdened" ) { - CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.01 ) == burn_rate + 1 ); - CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.02 ) == burn_rate + 2 ); - //CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.50 ) == burn_rate + 50 ); - CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.99 ) == burn_rate + 99 ); - CHECK( burdened_burn_rate( dummy, CMM_WALK, 2.00 ) == burn_rate + 100 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.01 ) == normal_burn_rate + 1 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.02 ) == normal_burn_rate + 2 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.50 ) == normal_burn_rate + 50 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 1.99 ) == normal_burn_rate + 99 ); + CHECK( burdened_burn_rate( dummy, CMM_WALK, 2.00 ) == normal_burn_rate + 100 ); } THEN( "running burn rate increases by 14 for each percent overburdened" ) { - CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.01 ) == ( burn_rate + 1 ) * 14 ); - CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.02 ) == ( burn_rate + 2 ) * 14 ); - //CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.50 ) == ( burn_rate + 50 ) * 14 ); - CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.99 ) == ( burn_rate + 99 ) * 14 ); - CHECK( burdened_burn_rate( dummy, CMM_RUN, 2.00 ) == ( burn_rate + 100 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.01 ) == ( normal_burn_rate + 1 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.02 ) == ( normal_burn_rate + 2 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.50 ) == ( normal_burn_rate + 50 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 1.99 ) == ( normal_burn_rate + 99 ) * 14 ); + CHECK( burdened_burn_rate( dummy, CMM_RUN, 2.00 ) == ( normal_burn_rate + 100 ) * 14 ); } THEN( "crouching burn rate increases by 1/2 for each percent overburdened" ) { - CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.01 ) == ( burn_rate + 1 ) / 2 ); - CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.02 ) == ( burn_rate + 2 ) / 2 ); - CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.50 ) == ( burn_rate + 50 ) / 2 ); - CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.99 ) == ( burn_rate + 99 ) / 2 ); - CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 2.00 ) == ( burn_rate + 100 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.01 ) == ( normal_burn_rate + 1 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.02 ) == ( normal_burn_rate + 2 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.50 ) == ( normal_burn_rate + 50 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 1.99 ) == ( normal_burn_rate + 99 ) / 2 ); + CHECK( burdened_burn_rate( dummy, CMM_CROUCH, 2.00 ) == ( normal_burn_rate + 100 ) / 2 ); } } } @@ -304,22 +351,93 @@ TEST_CASE( "burning stamina when overburdened may cause pain", "[stamina][burn][ } } -// TODO: stamina burn is modified by bionic muscles - -// update_stamina (REFRESHES stamina status) -// - Considers PLAYER_BASE_STAMINA_REGEN_RATE -// - Multiplies by mutation_value( "stamina_regen_modifier" ) -// (from comments): -// - Mouth encumbrance interferes, even with mutated stamina -// - Stim recovers stamina -// - "Affect(sic) 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, effectively countering stamina gain of default 20 (???) -// - at 50% stamina it is -10 (50%), cuts by 25% at 25% stamina (???) -// - If "bio_gills" and enough power, "the effective recovery is up to 5x default" (???) -// !!! calls mod_stamina with roll_remainder (RNG) for final result, then caps it at max -// -TEST_CASE( "update stamina", "[stamina][update]" ) +TEST_CASE( "stamina regeneration rate", "[stamina][update][regen]" ) { + player &dummy = g->u; + clear_character( dummy ); + int turn_moves = to_moves( 1_turns ); + + const float normal_regen_rate = get_option( "PLAYER_BASE_STAMINA_REGEN_RATE" ); + REQUIRE( normal_regen_rate > 0 ); + + GIVEN( "character is not winded" ) { + catch_breath( dummy ); + + THEN( "they regain stamina at the normal rate per turn" ) { + CHECK( actual_regen_rate( dummy, turn_moves ) == normal_regen_rate * turn_moves ); + } + } + + GIVEN( "character is winded" ) { + dummy.add_effect( effect_winded, 10_turns ); + REQUIRE( dummy.has_effect( effect_winded ) ); + + THEN( "they regain stamina at only 10%% the normal rate per turn" ) { + CHECK( actual_regen_rate( dummy, turn_moves ) == 0.1 * normal_regen_rate * turn_moves ); + } + } +} + +TEST_CASE( "stamina regen in different movement modes", "[stamina][update][regen][mode]" ) +{ + player &dummy = g->u; + clear_character( dummy ); + catch_breath( dummy ); + + int turn_moves = to_moves( 1_turns ); + + dummy.set_movement_mode( CMM_RUN ); + REQUIRE( dummy.movement_mode_is( CMM_RUN ) ); + float run_regen_rate = actual_regen_rate( dummy, turn_moves ); + + dummy.set_movement_mode( CMM_WALK ); + REQUIRE( dummy.movement_mode_is( CMM_WALK ) ); + float walk_regen_rate = actual_regen_rate( dummy, turn_moves ); + + dummy.set_movement_mode( CMM_CROUCH ); + REQUIRE( dummy.movement_mode_is( CMM_CROUCH ) ); + float crouch_regen_rate = actual_regen_rate( dummy, turn_moves ); + + THEN( "run and walk mode give the same stamina regen per turn" ) { + CHECK( run_regen_rate == walk_regen_rate ); + } + + THEN( "walk and crouch mode give the same stamina regen per turn" ) { + CHECK( walk_regen_rate == crouch_regen_rate ); + } + + THEN( "crouch and run mode give the same stamina regen per turn" ) { + CHECK( crouch_regen_rate == run_regen_rate ); + } +} + +TEST_CASE( "stamina regen with mouth encumbrance", "[stamina][update][regen][encumbrance]" ) +{ + player &dummy = g->u; + clear_character( dummy ); + catch_breath( dummy ); + + int turn_moves = to_moves( 1_turns ); + + const float normal_regen_rate = get_option( "PLAYER_BASE_STAMINA_REGEN_RATE" ); + REQUIRE( normal_regen_rate > 0 ); + + GIVEN( "character has mouth encumbrance" ) { + dummy.wear_item( item( "scarf_fur" ) ); + REQUIRE( dummy.encumb( bp_mouth ) == 10 ); + + THEN( "stamina regeneration is diminished" ) { + CHECK( actual_regen_rate( dummy, turn_moves ) == ( normal_regen_rate - 2 ) * turn_moves ); + + WHEN( "they have even more mouth encumbrance" ) { + // Layering two scarves triples the encumbrance + dummy.wear_item( item( "scarf_fur" ) ); + REQUIRE( dummy.encumb( bp_mouth ) == 30 ); + + THEN( "stamina regeneration is diminshed further" ) { + CHECK( actual_regen_rate( dummy, turn_moves ) == ( normal_regen_rate - 6 ) * turn_moves ); + } + } + } + } } From c1cad2df8ebbb5ab9fdf8ebb28aee8a622d79e2e Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 22 Mar 2020 15:38:21 -0600 Subject: [PATCH 15/15] Use simpler words --- tests/char_stamina_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/char_stamina_test.cpp b/tests/char_stamina_test.cpp index ac44f8327043a..de22cddf6bf7a 100644 --- a/tests/char_stamina_test.cpp +++ b/tests/char_stamina_test.cpp @@ -426,7 +426,7 @@ TEST_CASE( "stamina regen with mouth encumbrance", "[stamina][update][regen][enc dummy.wear_item( item( "scarf_fur" ) ); REQUIRE( dummy.encumb( bp_mouth ) == 10 ); - THEN( "stamina regeneration is diminished" ) { + THEN( "stamina regen is reduced" ) { CHECK( actual_regen_rate( dummy, turn_moves ) == ( normal_regen_rate - 2 ) * turn_moves ); WHEN( "they have even more mouth encumbrance" ) { @@ -434,7 +434,7 @@ TEST_CASE( "stamina regen with mouth encumbrance", "[stamina][update][regen][enc dummy.wear_item( item( "scarf_fur" ) ); REQUIRE( dummy.encumb( bp_mouth ) == 30 ); - THEN( "stamina regeneration is diminshed further" ) { + THEN( "stamina regen is reduced further" ) { CHECK( actual_regen_rate( dummy, turn_moves ) == ( normal_regen_rate - 6 ) * turn_moves ); } }