diff --git a/data/json/effects.json b/data/json/effects.json index 502fe99469fb0..757f26c4b54db 100644 --- a/data/json/effects.json +++ b/data/json/effects.json @@ -3873,18 +3873,6 @@ "//": "Mood debuff is handled separately by bad_food_mood_debuff EoC.", "rating": "bad" }, - { - "type": "effect_type", - "id": "genetics_damaged", - "name": [ "Spent Phenotype", "Depleted Phenotype", "Bankrupt Phenotype" ], - "desc": [ - "Mutation has left you with a persistent mild discomfort. It seems harmless for now, but you can't predict if it'll get worse.", - "There's an enervating sense of dysmorphia throughout your whole body. It waxes and wanes, but never quite goes away. Mutating this much can't be good for you…", - "You're haunted by phantom pains across your entire body now. Everything aches from an exhaustion that never seems to fade. Any further mutation right now is very unlikely to have a happy ending." - ], - "max_intensity": 3, - "rating": "bad" - }, { "type": "effect_type", "id": "hypovolemia", diff --git a/data/json/mutations/mutations.json b/data/json/mutations/mutations.json index 96bccdd8dca94..57a20cd66c9f9 100644 --- a/data/json/mutations/mutations.json +++ b/data/json/mutations/mutations.json @@ -1190,11 +1190,10 @@ "id": "ROBUST", "name": { "str": "Robust Genetics" }, "points": 3, - "description": "Your genetics have rapidly adapted to the chaos of the Cataclysm, and the genetic damage caused by mutations will fade much quicker.", + "description": "Your genome has rapidly adapted to the Cataclysm and can handle the strain of mutation better. Taking different kinds of mutagen won't result in more defective mutations than normal.", "starting_trait": true, "cancels": [ "CHAOTIC_BAD" ], - "category": [ "FISH", "SLIME", "ALPHA", "MEDICAL", "PLANT" ], - "vitamin_rates": [ [ "instability", 7200 ] ] + "category": [ "FISH", "SLIME", "ALPHA", "MEDICAL", "PLANT" ] }, { "type": "mutation", diff --git a/data/json/obsoletion_and_migration_0.I/migration_mutation.json b/data/json/obsoletion_and_migration_0.I/migration_mutation.json index 9b84bd7181881..5b34b460b6b8e 100644 --- a/data/json/obsoletion_and_migration_0.I/migration_mutation.json +++ b/data/json/obsoletion_and_migration_0.I/migration_mutation.json @@ -3,5 +3,29 @@ "type": "TRAIT_MIGRATION", "id": "BILE_STINK", "remove": true + }, + { + "id": "instability", + "type": "vitamin", + "vit_type": "counter", + "name": { "str": "Instability" }, + "excess": "genetics_damaged", + "min": 0, + "max": 8000, + "flags": [ "NO_DISPLAY" ], + "rate": "1 s", + "disease_excess": [ [ 1, 899 ], [ 900, 2799 ], [ 2800, 8000 ] ] + }, + { + "type": "effect_type", + "id": "genetics_damaged", + "name": [ "Spent Phenotype", "Depleted Phenotype", "Bankrupt Phenotype" ], + "desc": [ + "Mutation has left you with a persistent mild discomfort. It seems harmless for now, but you can't predict if it'll get worse.", + "There's an enervating sense of dysmorphia throughout your whole body. It waxes and wanes, but never quite goes away. Mutating this much can't be good for you…", + "You're haunted by phantom pains across your entire body now. Everything aches from an exhaustion that never seems to fade. Any further mutation right now is very unlikely to have a happy ending." + ], + "max_intensity": 3, + "rating": "bad" } ] diff --git a/data/json/vitamin.json b/data/json/vitamin.json index 8a6f7199ac09c..4b7dac5429ca6 100644 --- a/data/json/vitamin.json +++ b/data/json/vitamin.json @@ -125,7 +125,6 @@ [ "mutagen_troglobite", 1 ], [ "mutagen_chiropteran", 1 ], [ "mutagen_ursine", 1 ], - [ "instability", 1 ], [ "mutant_toxin", 2 ] ] }, @@ -477,18 +476,6 @@ "rate": "1 h", "disease_excess": [ [ 100, 500 ], [ 501, 2199 ], [ 2200, 2500 ] ] }, - { - "id": "instability", - "type": "vitamin", - "vit_type": "counter", - "name": { "str": "Instability" }, - "excess": "genetics_damaged", - "min": 0, - "max": 8000, - "flags": [ "NO_DISPLAY" ], - "rate": "2 h", - "disease_excess": [ [ 1, 899 ], [ 900, 2799 ], [ 2800, 8000 ] ] - }, { "id": "bad_food", "type": "vitamin", diff --git a/src/character.cpp b/src/character.cpp index ee91b6d6d7690..610b75e05c942 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -7586,7 +7586,7 @@ std::string get_stat_name( character_stat Stat ) return pgettext( "fake stat there's an error", "ERR" ); } -int Character::mutation_height( const trait_id &mut ) +int Character::mutation_height( const trait_id &mut ) const { const mutation_branch &mdata = mut.obj(); int height_prereqs = 0; diff --git a/src/character.h b/src/character.h index 751374d2f7c4a..dffa4b0c53683 100644 --- a/src/character.h +++ b/src/character.h @@ -1042,6 +1042,25 @@ class Character : public Creature, public visitable steed_type get_steed_type() const; virtual void set_movement_mode( const move_mode_id &mode ) = 0; + /** + * Generates an integer based on how many times we've gained non-negative mutations. + * This is asked for any given tree, but counts all of our mutations in total. + * Different than mutation_category_level[] in many ways: + * - Does not count base traits, even if those are mutable, whereas mutation_category_level[] does. + * - Does not count negative mutations, whereas mutation_category_level[] does. + * - Assigns 1 point to each level of mutation in our category, and 2 for each level out of it. + * - Individually counts each step of a multi level mutation (it counts Strong *and* Very Strong as their own mutations). + * - mutation_category_level[] ignores Strong and counts Very Strong as slightly more than 1 mutation, but not 2 mutations. + * - Meanwhile this counts Very Strong as 2 mutations, since you had to mutate Strong and then mutate that into Very Strong. + * - This is to mimic the behavior of the old instability vitamin, which increased by 100 each time you mutated (so Very Strong was 200 instability). + * The final result is used to calculate our current instability (influences chance of a negative mutation) + * so each mutation we have that belongs to a different tree than the one we specified counts double. + * Example: you start with Trog and mutate Slimy and Night Vision. Within Trog you have 2 points. + * You then go to mutate Rat. Rat has Night Vision but not Slimy, so you have 1+2=3 points. + * Having the Robust Genetics trait lets you "negate" this penalty, and makes all traits worth just 1 instability whether in/out of tree. + */ + int get_instability_per_category( const mutation_category_id &categ ) const; + /**Determine if character is susceptible to dis_type and if so apply the symptoms*/ void expose_to_disease( const diseasetype_id &dis_type ); /** @@ -1584,8 +1603,8 @@ class Character : public Creature, public visitable const vitamin_id &mut_vit ) const; bool mutation_ok( const trait_id &mutation, bool allow_good, bool allow_bad, bool allow_neutral ) const; - /** Roll, based on instability, whether next mutation should be good or bad */ - bool roll_bad_mutation() const; + /** Roll, based on category and total mutations in/out of it, whether next mutation should be good or bad */ + bool roll_bad_mutation( const mutation_category_id &categ ) const; /** Opens a menu which allows players to choose from a list of mutations */ bool mutation_selector( const std::vector &prospective_traits, const mutation_category_id &cat, const bool &use_vitamins ); @@ -1610,7 +1629,7 @@ class Character : public Creature, public visitable /** Try to cross The Threshold */ void test_crossing_threshold( const mutation_category_id &mutation_category ); /** Returns how many steps are required to reach a mutation */ - int mutation_height( const trait_id &mut ); + int mutation_height( const trait_id &mut ) const; /** Recalculates mutation_category_level[] values for the player */ void calc_mutation_levels(); /** Returns a weighted list of mutation categories based on blood vitamin levels */ diff --git a/src/mutation.cpp b/src/mutation.cpp index 79b02a6cfac4b..62ef29e4ce1f9 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -78,14 +78,13 @@ static const trait_id trait_M_BLOOM( "M_BLOOM" ); static const trait_id trait_M_FERTILE( "M_FERTILE" ); static const trait_id trait_M_PROVENANCE( "M_PROVENANCE" ); static const trait_id trait_NAUSEA( "NAUSEA" ); +static const trait_id trait_ROBUST( "ROBUST" ); static const trait_id trait_SLIMESPAWNER( "SLIMESPAWNER" ); static const trait_id trait_SNAIL_TRAIL( "SNAIL_TRAIL" ); static const trait_id trait_TREE_COMMUNION( "TREE_COMMUNION" ); static const trait_id trait_VOMITOUS( "VOMITOUS" ); static const trait_id trait_WEB_WEAVER( "WEB_WEAVER" ); -static const vitamin_id vitamin_instability( "instability" ); - namespace io { @@ -172,6 +171,50 @@ bool Character::has_base_trait( const trait_id &b ) const return my_traits.find( b ) != my_traits.end(); } +int Character::get_instability_per_category( const mutation_category_id &categ ) const +{ + int mut_count = 0; + bool robust = has_trait( trait_ROBUST ); + // For each and every trait we have... + for( const trait_id &mut : get_mutations() ) { + // only count muts that have 0 or more points, aren't a threshold, are valid, and aren't a base trait. + if( mut.obj().points > -1 && !mut.obj().threshold && mut.obj().valid && !has_base_trait( mut ) ) { + bool in_categ = false; + // If among all allowed categories the mutation has, the input category is one of them. + for( const mutation_category_id &Ch_cat : mut.obj().category ) { + if( Ch_cat == categ ) { + in_categ = true; + } + } + + const int height = mutation_height( mut ); + + // Thus add 1 point if it's in the tree we mutate into, otherwise add 2 points + // or, if we have Robust Genetics, treat all mutations as in-tree. + if( in_categ || robust ) { + mut_count += height * 1; + } else { + mut_count += height * 2; + } + } + } + return mut_count; +} + +int get_total_nonbad_in_category( const mutation_category_id &categ ) +{ + int mut_count = 0; + + // Iterate through all available traits in this category and count every one that isn't bad or the threshold. + for( const trait_id &traits_iter : mutations_category[categ] ) { + const mutation_branch &mdata = traits_iter.obj(); + if( mdata.points > -1 && !mdata.threshold ) { + mut_count += 1; + } + } + return mut_count; +} + void Character::toggle_trait( const trait_id &trait_, const std::string &var_ ) { // Take copy of argument because it might be a reference into a container @@ -982,27 +1025,30 @@ bool Character::mutation_ok( const trait_id &mutation, bool allow_good, bool all return true; } -bool Character::roll_bad_mutation() const +bool Character::roll_bad_mutation( const mutation_category_id &categ ) const { + // We will never have worse odds than this no matter our instability + float MAX_BAD_CHANCE = 0.67; + // or, if we have Robust, cap it lower. + bool ret = false; - //Instability value at which bad mutations become possible - const float I0 = 900.0; - //Instability value at which good and bad mutations are equally likely - const float I50 = 2800.0; - //Static to avoid recalculating this every time - std::log is not constexpr - static const float exp = std::log( 2 ) / std::log( I50 / I0 ); + // The following values are, respectively, the total number of non-bad traits in a category and + int muts_max = get_total_nonbad_in_category( categ ); + // how many good mutations we have in total. Mutations which don't belong to the tree we're mutating towards count double for this value. Starting traits don't count at all. + int insta_actual = get_instability_per_category( categ ); - if( vitamin_get( vitamin_instability ) == 0 ) { - add_msg_debug( debugmode::DF_MUTATION, "No instability, no bad mutations allowed" ); + if( insta_actual == 0 ) { + add_msg_debug( debugmode::DF_MUTATION, "No mutations yet, no bad mutations allowed" ); return ret; } else { - //A curve that is 0 until I0, crosses 0.5 at I50, then slowly approaches 1 - float chance = std::max( 0.0f, 1 - std::pow( I0 / vitamin_get( vitamin_instability ), exp ) ); + // When we have a total instability score equal to the number of non-bad mutations in the tree, our odds of good/bad are 50/50. + float chance = 0.5 * static_cast( insta_actual ) / static_cast( muts_max ); + chance = std::min( chance, MAX_BAD_CHANCE ); ret = rng_float( 0, 1 ) < chance; add_msg_debug( debugmode::DF_MUTATION, - "Bad mutation chance caused by instability %.1f, roll_bad_mutation returned %s", chance, - ret ? "true" : "false" ); + "%s is the instability category chosen, which has %d total good traits. Adjusted instability score for the category is %d, giving a chance of bad mut of %.3f.", + categ.c_str(), muts_max, insta_actual, chance ); return ret; } } @@ -1025,12 +1071,6 @@ void Character::mutate( const int &true_random_chance, bool use_vitamins ) allow_good = true; allow_bad = true; try_opposite = false; - } else if( roll_bad_mutation() ) { - // If we picked bad, mutation can be bad or neutral - allow_bad = true; - } else { - // Otherwise, can be good or neutral - allow_good = true; } add_msg_debug( debugmode::DF_MUTATION, "mutate: true_random_chance %d", @@ -1048,6 +1088,14 @@ void Character::mutate( const int &true_random_chance, bool use_vitamins ) cat = *cat_list.pick(); cat_list.add_or_replace( cat, 0 ); add_msg_debug( debugmode::DF_MUTATION, "Picked category %s", cat.c_str() ); + // Only decide if it's good or bad after we pick the category. + if( roll_bad_mutation( cat ) ) { + // If we picked bad, mutation can be bad or neutral. + allow_bad = true; + } else { + // Otherwise, can be good or neutral. + allow_good = true; + } } else { // This is fairly direct in explaining why it fails - hopefully it'll help folks to learn the system without needing to read docs add_msg_if_player( m_bad, @@ -1241,7 +1289,7 @@ void Character::mutate_category( const mutation_category_id &cat, const bool use // Mutation selector and true_random overrides good / bad mutation rolls allow_good = true; allow_bad = true; - } else if( roll_bad_mutation() ) { + } else if( roll_bad_mutation( cat ) ) { // If we picked bad, mutation can be bad or neutral allow_bad = true; } else { @@ -1613,8 +1661,6 @@ bool Character::mutate_towards( const trait_id &mut, const mutation_category_id mut_vit.c_str(), vitamin_get( mut_vit ), vitamin_cost ); vitamin_mod( mut_vit, -vitamin_cost ); add_msg_debug( debugmode::DF_MUTATION, "mutate_towards: vitamin level %d", vitamin_get( mut_vit ) ); - // No instability necessary for true random mutations - they are, after all, true random - vitamin_mod( vitamin_instability, vitamin_cost ); } else { add_msg_debug( debugmode::DF_MUTATION, "mutate_towards: vitamin %s level %d below vitamin cost %d", mut_vit.c_str(), vitamin_get( mut_vit ), vitamin_cost ); diff --git a/src/mutation.h b/src/mutation.h index a0cc6d728330d..cbe8091453c6d 100644 --- a/src/mutation.h +++ b/src/mutation.h @@ -554,6 +554,7 @@ bool b_is_higher_trait_of_a( const trait_id &trait_a, const trait_id &trait_b ); bool are_opposite_traits( const trait_id &trait_a, const trait_id &trait_b ); bool are_same_type_traits( const trait_id &trait_a, const trait_id &trait_b ); bool contains_trait( std::vector> traits, const trait_id &trait ); +int get_total_nonbad_in_category( const mutation_category_id &categ ); enum class mutagen_technique : int { consumed_mutagen, diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index ce43b64edad12..ebfabb5105fd3 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -24,9 +24,16 @@ static const mutation_category_id mutation_category_LUPINE( "LUPINE" ); static const mutation_category_id mutation_category_MOUSE( "MOUSE" ); static const mutation_category_id mutation_category_RAPTOR( "RAPTOR" ); static const mutation_category_id mutation_category_REMOVAL_TEST( "REMOVAL_TEST" ); +static const mutation_category_id mutation_category_TROGLOBITE( "TROGLOBITE" ); +static const trait_id trait_EAGLEEYED( "EAGLEEYED" ); +static const trait_id trait_FELINE_EARS( "FELINE_EARS" ); static const trait_id trait_GOURMAND( "GOURMAND" ); +static const trait_id trait_MYOPIC( "MYOPIC" ); +static const trait_id trait_QUICK( "QUICK" ); static const trait_id trait_SMELLY( "SMELLY" ); +static const trait_id trait_STR_UP( "STR_UP" ); +static const trait_id trait_STR_UP_2( "STR_UP_2" ); static const trait_id trait_TEST_OVERMAP_SIGHT_5( "TEST_OVERMAP_SIGHT_5" ); static const trait_id trait_TEST_OVERMAP_SIGHT_MINUS_10( "TEST_OVERMAP_SIGHT_MINUS_10" ); static const trait_id trait_TEST_REMOVAL_0( "TEST_REMOVAL_0" ); @@ -37,10 +44,8 @@ static const trait_id trait_TEST_TRIGGER_2_active( "TEST_TRIGGER_2_active" ); static const trait_id trait_TEST_TRIGGER_active( "TEST_TRIGGER_active" ); static const trait_id trait_UGLY( "UGLY" ); -static const vitamin_id vitamin_instability( "instability" ); static const vitamin_id vitamin_mutagen( "mutagen" ); static const vitamin_id vitamin_mutagen_human( "mutagen_human" ); -static const vitamin_id vitamin_mutagen_test( "mutagen_test" ); static const vitamin_id vitamin_mutagen_test_removal( "mutagen_test_removal" ); static std::string get_mutations_as_string( const Character &you ); @@ -563,70 +568,129 @@ TEST_CASE( "All_valid_mutations_can_be_purified", "[mutations][purifier]" ) } } -//The chance of a mutation being bad is a function of instability, see -//Character::roll_bad_mutation. This can't be easily tested on in-game -//mutations, because exceptions exist - e.g., you can roll a good mutation, but -//end up mutating a bad one, because the bad mutation is a prerequisite for the one -//you actually rolled. -//For this reason, this tests on an artificial category that doesn't -//mix good and bad mutations within trees. Additionally, it doesn't contain -//neutral mutations, because those are always allowed. -//This also incidentally tests that, given available mutations and enough mutagen, -//Character::mutate always succeeds to give exactly one mutation. +// Your odds of getting a good or bad mutation depend on what mutations you have and what tree you're in. +// You are given an integer that is first multiplied by 0.5 and then divided by the total non-bad mutations in the tree. +// For example if you mutate into Alpha, that tree has 21 mutations (as of 3/14/2024) that aren't bad. +// Troglobite has 28 mutations that aren't bad. (as of 3/4/2024). +// Thus, for all things equal, Alpha gets more bad mutations per good mutation than Trog, +// but since Trog gets more good muts total it has more chances to get bad mutations. +// The aforementioned "integer" is increased from 0 by 1 for each non-bad mutation you have. +// Mutations you have are counted as two if they don't belong to the tree you're mutating into. +// Starting traits are never counted, and bad mutations are never counted. Only "valid" (mutable) mutations count. +// This test case compares increasing instability in both Alpha and Trog as more mutations are added. +// Given that Alpha and Trog share some mutations but not all, they should either increase instability at the same or different rates. +// This test then also checks and makes sure that Alpha gets more bad mutations than Trog given the same total instability in each. +// After that it checks to make sure each tree gets more bad mutations than before after increasing effective instability by 4x. + TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" ) { Character &dummy = get_player_character(); + clear_avatar(); - static const std::vector> bad_chance_by_inst = { - {0, 0.0f}, - {500, 0.0f}, - {1000, 0.062f}, - {1500, 0.268f}, - {2000, 0.386f}, - {2500, 0.464f}, - {3000, 0.521f}, - {4000, 0.598f}, - {5000, 0.649f}, - {6000, 0.686f}, - {8000, 0.737f} - }; - - const int tries = 1000; - const int muts_per_try = 5; - const float margin = 0.05f; - - for( const std::pair &ilevel : bad_chance_by_inst ) { - int bad = 0; - for( int i = 0; i < tries; i++ ) { - clear_avatar(); - dummy.vitamin_set( vitamin_mutagen_test, 10000 ); - for( int ii = 0; ii < muts_per_try; ii++ ) { - dummy.vitamin_set( vitamin_instability, ilevel.first ); - dummy.mutate( 0, true ); - } + REQUIRE( dummy.get_instability_per_category( mutation_category_ALPHA ) == 0 ); + REQUIRE( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 0 ); - std::vector muts = dummy.get_mutations(); - REQUIRE( muts.size() == static_cast( muts_per_try ) ); - for( const trait_id &m : muts ) { - REQUIRE( m.obj().points != 0 ); - if( m.obj().points < 0 ) { - bad += 1; - } - } + + WHEN( "character mutates Strong, a mutation belonging to both Alpha and Troglobite" ) { + dummy.set_mutation( trait_STR_UP ); + THEN( "Both Alpha and Troglobite see their instability increase by 1" ) { + CHECK( dummy.get_instability_per_category( mutation_category_ALPHA ) == 1 ); + CHECK( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 1 ); + } + } + + WHEN( "they then mutate Very Strong, which requires Strong and also belongs to both trees" ) { + dummy.set_mutation( trait_STR_UP_2 ); + THEN( "Both Alpha and Troglobite see their instability increase by 1 more (2 total)" ) { + CHECK( dummy.get_instability_per_category( mutation_category_ALPHA ) == 2 ); + CHECK( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 2 ); + } + } + + WHEN( "they then mutate Quick, which belongs to Troglobite but not Alpha" ) { + dummy.set_mutation( trait_STR_UP_2 ); + dummy.set_mutation( trait_QUICK ); + THEN( "Alpha increases instability by 2 and Troglobite increases by 1" ) { + CHECK( dummy.get_instability_per_category( mutation_category_ALPHA ) == 4 ); + CHECK( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 3 ); + } + } + + WHEN( "The character has Quick as a starting trait instead of a mutation" ) { + dummy.set_mutation( trait_STR_UP_2 ); + dummy.toggle_trait( trait_QUICK ); + REQUIRE( dummy.has_trait( trait_QUICK ) ); + THEN( "Neither Alpha or Troglobite have their instability increased" ) { + CHECK( dummy.get_instability_per_category( mutation_category_ALPHA ) == 2 ); + CHECK( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 2 ); + } + } + + WHEN( "The character mutates Near Sighted, which is a \"bad\" mutation" ) { + dummy.set_mutation( trait_STR_UP_2 ); + dummy.set_mutation( trait_MYOPIC ); + THEN( "They do not gain instability since only 0+ point mutations increase that" ) { + CHECK( dummy.get_instability_per_category( mutation_category_ALPHA ) == 2 ); + CHECK( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 2 ); } + } - INFO( "Current instability: " << ilevel.first ); - if( ilevel.second == 0.0f ) { - CHECK( bad == 0 ); - } else { - float lower = ilevel.second - margin; - float upper = ilevel.second + margin; - float frac_bad = static_cast( bad ) / ( tries * muts_per_try ); + const int tries = 10000; + int trogBads = 0; + int alphaBads = 0; - CHECK( frac_bad > lower ); - CHECK( frac_bad < upper ); + clear_avatar(); + dummy.set_mutation( trait_STR_UP_2 ); + REQUIRE( dummy.get_instability_per_category( mutation_category_ALPHA ) == 2 ); + REQUIRE( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 2 ); + + // 10k trials, compare the number of successes of bad mutations. + // As of 3/4/2024, Alpha has 21 non-bad mutations and Trog has 28. + // Given that effective instability is currently 2, Alpha should see avg. 475 badmuts and trog 357 avg. + // The odds of Trog rolling well enough to "win" are effectively zero if roll_bad_mutation() works correctly. + // roll_bad_mutation does not actually give a bad mutation; it is called by the primary mutate function. + for( int i = 0; i < tries; i++ ) { + if( dummy.roll_bad_mutation( mutation_category_ALPHA ) ) { + alphaBads++; + } + if( dummy.roll_bad_mutation( mutation_category_TROGLOBITE ) ) { + trogBads++; } } + + WHEN( "Alpha and Troglobite both have the same level of instability" ) { + THEN( "Alpha has fewer total mutations, so its odds of a bad mutation are higher than Trog's" ) { + CHECK( alphaBads > trogBads ); + } + } + + // Then, increase our instability and try again. + dummy.set_mutation( trait_FELINE_EARS ); + dummy.set_mutation( trait_EAGLEEYED ); + dummy.set_mutation( trait_GOURMAND ); + REQUIRE( dummy.get_instability_per_category( mutation_category_ALPHA ) == 8 ); + REQUIRE( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 8 ); + + int trogBads2 = 0; + int alphaBads2 = 0; + + for( int i = 0; i < tries; i++ ) { + if( dummy.roll_bad_mutation( mutation_category_ALPHA ) ) { + alphaBads2++; + } + if( dummy.roll_bad_mutation( mutation_category_TROGLOBITE ) ) { + trogBads2++; + } + } + + WHEN( "The player has more mutation instability than before" ) { + THEN( "They have a higher chance of getting bad mutations than before" ) { + CHECK( alphaBads2 > alphaBads ); + CHECK( trogBads2 > trogBads ); + } + } + clear_avatar(); + } // Verify that flags linked to core mutations are still there.