Skip to content

Commit

Permalink
Start menu: Selector for trait variants
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
anothersimulacrum committed Jun 15, 2024
1 parent 3a12119 commit 6412926
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 71 deletions.
6 changes: 4 additions & 2 deletions src/mutation.h
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,10 @@ std::vector<trait_id> get_mutations_in_types( const std::set<std::string> &ids )
std::vector<trait_id> get_mutations_in_type( const std::string &id );
bool mutation_is_in_category( const trait_id &mut, const mutation_category_id &cat );
std::vector<trait_and_var> 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 );
Expand Down
18 changes: 16 additions & 2 deletions src/mutation_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
Expand All @@ -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" ) ) {
Expand Down
176 changes: 110 additions & 66 deletions src/newcharacter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<trait_and_var> &to, const trait_id &trait )
static void add_trait( std::vector<trait_id> &to, const trait_id &trait )
{
if( trait->variants.empty() ) {
to.emplace_back( trait, "" );
return;
}
for( const std::pair<const std::string, mutation_variant> &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<int, 60> 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<const mutation_variant *> variants;
variants.reserve( cur_trait->variants.size() );
for( const std::pair<const std::string, mutation_variant> &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 )
Expand All @@ -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<std::vector<trait_and_var>, 3> vStartingTraits;
std::array<std::vector<trait_id>, 3> vStartingTraits;

for( const mutation_branch &traits_iter : mutation_branch::get_all() ) {
// Don't list blacklisted traits
Expand Down Expand Up @@ -1602,7 +1647,7 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool )
std::array<size_t, 3> traits_size;
bool recalc_traits = false;
// pointer for memory footprint reasons
std::array<std::vector<const trait_and_var *>, 3> sorted_traits;
std::array<std::vector<const trait_id *>, 3> sorted_traits;
std::array<scrollbar, 3> trait_sbs;
std::string filterstring;

Expand Down Expand Up @@ -1748,8 +1793,7 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool )
int end = start + static_cast<int>( 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;
Expand All @@ -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 );
}
Expand All @@ -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 ) ) {
Expand All @@ -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 &current = *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;
Expand Down Expand Up @@ -1874,12 +1906,12 @@ void set_traits( tab_manager &tabs, avatar &u, pool_type pool )
}

if( !filterstring.empty() ) {
for( std::vector<const trait_and_var *> &traits : sorted_traits ) {
for( std::vector<const trait_id *> &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() );
Expand Down Expand Up @@ -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<bionic_id> cbms_blocking_trait = bionics_cancelling_trait( u.prof->CBMs(), cur_trait );
const std::unordered_set<trait_id> 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<std::string> conflict_names;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<trait_and_var> 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 {
Expand Down
2 changes: 1 addition & 1 deletion src/player_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<trait_and_var> 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<bionic_grouping> bionicslist;
Expand Down

0 comments on commit 6412926

Please sign in to comment.