void Character::unset_mutation( const trait_id &flag ) { const auto iter = my_mutations.find( flag ); if( iter != my_mutations.end() ) { my_mutations.erase( iter ); const mutation_branch *mut = &flag.obj(); cached_mutations.erase( std::remove( cached_mutations.begin(), cached_mutations.end(), mut ), cached_mutations.end() ); } else { return; } recalc_sight_limits(); reset_encumbrance(); }
bool player::mutation_ok( const trait_id &mutation, bool force_good, bool force_bad ) const { if (mutation_branch::trait_is_blacklisted(mutation)) { return false; } if (has_trait(mutation) || has_child_flag(mutation)) { // We already have this mutation or something that replaces it. return false; } const mutation_branch &mdata = mutation.obj(); if (force_bad && mdata.points > 0) { // This is a good mutation, and we're due for a bad one. return false; } if (force_good && mdata.points < 0) { // This is a bad mutation, and we're due for a good one. return false; } return true; }
void debug_menu::wishmutate( player *p ) { uimenu wmenu; int c = 0; for( auto &traits_iter : mutation_branch::get_all() ) { wmenu.addentry( -1, true, -2, "%s", traits_iter.second.name.c_str() ); wmenu.entries[ c ].extratxt.left = 1; wmenu.entries[ c ].extratxt.txt = ""; wmenu.entries[ c ].extratxt.color = c_ltgreen; if( p->has_trait( traits_iter.first ) ) { wmenu.entries[ c ].text_color = c_green; if( p->has_base_trait( traits_iter.first ) ) { wmenu.entries[ c ].extratxt.txt = "T"; } } c++; } wmenu.w_x = 0; wmenu.w_width = TERMX; // disabled due to foldstring crash // ( TERMX - getmaxx(w_terrain) - 30 > 24 ? getmaxx(w_terrain) : TERMX ); wmenu.pad_right = ( wmenu.w_width - 40 ); wmenu.return_invalid = true; wmenu.selected = uistate.wishmutate_selected; wish_mutate_callback cb; cb.p = p; wmenu.callback = &cb; do { wmenu.query(); if( wmenu.ret >= 0 ) { int rc = 0; const trait_id mstr = cb.vTraits[ wmenu.ret ]; const auto &mdata = mstr.obj(); bool threshold = mdata.threshold; bool profession = mdata.profession; //Manual override for the threshold-gaining if( threshold || profession ) { if( p->has_trait( mstr ) ) { do { p->remove_mutation( mstr ); rc++; } while( p->has_trait( mstr ) && rc < 10 ); } else { do { p->set_mutation( mstr ); rc++; } while( !p->has_trait( mstr ) && rc < 10 ); } } else if( p->has_trait( mstr ) ) { do { p->remove_mutation( mstr ); rc++; } while( p->has_trait( mstr ) && rc < 10 ); } else { do { p->mutate_towards( mstr ); rc++; } while( !p->has_trait( mstr ) && rc < 10 ); } cb.msg = string_format( _( "%s Mutation changes: %d" ), mstr.c_str(), rc ); uistate.wishmutate_selected = wmenu.ret; if( rc != 0 ) { for( size_t i = 0; i < cb.vTraits.size(); i++ ) { wmenu.entries[ i ].extratxt.txt = ""; if( p->has_trait( cb.vTraits[ i ] ) ) { wmenu.entries[ i ].text_color = c_green; cb.pTraits[ cb.vTraits[ i ] ] = true; if( p->has_base_trait( cb.vTraits[ i ] ) ) { wmenu.entries[ i ].extratxt.txt = "T"; } } else { wmenu.entries[ i ].text_color = wmenu.text_color; cb.pTraits[ cb.vTraits[ i ] ] = false; } } } } } while( wmenu.keypress != 'q' && wmenu.keypress != KEY_ESCAPE && wmenu.keypress != ' ' ); }
void player::remove_mutation( const trait_id &mut ) { const auto &mdata = mut.obj(); // Check if there's a prerequisite we should shrink back into trait_id replacing = trait_id::NULL_ID(); std::vector<trait_id> originals = mdata.prereqs; for (size_t i = 0; !replacing && i < originals.size(); i++) { trait_id pre = originals[i]; const auto &p = pre.obj(); for (size_t j = 0; !replacing && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing = pre; } } } trait_id replacing2 = trait_id::NULL_ID(); std::vector<trait_id> originals2 = mdata.prereqs2; for (size_t i = 0; !replacing2 && i < originals2.size(); i++) { trait_id pre2 = originals2[i]; const auto &p = pre2.obj(); for (size_t j = 0; !replacing2 && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing2 = pre2; } } } // See if this mutation is canceled by a base trait //Only if there's no prerequisite to shrink to, thus we're at the bottom of the trait line if( !replacing ) { //Check each mutation until we reach the end or find a trait to revert to for( auto &iter : mutation_branch::get_all() ) { //See if it's in our list of base traits but not active if (has_base_trait(iter.first) && !has_trait(iter.first)) { //See if that base trait cancels the mutation we are using std::vector<trait_id> traitcheck = iter.second.cancels; if (!traitcheck.empty()) { for (size_t j = 0; !replacing && j < traitcheck.size(); j++) { if (traitcheck[j] == mut) { replacing = (iter.first); } } } } if( replacing ) { break; } } } // Duplicated for prereq2 if( !replacing2 ) { //Check each mutation until we reach the end or find a trait to revert to for( auto &iter : mutation_branch::get_all() ) { //See if it's in our list of base traits but not active if (has_base_trait(iter.first) && !has_trait(iter.first)) { //See if that base trait cancels the mutation we are using std::vector<trait_id> traitcheck = iter.second.cancels; if (!traitcheck.empty()) { for (size_t j = 0; !replacing2 && j < traitcheck.size(); j++) { if (traitcheck[j] == mut && (iter.first) != replacing) { replacing2 = (iter.first); } } } } if( replacing2 ) { break; } } } // make sure we don't toggle a mutation or trait twice, or it will cancel itself out. if(replacing == replacing2) { replacing2 = trait_id::NULL_ID(); } // This should revert back to a removed base trait rather than simply removing the mutation unset_mutation(mut); bool mutation_replaced = false; game_message_type rating; if( replacing ) { const auto &replace_mdata = replacing.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points > 0) { rating = m_good; } else if(mdata.points - replace_mdata.points > 0) { rating = m_bad; } else { rating = m_neutral; } add_msg_player_or_npc( rating, _("Your %1$s mutation turns into %2$s."), _("<npcname>'s %1$s mutation turns into %2$s."), mdata.name.c_str(), replace_mdata.name.c_str() ); set_mutation(replacing); mutation_loss_effect(mut); mutation_effect(replacing); mutation_replaced = true; } if( replacing2 ) { const auto &replace_mdata = replacing2.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points > 0) { rating = m_good; } else if(mdata.points - replace_mdata.points > 0) { rating = m_bad; } else { rating = m_neutral; } add_msg_player_or_npc( rating, _("Your %1$s mutation turns into %2$s."), _("<npcname>'s %1$s mutation turns into %2$s."), mdata.name.c_str(), replace_mdata.name.c_str() ); set_mutation(replacing2); mutation_loss_effect(mut); mutation_effect(replacing2); mutation_replaced = true; } if(!mutation_replaced) { if(mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points > 0) { rating = m_bad; } else if(mdata.points < 0) { rating = m_good; } else { rating = m_neutral; } add_msg_player_or_npc( rating, _("You lose your %s mutation."), _("<npcname> loses their %s mutation."), mdata.name.c_str() ); mutation_loss_effect(mut); } set_highest_cat_level(); drench_mut_calc(); }
bool player::mutate_towards( const trait_id &mut ) { if (has_child_flag(mut)) { remove_child_flag(mut); return true; } const mutation_branch &mdata = mut.obj(); bool has_prereqs = false; bool prereq1 = false; bool prereq2 = false; std::vector<trait_id> canceltrait; std::vector<trait_id> prereq = mdata.prereqs; std::vector<trait_id> prereqs2 = mdata.prereqs2; std::vector<trait_id> cancel = mdata.cancels; for (size_t i = 0; i < cancel.size(); i++) { if (!has_trait( cancel[i] )) { cancel.erase(cancel.begin() + i); i--; } else if (has_base_trait( cancel[i] )) { //If we have the trait, but it's a base trait, don't allow it to be removed normally canceltrait.push_back( cancel[i]); cancel.erase(cancel.begin() + i); i--; } } for (size_t i = 0; i < cancel.size(); i++) { if (!cancel.empty()) { trait_id removed = cancel[i]; remove_mutation(removed); cancel.erase(cancel.begin() + i); i--; // This checks for cases where one trait knocks out several others // Probably a better way, but gets it Fixed Now--KA101 return mutate_towards(mut); } } for (size_t i = 0; (!prereq1) && i < prereq.size(); i++) { if (has_trait(prereq[i])) { prereq1 = true; } } for (size_t i = 0; (!prereq2) && i < prereqs2.size(); i++) { if (has_trait(prereqs2[i])) { prereq2 = true; } } if (prereq1 && prereq2) { has_prereqs = true; } if (!has_prereqs && (!prereq.empty() || !prereqs2.empty())) { if (!prereq1 && !prereq.empty()) { return mutate_towards( random_entry( prereq ) ); } else if (!prereq2 && !prereqs2.empty()) { return mutate_towards( random_entry( prereqs2 ) ); } } // Check for threshold mutation, if needed bool threshold = mdata.threshold; bool profession = mdata.profession; bool has_threshreq = false; std::vector<trait_id> threshreq = mdata.threshreq; // It shouldn't pick a Threshold anyway--they're supposed to be non-Valid // and aren't categorized. This can happen if someone makes a threshold mutation into a prerequisite. if (threshold) { add_msg_if_player(_("You feel something straining deep inside you, yearning to be free...")); return false; } if (profession) { // Profession picks fail silently return false; } for (size_t i = 0; !has_threshreq && i < threshreq.size(); i++) { if (has_trait(threshreq[i])) { has_threshreq = true; } } // No crossing The Threshold by simply not having it if (!has_threshreq && !threshreq.empty()) { add_msg_if_player(_("You feel something straining deep inside you, yearning to be free...")); return false; } // Check if one of the prerequisites that we have TURNS INTO this one trait_id replacing = trait_id::NULL_ID(); prereq = mdata.prereqs; // Reset it for( auto &elem : prereq ) { if( has_trait( elem ) ) { trait_id pre = elem; const auto &p = pre.obj(); for (size_t j = 0; !replacing && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing = pre; } } } } // Loop through again for prereqs2 trait_id replacing2 = trait_id::NULL_ID(); prereq = mdata.prereqs2; // Reset it for( auto &elem : prereq ) { if( has_trait( elem ) ) { trait_id pre2 = elem; const auto &p = pre2.obj(); for (size_t j = 0; !replacing2 && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing2 = pre2; } } } } set_mutation(mut); bool mutation_replaced = false; game_message_type rating; if( replacing ) { const auto &replace_mdata = replacing.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points < 0) { rating = m_good; } else if(mdata.points - replace_mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } // TODO: Limit this to visible mutations // TODO: In case invisible mutation turns into visible or vice versa // print only the visible mutation appearing/disappearing add_msg_player_or_npc(rating, _("Your %1$s mutation turns into %2$s!"), _("<npcname>'s %1$s mutation turns into %2$s!"), replace_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), replace_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(replacing); mutation_loss_effect(replacing); mutation_effect(mut); mutation_replaced = true; } if( replacing2 ) { const auto &replace_mdata = replacing2.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points < 0) { rating = m_good; } else if(mdata.points - replace_mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } add_msg_player_or_npc(rating, _("Your %1$s mutation turns into %2$s!"), _("<npcname>'s %1$s mutation turns into %2$s!"), replace_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), replace_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(replacing2); mutation_loss_effect(replacing2); mutation_effect(mut); mutation_replaced = true; } for (size_t i = 0; i < canceltrait.size(); i++) { const auto &cancel_mdata = canceltrait[i].obj(); if(mdata.mixed_effect || cancel_mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points < cancel_mdata.points) { rating = m_bad; } else if(mdata.points > cancel_mdata.points) { rating = m_good; } else if(mdata.points == cancel_mdata.points) { rating = m_neutral; } else { rating = m_mixed; } // If this new mutation cancels a base trait, remove it and add the mutation at the same time add_msg_player_or_npc( rating, _("Your innate %1$s trait turns into %2$s!"), _("<npcname>'s innate %1$s trait turns into %2$s!"), cancel_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), cancel_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(canceltrait[i]); mutation_loss_effect(canceltrait[i]); mutation_effect(mut); mutation_replaced = true; } if (!mutation_replaced) { if(mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points > 0) { rating = m_good; } else if(mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } // TODO: Limit to visible mutations add_msg_player_or_npc( rating, _("You gain a mutation called %s!"), _("<npcname> gains a mutation called %s!"), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "Gained the mutation '%s'."), pgettext("memorial_female", "Gained the mutation '%s'."), mdata.name.c_str()); mutation_effect(mut); } set_highest_cat_level(); drench_mut_calc(); return true; }
void player::activate_mutation( const trait_id &mut ) { const mutation_branch &mdata = mut.obj(); auto &tdata = my_mutations[mut]; int cost = mdata.cost; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. if ((mdata.hunger && get_hunger() >= 700) || (mdata.thirst && get_thirst() >= 260) || (mdata.fatigue && get_fatigue() >= EXHAUSTED)) { // Insufficient Foo to *maintain* operation is handled in player::suffer add_msg_if_player(m_warning, _("You feel like using your %s would kill you!"), mdata.name.c_str()); return; } if (tdata.powered && tdata.charge > 0) { // Already-on units just lose a bit of charge tdata.charge--; } else { // Not-on units, or those with zero charge, have to pay the power cost if (mdata.cooldown > 0) { tdata.charge = mdata.cooldown - 1; } if (mdata.hunger){ mod_hunger(cost); } if (mdata.thirst){ mod_thirst(cost); } if (mdata.fatigue){ mod_fatigue(cost); } tdata.powered = true; // Handle stat changes from activation apply_mods(mut, true); recalc_sight_limits(); } if( mut == trait_WEB_WEAVER ) { g->m.add_field( pos(), fd_web, 1 ); add_msg_if_player(_("You start spinning web with your spinnerets!")); } else if (mut == "BURROW"){ if( is_underwater() ) { add_msg_if_player(m_info, _("You can't do that while underwater.")); tdata.powered = false; return; } tripoint dirp; if (!choose_adjacent(_("Burrow where?"), dirp)) { tdata.powered = false; return; } if( dirp == pos() ) { add_msg_if_player(_("You've got places to go and critters to beat.")); add_msg_if_player(_("Let the lesser folks eat their hearts out.")); tdata.powered = false; return; } time_duration time_to_do = 0_turns; if (g->m.is_bashable(dirp) && g->m.has_flag("SUPPORTS_ROOF", dirp) && g->m.ter(dirp) != t_tree) { // Being better-adapted to the task means that skillful Survivors can do it almost twice as fast. time_to_do = 30_minutes; } else if (g->m.move_cost(dirp) == 2 && g->get_levz() == 0 && g->m.ter(dirp) != t_dirt && g->m.ter(dirp) != t_grass) { time_to_do = 10_minutes; } else { add_msg_if_player(m_info, _("You can't burrow there.")); tdata.powered = false; return; } assign_activity( activity_id( "ACT_BURROW" ), to_moves<int>( time_to_do ), -1, 0 ); activity.placement = dirp; add_msg_if_player(_("You tear into the %s with your teeth and claws."), g->m.tername(dirp).c_str()); tdata.powered = false; return; // handled when the activity finishes } else if( mut == trait_SLIMESPAWNER ) { std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if (g->is_empty(dest)) { valid.push_back( dest ); } } // Oops, no room to divide! if( valid.empty() ) { add_msg_if_player(m_bad, _("You focus, but are too hemmed in to birth a new slimespring!")); tdata.powered = false; return; } add_msg_if_player(m_good, _("You focus, and with a pleasant splitting feeling, birth a new slimespring!")); int numslime = 1; for (int i = 0; i < numslime && !valid.empty(); i++) { const tripoint target = random_entry_removed( valid ); if( monster * const slime = g->summon_mon( mtype_id( "mon_player_blob" ), target ) ) { slime->friendly = -1; } } if (one_in(3)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("wow! you look just like me! we should look out for each other!")); } else if (one_in(2)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("come on, big me, let's go!")); } else { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("we're a team, we've got this!")); } tdata.powered = false; return; } else if( mut == trait_NAUSEA || mut == trait_VOMITOUS ) { vomit(); tdata.powered = false; return; } else if( mut == trait_M_FERTILE ) { spores(); tdata.powered = false; return; } else if( mut == trait_M_BLOOM ) { blossoms(); tdata.powered = false; return; } else if( mut == trait_SELFAWARE ) { print_health(); tdata.powered = false; return; } else if( !mdata.spawn_item.empty() ) { item tmpitem( mdata.spawn_item ); i_add_or_drop( tmpitem ); add_msg_if_player( _( mdata.spawn_item_message.c_str() ) ); tdata.powered = false; return; } }
void Character::mutation_loss_effect( const trait_id &mut ) { if( mut == "GLASSJAW" ) { recalc_hp(); } else if (mut == trait_STR_ALPHA) { ///\EFFECT_STR_MAX determines penalty from STR mutation loss if (str_max == 18) { str_max = 15; } else if (str_max == 15) { str_max = 8; } else if (str_max == 11) { str_max = 7; } else { str_max = 4; } recalc_hp(); } else if (mut == trait_DEX_ALPHA) { ///\EFFECT_DEX_MAX determines penalty from DEX mutation loss if (dex_max == 18) { dex_max = 15; } else if (dex_max == 15) { dex_max = 8; } else if (dex_max == 11) { dex_max = 7; } else { dex_max = 4; } } else if (mut == trait_INT_ALPHA) { ///\EFFECT_INT_MAX determines penalty from INT mutation loss if (int_max == 18) { int_max = 15; } else if (int_max == 15) { int_max = 8; } else if (int_max == 11) { int_max = 7; } else { int_max = 4; } } else if (mut == trait_INT_SLIME) { int_max /= 2; // In case you have a freak accident with the debug menu ;-) } else if (mut == trait_PER_ALPHA) { ///\EFFECT_PER_MAX determines penalty from PER mutation loss if (per_max == 18) { per_max = 15; } else if (per_max == 15) { per_max = 8; } else if (per_max == 11) { per_max = 7; } else { per_max = 4; } } else { apply_mods(mut, false); } const auto &branch = mut.obj(); if( branch.hp_modifier != 0.0f || branch.hp_modifier_secondary != 0.0f || branch.hp_adjustment != 0.0f ) { recalc_hp(); } on_mutation_loss( mut ); }
void Character::mutation_effect( const trait_id &mut ) { if( mut == "GLASSJAW" ) { recalc_hp(); } else if (mut == trait_STR_ALPHA) { ///\EFFECT_STR_MAX determines bonus from STR mutation if (str_max <= 6) { str_max = 8; } else if (str_max <= 7) { str_max = 11; } else if (str_max <= 14) { str_max = 15; } else { str_max = 18; } recalc_hp(); } else if (mut == trait_DEX_ALPHA) { ///\EFFECT_DEX_MAX determines bonus from DEX mutation if (dex_max <= 6) { dex_max = 8; } else if (dex_max <= 7) { dex_max = 11; } else if (dex_max <= 14) { dex_max = 15; } else { dex_max = 18; } } else if (mut == trait_INT_ALPHA) { ///\EFFECT_INT_MAX determines bonus from INT mutation if (int_max <= 6) { int_max = 8; } else if (int_max <= 7) { int_max = 11; } else if (int_max <= 14) { int_max = 15; } else { int_max = 18; } } else if (mut == trait_INT_SLIME) { int_max *= 2; // Now, can you keep it? :-) } else if (mut == trait_PER_ALPHA) { ///\EFFECT_PER_MAX determines bonus from PER mutation if (per_max <= 6) { per_max = 8; } else if (per_max <= 7) { per_max = 11; } else if (per_max <= 14) { per_max = 15; } else { per_max = 18; } } else { apply_mods(mut, true); } const auto &branch = mut.obj(); if( branch.hp_modifier != 0.0f || branch.hp_modifier_secondary != 0.0f || branch.hp_adjustment != 0.0f ) { recalc_hp(); } remove_worn_items_with( [&]( item& armor ) { static const std::string mutation_safe = "OVERSIZE"; if( armor.has_flag( mutation_safe ) ) { return false; } if( !branch.conflicts_with_item( armor ) ) { return false; } if( branch.destroys_gear ) { add_msg_player_or_npc( m_bad, _("Your %s is destroyed!"), _("<npcname>'s %s is destroyed!"), armor.tname().c_str() ); for( item& remain : armor.contents ) { g->m.add_item_or_charges( pos(), remain ); } } else { add_msg_player_or_npc( m_bad, _("Your %s is pushed off!"), _("<npcname>'s %s is pushed off!"), armor.tname().c_str() ); g->m.add_item_or_charges( pos(), armor ); } return true; } ); if( branch.starts_active ) { my_mutations[mut].powered = true; } on_mutation_gain( mut ); }