Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Redo instability: per-tree and static with mutatedness #72402

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1a5d510
adjust instability mechanics
anoobindisguise Mar 15, 2024
0513a2c
make mutation_height() a const
anoobindisguise Mar 15, 2024
02a4153
adjust instability mechanics
anoobindisguise Mar 15, 2024
2d39098
adjust instability mechanics
anoobindisguise Mar 15, 2024
05d6d7b
rewrite instability test
anoobindisguise Mar 15, 2024
3102656
obsolete instability
anoobindisguise Mar 15, 2024
322704b
obsolete instability
anoobindisguise Mar 15, 2024
15dfba6
obsolete instability
anoobindisguise Mar 15, 2024
a8d3728
untie Robust Genetics from instability
anoobindisguise Mar 15, 2024
d6bbbea
ck
anoobindisguise Mar 15, 2024
3889f77
add additional clarification
anoobindisguise Mar 15, 2024
639ecc5
Apply suggestions from code review
anoobindisguise Mar 15, 2024
1cf71fc
declare missing variables
anoobindisguise Mar 15, 2024
0c07672
Apply suggestions from code review
anoobindisguise Mar 15, 2024
9153fb9
fix more missing decs
anoobindisguise Mar 15, 2024
46df05c
fix issue with when/then clearing muts
anoobindisguise Mar 15, 2024
81b7f5b
more test fixes
anoobindisguise Mar 15, 2024
2d09fcf
add an extra sanity check
anoobindisguise Mar 15, 2024
f2fad37
change robust to negate out of tree penalties
anoobindisguise Mar 15, 2024
fbb12f6
reword robust genetics to reflect new function
anoobindisguise Mar 15, 2024
23d1bee
fix for toggle_trait / set_mutation conflict
anoobindisguise Mar 15, 2024
305c014
documentation
anoobindisguise Mar 15, 2024
e7787d5
fix clang-tidy error
anoobindisguise Mar 16, 2024
19c72ca
Apply Kamayana's style suggestions
anoobindisguise Mar 24, 2024
ca0187d
Apply suggestions from code review
anoobindisguise Apr 1, 2024
fd0ccb4
Merge branch 'CleverRaven:master' into anoobindisguise-instability-redux
anoobindisguise Apr 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions data/json/effects.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 2 additions & 3 deletions data/json/mutations/mutations.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 24 additions & 0 deletions data/json/obsoletion_and_migration_0.I/migration_mutation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
13 changes: 0 additions & 13 deletions data/json/vitamin.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@
[ "mutagen_troglobite", 1 ],
[ "mutagen_chiropteran", 1 ],
[ "mutagen_ursine", 1 ],
[ "instability", 1 ],
[ "mutant_toxin", 2 ]
]
},
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
25 changes: 22 additions & 3 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
/**
Expand Down Expand Up @@ -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<trait_id> &prospective_traits,
const mutation_category_id &cat, const bool &use_vitamins );
Expand All @@ -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 */
Expand Down
94 changes: 70 additions & 24 deletions src/mutation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
// 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<float>( insta_actual ) / static_cast<float>( 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;
}
}
Expand All @@ -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",
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 );
Expand Down
1 change: 1 addition & 0 deletions src/mutation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<string_id<mutation_branch>> traits, const trait_id &trait );
int get_total_nonbad_in_category( const mutation_category_id &categ );

enum class mutagen_technique : int {
consumed_mutagen,
Expand Down
Loading
Loading