From 1a5d5106781c75b6dae4acb03ff1c6bb0392270b Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:54:55 -0700 Subject: [PATCH 01/25] adjust instability mechanics --- src/character.h | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/character.h b/src/character.h index ebb989fc66a50..0f183909034bd 100644 --- a/src/character.h +++ b/src/character.h @@ -1042,6 +1042,23 @@ 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 negative mutations + * - 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 (likelihood 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 Light Sensitive. Within Trog you have 2 points. + * you then go to mutate Rat. Rat has Light Sensitive but not Slimy, so you have 1+2=3 points. + */ + 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 +1601,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 +1627,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 */ From 0513a2c94d8b18234170e30b368745b2d20b6869 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:57:58 -0700 Subject: [PATCH 02/25] make mutation_height() a const --- src/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/character.cpp b/src/character.cpp index ecafba393bb9d..6228ec859497d 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -7659,7 +7659,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; From 02a4153f7de115c5f6a55e9b0da00a50cb0f49fb Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:03:08 -0700 Subject: [PATCH 03/25] adjust instability mechanics --- src/mutation.cpp | 102 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 77 insertions(+), 25 deletions(-) diff --git a/src/mutation.cpp b/src/mutation.cpp index 79b02a6cfac4b..4f72a8fc3d7bc 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; + // 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 + int cats = 0; + for( const mutation_category_id &Ch_cat : mut.obj().category ) { + if( Ch_cat == categ ) { + in_categ = true; + } + cats++; + } + + const int height = mutation_height( mut ); + + // thus add 1 point if it's in the tree we mutate into, otherwise add 2 points + if( in_categ ) { + 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,36 @@ 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 { + // if we have Robust Genetics, it reduces our instability by this much + int ROBUST_FACTOR = 5; + // 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 robust = has_trait( trait_ROBUST ); + if( robust ) { + MAX_BAD_CHANCE = 0.50; + } 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 nonbad 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 ) ); + if( robust ) { + insta_actual = std::max( 0, insta_actual - ROBUST_FACTOR ); + } + // when we have a total instability score equal to the number of nonbad 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" ); + add_msg_debug( debugmode::DF_MUTATION, "%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 +1077,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 +1094,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 +1295,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 +1667,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 ); From 2d3909866ca281b49a8dc2c6fc759f767720255d Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:04:11 -0700 Subject: [PATCH 04/25] adjust instability mechanics --- src/mutation.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mutation.h b/src/mutation.h index 6b933fc2aed9f..b07b861e6ab5a 100644 --- a/src/mutation.h +++ b/src/mutation.h @@ -569,6 +569,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, From 05d6d7b2c5f4661dd3c6333b9595affe2d52ce76 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:06:35 -0700 Subject: [PATCH 05/25] rewrite instability test --- tests/mutation_test.cpp | 166 ++++++++++++++++++++++++++-------------- 1 file changed, 109 insertions(+), 57 deletions(-) diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index ce43b64edad12..a50edeac981ae 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -23,10 +23,13 @@ static const mutation_category_id mutation_category_HUMAN( "HUMAN" ); 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_SMELLY( "SMELLY" ); +static const trait_id trait_MYOPIC( "MYOPIC" ); +static const trait_id trait_QUICK( "QUICK" ); 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,7 +40,6 @@ 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" ); @@ -563,70 +565,120 @@ 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 ); } + } - 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 ); + WHEN( "they then mutate Quick, which belongs to Troglobite but not Alpha" ) { + 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 ); + } + } - CHECK( frac_bad > lower ); - CHECK( frac_bad < upper ); + WHEN( "The character has Quick as a starting trait instead of a mutation" ) { + dummy.toggle_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_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 ); + } + } + + const int tries = 10000; + int trogBads = 0; + int alphaBads = 0; + + // 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. From 3102656eadd8d1ca16e75c5b9f74b3569ba97185 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:07:31 -0700 Subject: [PATCH 06/25] obsolete instability --- data/json/vitamin.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/data/json/vitamin.json b/data/json/vitamin.json index 0639271f9cc7b..4a34441902c9e 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", From 322704b1e4ece3bd9d59422c01fbb09bfdf7b57e Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:08:40 -0700 Subject: [PATCH 07/25] obsolete instability --- data/json/effects.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/data/json/effects.json b/data/json/effects.json index 6735d6c84b41f..571e98144393e 100644 --- a/data/json/effects.json +++ b/data/json/effects.json @@ -3868,18 +3868,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", From 15dfba6b10c716d0a98138b86d1a2559d3a73cba Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:08:50 -0700 Subject: [PATCH 08/25] obsolete instability --- .../migration_mutation.json | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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" } ] From a8d37285e97bff5b3295f0f34624799be929769f Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:10:29 -0700 Subject: [PATCH 09/25] untie Robust Genetics from instability --- data/json/mutations/mutations.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data/json/mutations/mutations.json b/data/json/mutations/mutations.json index 960514e8c135b..d6ff2c6dfad7b 100644 --- a/data/json/mutations/mutations.json +++ b/data/json/mutations/mutations.json @@ -1184,11 +1184,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 chaos of the Cataclysm, and the genetic damage caused by mutations will accumulate more slowly.", "starting_trait": true, "cancels": [ "CHAOTIC_BAD" ], - "category": [ "FISH", "SLIME", "ALPHA", "MEDICAL", "PLANT" ], - "vitamin_rates": [ [ "instability", 7200 ] ] + "category": [ "FISH", "SLIME", "ALPHA", "MEDICAL", "PLANT" ] }, { "type": "mutation", From d6bbbea5a50d5344d4bddb7b662ffb1f4cf16741 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:13:08 -0700 Subject: [PATCH 10/25] ck --- src/character.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/character.h b/src/character.h index 0f183909034bd..a29094b6e030c 100644 --- a/src/character.h +++ b/src/character.h @@ -1054,8 +1054,8 @@ class Character : public Creature, public visitable * - 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 (likelihood 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 Light Sensitive. Within Trog you have 2 points. - * you then go to mutate Rat. Rat has Light Sensitive but not Slimy, so you have 1+2=3 points. + * 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. */ int get_instability_per_category( const mutation_category_id &categ ) const; From 3889f77be5ae5f09c6d14a8e25941b98512f6df7 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:16:41 -0700 Subject: [PATCH 11/25] add additional clarification --- src/character.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/character.h b/src/character.h index a29094b6e030c..51d3db81ade53 100644 --- a/src/character.h +++ b/src/character.h @@ -1046,13 +1046,14 @@ class Character : public Creature, public visitable * 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 negative mutations + * - 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 (likelihood of a negative mutation) + * 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. From 639ecc5f306bdca9a53167866c0af5c918a0b0a7 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 23:36:54 -0700 Subject: [PATCH 12/25] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/mutation.cpp | 46 +++++++++++++++++++------------------ tests/mutation_test.cpp | 50 ++++++++++++++++++++--------------------- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/mutation.cpp b/src/mutation.cpp index 4f72a8fc3d7bc..fa45ce4644e55 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -174,23 +174,23 @@ bool Character::has_base_trait( const trait_id &b ) const int Character::get_instability_per_category( const mutation_category_id &categ ) const { int mut_count = 0; - // for each and every trait we have... + // 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 + // 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 - int cats = 0; + // if among all allowed categories the mutation has, the input category is one of them + int cats = 0; for( const mutation_category_id &Ch_cat : mut.obj().category ) { if( Ch_cat == categ ) { in_categ = true; } - cats++; + cats++; } - const int height = mutation_height( mut ); + const int height = mutation_height( mut ); - // thus add 1 point if it's in the tree we mutate into, otherwise add 2 points + // thus add 1 point if it's in the tree we mutate into, otherwise add 2 points if( in_categ ) { mut_count += height * 1; } else { @@ -1027,34 +1027,36 @@ bool Character::mutation_ok( const trait_id &mutation, bool allow_good, bool all bool Character::roll_bad_mutation( const mutation_category_id &categ ) const { - // if we have Robust Genetics, it reduces our instability by this much + // if we have Robust Genetics, it reduces our instability by this much int ROBUST_FACTOR = 5; - // we will never have worse odds than this no matter our instability + // 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 robust = has_trait( trait_ROBUST ); - if( robust ) { + // or if we have Robust, cap it lower + bool robust = has_trait( trait_ROBUST ); + if( robust ) { MAX_BAD_CHANCE = 0.50; - } + } bool ret = false; // the following values are, respectively, the total number of nonbad 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 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( insta_actual == 0 ) { add_msg_debug( debugmode::DF_MUTATION, "No mutations yet, no bad mutations allowed" ); return ret; } else { - if( robust ) { - insta_actual = std::max( 0, insta_actual - ROBUST_FACTOR ); - } - // when we have a total instability score equal to the number of nonbad mutations in the tree, our odds of good/bad are 50/50 + if( robust ) { + insta_actual = std::max( 0, insta_actual - ROBUST_FACTOR ); + } + // when we have a total instability score equal to the number of nonbad 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 ); + chance = std::min( chance, MAX_BAD_CHANCE ); ret = rng_float( 0, 1 ) < chance; - add_msg_debug( debugmode::DF_MUTATION, "%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 ); + add_msg_debug( debugmode::DF_MUTATION, + "%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; } } @@ -1094,7 +1096,7 @@ 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 + // 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; diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index a50edeac981ae..a0a5676b5d05b 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -589,66 +589,66 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" WHEN( "character mutates Strong, a mutation belonging to both Alpha and Troglobite" ) { - dummy.set_mutation( trait_STR_UP ); + 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 ); + 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 ); + 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 ); + 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_QUICK ); + 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 ); + 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.toggle_trait( trait_QUICK ); + dummy.toggle_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 ); + 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_MYOPIC ); + 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 ); + CHECK( dummy.get_instability_per_category( mutation_category_ALPHA ) == 2 ); + CHECK( dummy.get_instability_per_category( mutation_category_TROGLOBITE ) == 2 ); } } const int tries = 10000; int trogBads = 0; - int alphaBads = 0; + int alphaBads = 0; // 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. + // 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++; - } + alphaBads++; + } if( dummy.roll_bad_mutation( mutation_category_TROGLOBITE ) ) { - trogBads++; - } + 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 ); + CHECK( alphaBads > trogBads ); } } From 1cf71fcd99fae7250088b404e9bec653a9f676e7 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 23:51:23 -0700 Subject: [PATCH 13/25] declare missing variables --- tests/mutation_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index a0a5676b5d05b..f31ea0419adf0 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -30,6 +30,8 @@ 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_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" ); From 0c07672e0b0bd0ede4622f1f5354f0ff92ca0533 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Thu, 14 Mar 2024 23:52:45 -0700 Subject: [PATCH 14/25] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- tests/mutation_test.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index f31ea0419adf0..d4252c2be6ad0 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -658,25 +658,25 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" 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 ); + 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; + int alphaBads2 = 0; for( int i = 0; i < tries; i++ ) { if( dummy.roll_bad_mutation( mutation_category_ALPHA ) ) { - alphaBads2++; - } + alphaBads2++; + } if( dummy.roll_bad_mutation( mutation_category_TROGLOBITE ) ) { - trogBads2++; - } + 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 ); + CHECK( alphaBads2 > alphaBads ); + CHECK( trogBads2 > trogBads ); } } clear_avatar(); From 9153fb9e9d13034f7ed0a86ee6c48d11af75e0a2 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 00:04:15 -0700 Subject: [PATCH 15/25] fix more missing decs --- tests/mutation_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index d4252c2be6ad0..a5f4da3298de1 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -23,6 +23,7 @@ static const mutation_category_id mutation_category_HUMAN( "HUMAN" ); 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" ); @@ -30,6 +31,7 @@ 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" ); From 46df05cdb119eab961620ae14caaa7c1f8247c8d Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 02:10:44 -0700 Subject: [PATCH 16/25] fix issue with when/then clearing muts --- tests/mutation_test.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index a5f4da3298de1..c60c177df489c 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -601,6 +601,7 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } WHEN( "they then mutate Very Strong, which requires Strong and also belongs to both trees" ) { + dummy.set_mutation( trait_STR_UP ); 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 ); @@ -609,6 +610,8 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } WHEN( "they then mutate Quick, which belongs to Troglobite but not Alpha" ) { + dummy.set_mutation( trait_STR_UP ); + 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 ); @@ -617,6 +620,9 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } WHEN( "The character has Quick as a starting trait instead of a mutation" ) { + dummy.set_mutation( trait_STR_UP ); + dummy.set_mutation( trait_STR_UP_2 ); + dummy.set_mutation( trait_QUICK ); dummy.toggle_trait( trait_QUICK ); THEN( "Neither Alpha or Troglobite have their instability increased" ) { CHECK( dummy.get_instability_per_category( mutation_category_ALPHA ) == 2 ); @@ -625,6 +631,8 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } WHEN( "The character mutates Near Sighted, which is a \"bad\" mutation" ) { + dummy.set_mutation( trait_STR_UP ); + 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 ); @@ -636,6 +644,11 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" int trogBads = 0; int alphaBads = 0; + dummy.set_mutation( trait_STR_UP ); + 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. From 81b7f5bb26d26319b3959da7b0ebd49957173eb2 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 07:58:31 -0700 Subject: [PATCH 17/25] more test fixes --- tests/mutation_test.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index c60c177df489c..dcbe5d447f80e 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -46,7 +46,6 @@ static const trait_id trait_UGLY( "UGLY" ); 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 ); @@ -601,7 +600,6 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } WHEN( "they then mutate Very Strong, which requires Strong and also belongs to both trees" ) { - dummy.set_mutation( trait_STR_UP ); 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 ); @@ -610,7 +608,6 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } WHEN( "they then mutate Quick, which belongs to Troglobite but not Alpha" ) { - dummy.set_mutation( trait_STR_UP ); dummy.set_mutation( trait_STR_UP_2 ); dummy.set_mutation( trait_QUICK ); THEN( "Alpha increases instability by 2 and Troglobite increases by 1" ) { @@ -620,7 +617,6 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } WHEN( "The character has Quick as a starting trait instead of a mutation" ) { - dummy.set_mutation( trait_STR_UP ); dummy.set_mutation( trait_STR_UP_2 ); dummy.set_mutation( trait_QUICK ); dummy.toggle_trait( trait_QUICK ); @@ -631,7 +627,6 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } WHEN( "The character mutates Near Sighted, which is a \"bad\" mutation" ) { - dummy.set_mutation( trait_STR_UP ); 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" ) { @@ -644,7 +639,6 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" int trogBads = 0; int alphaBads = 0; - dummy.set_mutation( trait_STR_UP ); 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 ); From 2d09fcf6a4ae81594f2768a69ec3dc5a6e3b217a Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:24:59 -0700 Subject: [PATCH 18/25] add an extra sanity check --- tests/mutation_test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index dcbe5d447f80e..8cc2d18bc05bf 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -639,6 +639,7 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" int trogBads = 0; int alphaBads = 0; + 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 ); From f2fad37f3b60b87bc2f4c823358dfb8ec2bdc372 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:16:27 -0700 Subject: [PATCH 19/25] change robust to negate out of tree penalties --- src/mutation.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/mutation.cpp b/src/mutation.cpp index fa45ce4644e55..f2b40ab42556d 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -174,6 +174,7 @@ bool Character::has_base_trait( const trait_id &b ) const 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 @@ -191,7 +192,8 @@ int Character::get_instability_per_category( const mutation_category_id &categ ) const int height = mutation_height( mut ); // thus add 1 point if it's in the tree we mutate into, otherwise add 2 points - if( in_categ ) { + // 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; @@ -1027,15 +1029,10 @@ bool Character::mutation_ok( const trait_id &mutation, bool allow_good, bool all bool Character::roll_bad_mutation( const mutation_category_id &categ ) const { - // if we have Robust Genetics, it reduces our instability by this much - int ROBUST_FACTOR = 5; // 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 robust = has_trait( trait_ROBUST ); - if( robust ) { - MAX_BAD_CHANCE = 0.50; - } + bool ret = false; // the following values are, respectively, the total number of nonbad traits in a category and @@ -1047,9 +1044,6 @@ bool Character::roll_bad_mutation( const mutation_category_id &categ ) const add_msg_debug( debugmode::DF_MUTATION, "No mutations yet, no bad mutations allowed" ); return ret; } else { - if( robust ) { - insta_actual = std::max( 0, insta_actual - ROBUST_FACTOR ); - } // when we have a total instability score equal to the number of nonbad 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 ); From fbb12f6b4ee5ea1d8a34c3985d26ec354777b2b7 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:21:19 -0700 Subject: [PATCH 20/25] reword robust genetics to reflect new function --- data/json/mutations/mutations.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/json/mutations/mutations.json b/data/json/mutations/mutations.json index d6ff2c6dfad7b..23795a31734c1 100644 --- a/data/json/mutations/mutations.json +++ b/data/json/mutations/mutations.json @@ -1184,7 +1184,7 @@ "id": "ROBUST", "name": { "str": "Robust Genetics" }, "points": 3, - "description": "Your genome has rapidly adapted to the chaos of the Cataclysm, and the genetic damage caused by mutations will accumulate more slowly.", + "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" ] From 23d1beecb4b1e383fdb5b260cb48e80cc21f90e2 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:30:53 -0700 Subject: [PATCH 21/25] fix for toggle_trait / set_mutation conflict --- tests/mutation_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index 8cc2d18bc05bf..93247f232fe9f 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -618,8 +618,8 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" WHEN( "The character has Quick as a starting trait instead of a mutation" ) { dummy.set_mutation( trait_STR_UP_2 ); - dummy.set_mutation( trait_QUICK ); 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 ); From 305c014d17260a8daf333a2dddd3a02f45c0947b Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:32:28 -0700 Subject: [PATCH 22/25] documentation --- src/character.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/character.h b/src/character.h index 51d3db81ade53..84685f2de3053 100644 --- a/src/character.h +++ b/src/character.h @@ -1057,6 +1057,7 @@ class Character : public Creature, public visitable * 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; From e7787d5156fac6e7234ef1017a4c06daafd3eb2c Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:29:55 -0700 Subject: [PATCH 23/25] fix clang-tidy error --- src/mutation.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mutation.cpp b/src/mutation.cpp index f2b40ab42556d..9f0bbd81638ca 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -181,12 +181,10 @@ int Character::get_instability_per_category( const mutation_category_id &categ ) 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 - int cats = 0; for( const mutation_category_id &Ch_cat : mut.obj().category ) { if( Ch_cat == categ ) { in_categ = true; } - cats++; } const int height = mutation_height( mut ); @@ -1049,7 +1047,7 @@ bool Character::roll_bad_mutation( const mutation_category_id &categ ) const chance = std::min( chance, MAX_BAD_CHANCE ); ret = rng_float( 0, 1 ) < chance; add_msg_debug( debugmode::DF_MUTATION, - "%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.", + "%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; } From 19c72cac499a87e16133daa13444f6264becfd51 Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Sun, 24 Mar 2024 01:47:39 -0700 Subject: [PATCH 24/25] Apply Kamayana's style suggestions Co-authored-by: Kamayana --- src/mutation.cpp | 26 +++++++++++++------------- tests/mutation_test.cpp | 12 ++++++------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/mutation.cpp b/src/mutation.cpp index 9f0bbd81638ca..0cfe926176c64 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -175,12 +175,12 @@ int Character::get_instability_per_category( const mutation_category_id &categ ) { int mut_count = 0; bool robust = has_trait( trait_ROBUST ); - // for each and every trait we have... + // 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 + // 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 + // 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; @@ -189,8 +189,8 @@ int Character::get_instability_per_category( const mutation_category_id &categ ) 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 + // 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 { @@ -205,7 +205,7 @@ 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 + // 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 ) { @@ -1027,15 +1027,15 @@ bool Character::mutation_ok( const trait_id &mutation, bool allow_good, bool all bool Character::roll_bad_mutation( const mutation_category_id &categ ) const { - // we will never have worse odds than this no matter our instability + // 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 + // or, if we have Robust, cap it lower. bool ret = false; - // the following values are, respectively, the total number of nonbad traits in a category and + // 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. + // 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( insta_actual == 0 ) { @@ -1088,12 +1088,12 @@ 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 + // 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 + // If we picked bad, mutation can be bad or neutral. allow_bad = true; } else { - // Otherwise, can be good or neutral + // Otherwise, can be good or neutral. allow_good = true; } } else { diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index 93247f232fe9f..ebfabb5105fd3 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -568,14 +568,14 @@ TEST_CASE( "All_valid_mutations_can_be_purified", "[mutations][purifier]" ) } } -// 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. +// 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, +// 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. +// 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. @@ -664,7 +664,7 @@ TEST_CASE( "Chance_of_bad_mutations_vs_instability", "[mutations][instability]" } } - // then increase our instability and try again. + // Then, increase our instability and try again. dummy.set_mutation( trait_FELINE_EARS ); dummy.set_mutation( trait_EAGLEEYED ); dummy.set_mutation( trait_GOURMAND ); From ca0187deaa85f5e12a6e95e4299bf11c4fa9c2ee Mon Sep 17 00:00:00 2001 From: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> Date: Sun, 31 Mar 2024 23:11:38 -0700 Subject: [PATCH 25/25] Apply suggestions from code review Co-authored-by: Kamayana --- src/character.h | 22 +++++++++++----------- src/mutation.cpp | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/character.h b/src/character.h index 84685f2de3053..a372203d45213 100644 --- a/src/character.h +++ b/src/character.h @@ -1043,20 +1043,20 @@ class Character : public Creature, public visitable 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) + * 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) + * - 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. + * 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; diff --git a/src/mutation.cpp b/src/mutation.cpp index 0cfe926176c64..62ef29e4ce1f9 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -1042,7 +1042,7 @@ bool Character::roll_bad_mutation( const mutation_category_id &categ ) const add_msg_debug( debugmode::DF_MUTATION, "No mutations yet, no bad mutations allowed" ); return ret; } else { - // when we have a total instability score equal to the number of nonbad mutations in the tree, our odds of good/bad are 50/50 + // 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;