diff --git a/src/mutation.h b/src/mutation.h index a4573809bf5e7..200d2fff0b90e 100644 --- a/src/mutation.h +++ b/src/mutation.h @@ -549,8 +549,10 @@ std::vector get_mutations_in_types( const std::set &ids ) std::vector get_mutations_in_type( const std::string &id ); bool mutation_is_in_category( const trait_id &mut, const mutation_category_id &cat ); std::vector mutations_var_in_type( const std::string &id ); -bool trait_display_sort( const trait_and_var &a, const trait_and_var &b ) noexcept; -bool trait_display_nocolor_sort( const trait_and_var &a, const trait_and_var &b ) noexcept; +bool trait_var_display_sort( const trait_and_var &a, const trait_and_var &b ) noexcept; +bool trait_var_display_nocolor_sort( const trait_and_var &a, const trait_and_var &b ) noexcept; +bool trait_display_sort( const trait_id &a, const trait_id &b ) noexcept; +bool trait_display_nocolor_sort( const trait_id &a, const trait_id &b ) noexcept; bool are_conflicting_traits( const trait_id &trait_a, const trait_id &trait_b ); bool b_is_lower_trait_of_a( const trait_id &trait_a, const trait_id &trait_b ); diff --git a/src/mutation_data.cpp b/src/mutation_data.cpp index f36f66375bf47..6bd73e7b3da3c 100644 --- a/src/mutation_data.cpp +++ b/src/mutation_data.cpp @@ -882,7 +882,7 @@ void dream::load( const JsonObject &jsobj ) dreams.push_back( newdream ); } -bool trait_display_sort( const trait_and_var &a, const trait_and_var &b ) noexcept +bool trait_var_display_sort( const trait_and_var &a, const trait_and_var &b ) noexcept { auto trait_sort_key = []( const trait_and_var & t ) { return std::make_pair( -t.trait->get_display_color().to_int(), t.name() ); @@ -891,11 +891,25 @@ bool trait_display_sort( const trait_and_var &a, const trait_and_var &b ) noexce return localized_compare( trait_sort_key( a ), trait_sort_key( b ) ); } -bool trait_display_nocolor_sort( const trait_and_var &a, const trait_and_var &b ) noexcept +bool trait_display_sort( const trait_id &a, const trait_id &b ) noexcept +{ + auto trait_sort_key = []( const trait_id & t ) { + return std::make_pair( -t->get_display_color().to_int(), t->name() ); + }; + + return localized_compare( trait_sort_key( a ), trait_sort_key( b ) ); +} + +bool trait_var_display_nocolor_sort( const trait_and_var &a, const trait_and_var &b ) noexcept { return localized_compare( a.name(), b.name() ); } +bool trait_display_nocolor_sort( const trait_id &a, const trait_id &b ) noexcept +{ + return localized_compare( a->name(), b->name() ); +} + void mutation_branch::load_trait_blacklist( const JsonObject &jsobj ) { for( const std::string line : jsobj.get_array( "traits" ) ) { diff --git a/src/newcharacter.cpp b/src/newcharacter.cpp index 0d1732f5ab2e7..2ab15895c5dd5 100644 --- a/src/newcharacter.cpp +++ b/src/newcharacter.cpp @@ -1510,20 +1510,65 @@ void set_stats( tab_manager &tabs, avatar &u, pool_type pool ) static struct { bool sort_by_points = false; /** @related player */ - bool operator()( const trait_and_var *a, const trait_and_var *b ) { - return std::abs( a->trait->points ) > std::abs( b->trait->points ); + bool operator()( const trait_id *a, const trait_id *b ) { + return std::abs( ( *a )->points ) > std::abs( ( *b )->points ); } } traits_sorter; -static void add_trait( std::vector &to, const trait_id &trait ) +static void add_trait( std::vector &to, const trait_id &trait ) { - if( trait->variants.empty() ) { - to.emplace_back( trait, "" ); - return; - } - for( const std::pair &var : trait->variants ) { - to.emplace_back( trait, var.first ); + to.emplace_back( trait ); +} + +static const mutation_variant *variant_trait_selection_menu( const trait_id &cur_trait ) +{ + // Because the keys will change on each loop if I clear the entries, and + // if I don't clear the entries, the menu bugs out + static std::array keys = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p',/*q */'r', 's', 't',/*u */'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + }; + uilist menu; + const mutation_variant *ret = nullptr; + avatar &pc = get_avatar(); + std::vector variants; + variants.reserve( cur_trait->variants.size() ); + for( const std::pair &pr : cur_trait->variants ) { + if( pc.has_trait_variant( {cur_trait, pr.first} ) ) { + ret = &pr.second; + } + variants.emplace_back( &pr.second ); } + + menu.title = _( "Which trait?" ); + menu.desc_enabled = true; + menu.allow_cancel = false; + int idx; + do { + menu.entries.clear(); + idx = 0; + menu.addentry_desc( idx, true, 'u', ret != nullptr ? _( "Unselect" ) : + colorize( _( "Unselected" ), c_white ), + _( "Remove this trait." ) ); + ++idx; + for( const mutation_variant *var : variants ) { + std::string name = var->alt_name.translated(); + menu.addentry_desc( idx, true, keys[( idx - 1 ) % keys.size()], + ( ret && ret == var ) ? colorize( name, c_white ) : name, + var->alt_description.translated() ); + ++idx; + } + menu.addentry_desc( idx, true, 'q', _( "Done" ), _( "Exit menu." ) ); + menu.query(); + if( menu.ret == 0 ) { + ret = nullptr; + } else if( menu.ret < idx ) { + ret = variants[menu.ret - 1]; + } + } while( menu.ret != idx ); + return ret; } void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) @@ -1537,7 +1582,7 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) // 0 -> traits that take points ( positive traits ) // 1 -> traits that give points ( negative traits ) // 2 -> neutral traits ( facial hair, skin color, etc ) - std::array, 3> vStartingTraits; + std::array, 3> vStartingTraits; for( const mutation_branch &traits_iter : mutation_branch::get_all() ) { // Don't list blacklisted traits @@ -1602,7 +1647,7 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) std::array traits_size; bool recalc_traits = false; // pointer for memory footprint reasons - std::array, 3> sorted_traits; + std::array, 3> sorted_traits; std::array trait_sbs; std::string filterstring; @@ -1748,8 +1793,7 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) int end = start + static_cast( std::min( traits_size[iCurrentPage], iContentHeight ) ); for( int i = start; i < end; i++ ) { - const trait_and_var &cursor = *sorted_traits[iCurrentPage][i]; - const trait_id &cur_trait = cursor.trait; + const trait_id &cur_trait = *sorted_traits[iCurrentPage][i]; if( current == i && iCurrentPage == iCurWorkingPage ) { int points = cur_trait->points; bool negativeTrait = points < 0; @@ -1759,7 +1803,7 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) if( pool != pool_type::FREEFORM ) { mvwprintz( w, point( full_string_length + 3, 3 ), col_tr, n_gettext( "%s %s %d point", "%s %s %d points", points ), - cursor.name(), + cur_trait->name(), negativeTrait ? _( "earns" ) : _( "costs" ), points ); } @@ -1773,22 +1817,14 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) if( u.has_conflicting_trait( cur_trait ) ) { cLine = hilite( c_dark_gray ); } else if( u.has_trait( cur_trait ) ) { - if( !cur_trait->variants.empty() && !u.has_trait_variant( cursor ) ) { - cLine = hilite( c_dark_gray ); - } else { - cLine = hi_on; - } + cLine = hi_on; } } else { if( u.has_conflicting_trait( cur_trait ) || get_scenario()->is_forbidden_trait( cur_trait ) ) { cLine = c_dark_gray; } else if( u.has_trait( cur_trait ) ) { - if( !cur_trait->variants.empty() && !u.has_trait_variant( cursor ) ) { - cLine = c_dark_gray; - } else { - cLine = col_on_act; - } + cLine = col_on_act; } } } else if( u.has_trait( cur_trait ) ) { @@ -1806,44 +1842,40 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) // This list only clutters up the screen in screen reader mode } else { mvwprintz( w, opt_pos, cLine, - utf8_truncate( cursor.name(), page_width - 2 ) ); + utf8_truncate( cur_trait->name(), page_width - 2 ) ); } } if( details_recalc ) { std::string description; - const trait_and_var ¤t = *sorted_traits[iCurWorkingPage][iCurrentLine[iCurWorkingPage]]; + const trait_id &cur_trait = *sorted_traits[iCurWorkingPage][iCurrentLine[iCurWorkingPage]]; if( screen_reader_mode ) { /* Screen readers will skip over text that has not changed. Since the lists of traits are * alphabetical, this frequently results in letters/words being skipped. So, if the screen * reader is likely to skip over part of a trait name, we trick it into thinking things have * changed by shifting the text slightly. */ - if( !last_trait.empty() && last_trait[0] == current.name()[0] ) { - description = " " + current.name(); + if( !last_trait.empty() && last_trait[0] == cur_trait->name()[0] ) { + description = " " + cur_trait->name(); } else { - description = current.name(); + description = cur_trait->name(); } last_trait = description; std::string cur_trait_notes; - if( u.has_conflicting_trait( current.trait ) ) { + if( u.has_conflicting_trait( cur_trait ) ) { cur_trait_notes = _( "a conflicting trait is active" ); - } else if( u.has_trait( current.trait ) ) { - if( !current.trait->variants.empty() && !u.has_trait_variant( current ) ) { - cur_trait_notes = _( "a different variant of this trait is active" ); - } else { - cur_trait_notes = _( "active" ); - } + } else if( u.has_trait( cur_trait ) ) { + cur_trait_notes = _( "active" ); } if( !cur_trait_notes.empty() ) { description.append( string_format( " - %s", cur_trait_notes ) ); } - description.append( "\n" + current.desc() ); + description.append( "\n" + cur_trait->desc() ); } else { - description = current.desc(); + description = cur_trait->desc(); } details.set_text( colorize( description, col_tr ) ); details_recalc = false; @@ -1874,12 +1906,12 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) } if( !filterstring.empty() ) { - for( std::vector &traits : sorted_traits ) { + for( std::vector &traits : sorted_traits ) { const auto new_end_iter = std::remove_if( traits.begin(), traits.end(), - [&filterstring]( const trait_and_var * trait ) { - return !lcmatch( trait->name(), filterstring ); + [&filterstring]( const trait_id * trait ) { + return !lcmatch( ( *trait )->name(), filterstring ); } ); traits.erase( new_end_iter, traits.end() ); @@ -1946,39 +1978,41 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) // No additional action required } else if( action == "CONFIRM" ) { int inc_type = 0; - const trait_id cur_trait = sorted_traits[iCurWorkingPage][iCurrentLine[iCurWorkingPage]]->trait; - const std::string variant = sorted_traits[iCurWorkingPage][iCurrentLine[iCurWorkingPage]]->variant; + const trait_id cur_trait = *sorted_traits[iCurWorkingPage][iCurrentLine[iCurWorkingPage]]; const mutation_branch &mdata = cur_trait.obj(); + std::string variant; // Look through the profession bionics, and see if any of them conflict with this trait std::vector cbms_blocking_trait = bionics_cancelling_trait( u.prof->CBMs(), cur_trait ); const std::unordered_set conflicting_traits = u.get_conflicting_traits( cur_trait ); if( u.has_trait( cur_trait ) ) { + if( !cur_trait->variants.empty() ) { + const mutation_variant *rval = variant_trait_selection_menu( cur_trait ); + if( rval == nullptr ) { + inc_type = -1; + } else { + u.set_mut_variant( cur_trait, rval ); + } + } else { + inc_type = -1; - inc_type = -1; - - if( get_scenario()->is_locked_trait( cur_trait ) ) { - inc_type = 0; - popup( _( "Your scenario of %s prevents you from removing this trait." ), - get_scenario()->gender_appropriate_name( u.male ) ); - } else if( u.prof->is_locked_trait( cur_trait ) ) { - inc_type = 0; - popup( _( "Your profession of %s prevents you from removing this trait." ), - u.prof->gender_appropriate_name( u.male ) ); - } - for( const profession *hobbies : u.hobbies ) { - if( hobbies->is_locked_trait( cur_trait ) ) { + if( get_scenario()->is_locked_trait( cur_trait ) ) { + inc_type = 0; + popup( _( "Your scenario of %s prevents you from removing this trait." ), + get_scenario()->gender_appropriate_name( u.male ) ); + } else if( u.prof->is_locked_trait( cur_trait ) ) { inc_type = 0; - popup( _( "Your background of %s prevents you from removing this trait." ), - hobbies->gender_appropriate_name( u.male ) ); + popup( _( "Your profession of %s prevents you from removing this trait." ), + u.prof->gender_appropriate_name( u.male ) ); + } + for( const profession *hobbies : u.hobbies ) { + if( hobbies->is_locked_trait( cur_trait ) ) { + inc_type = 0; + popup( _( "Your background of %s prevents you from removing this trait." ), + hobbies->gender_appropriate_name( u.male ) ); + } } - } - // Switch variant - if( !u.has_trait_variant( trait_and_var( cur_trait, variant ) ) ) { - u.set_mut_variant( cur_trait, cur_trait->variant( variant ) ); - inc_type = 0; - details_recalc = true; } } else if( !conflicting_traits.empty() ) { std::vector conflict_names; @@ -2016,7 +2050,17 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool ) max_trait_points ); } else { - inc_type = 1; + if( !cur_trait->variants.empty() ) { + const mutation_variant *rval = variant_trait_selection_menu( cur_trait ); + if( rval != nullptr ) { + inc_type = 1; + variant = rval->id; + } else { + inc_type = 0; + } + } else { + inc_type = 1; + } } //inc_type is either -1 or 1, so we can just multiply by it to invert @@ -4059,7 +4103,7 @@ void set_description( tab_manager &tabs, avatar &you, const bool allow_reroll, werase( w_traits ); mvwprintz( w_traits, point_zero, COL_HEADER, _( "Traits: " ) ); std::vector current_traits = you.get_mutations_variants(); - std::sort( current_traits.begin(), current_traits.end(), trait_display_sort ); + std::sort( current_traits.begin(), current_traits.end(), trait_var_display_sort ); if( current_traits.empty() ) { wprintz( w_traits, c_light_red, _( "None!" ) ); } else { diff --git a/src/player_display.cpp b/src/player_display.cpp index 7d8f25e7b65cd..d2e267b4e1718 100644 --- a/src/player_display.cpp +++ b/src/player_display.cpp @@ -1628,7 +1628,7 @@ void Character::disp_info( bool customize_character ) const unsigned int proficiency_win_size_y_max = 1 + display_proficiencies().size(); std::vector traitslist = get_mutations_variants( false ); - std::sort( traitslist.begin(), traitslist.end(), trait_display_sort ); + std::sort( traitslist.begin(), traitslist.end(), trait_var_display_sort ); const unsigned int trait_win_size_y_max = 1 + traitslist.size(); std::vector bionicslist;