From 2835be49655188313c0a9604902c4027d7d4a971 Mon Sep 17 00:00:00 2001 From: anothersimulacrum Date: Fri, 14 Jun 2024 14:06:25 +0000 Subject: [PATCH] Start menu: Selector for trait variants Instead of listing all the trait variants individually, add a menu selector for the variants and display the "archetype" trait for them. This does some funky things with uilist, but this was the path of least resistance. It would be nice if uilist had a way to show a "selected" entry that wasn't the one the cursor was over. --- src/mutation.h | 6 +- src/mutation_data.cpp | 18 ++++- src/newcharacter.cpp | 176 +++++++++++++++++++++++++---------------- src/player_display.cpp | 2 +- 4 files changed, 131 insertions(+), 71 deletions(-) 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..afddfe9634a9a 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], + ( 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;