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

Disassembly menu: highlight filtered components, always show required & components #78737

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
161 changes: 137 additions & 24 deletions src/game_inventory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,49 +591,113 @@ class pickup_inventory_preset : public inventory_selector_preset
bool ignore_liquidcont;
};

std::function<bool( const std::string & )> basic_item_filter_only_disassembly( std::string filter )
{
size_t colon;
char flag = '\0';
if( ( colon = filter.find( ':' ) ) != std::string::npos ) {
if( colon >= 1 ) {
flag = filter[colon - 1];
filter = filter.substr( colon + 1 );
}
}
switch( flag ) {
// disassembled components
case 'd':
return [filter]( const std::string & component ) {
return lcmatch( component, filter );
};
default:
return []( const std::string & ) {
return false;
};
}
}

/**
* Take disassembly requests from filter, return OR function matching any disassembly components asked for in filter.
*/
std::function<bool( const std::string & )> filter_from_string( std::string filter )
{
// remove curly braces (they only get in the way)
if( filter.find( '{' ) != std::string::npos ) {
filter.erase( std::remove( filter.begin(), filter.end(), '{' ), filter.end() );
}
if( filter.find( '}' ) != std::string::npos ) {
filter.erase( std::remove( filter.begin(), filter.end(), '}' ), filter.end() );
}
if( filter.find( ',' ) != std::string::npos ) {
// functions which only one of which must return true
std::vector<std::function<bool( const std::string & )> > functions;
size_t comma = filter.find( ',' );
while( !filter.empty() ) {
const std::string &current_filter = trim( filter.substr( 0, comma ) );
if( !current_filter.empty() ) {
auto current_func = filter_from_string( current_filter );
functions.push_back( current_func );
}
if( comma != std::string::npos ) {
filter = trim( filter.substr( comma + 1 ) );
comma = filter.find( ',' );
} else {
break;
}
}

return [functions]( const std::string & it ) {
auto apply = [&]( const std::function<bool( const std::string & )> &func ) {
return func( it );
};
return std::any_of( functions.begin(), functions.end(), apply );;
};
}

return basic_item_filter_only_disassembly( filter );
}

class disassemble_inventory_preset : public inventory_selector_preset
{
public:
disassemble_inventory_preset( const Character &you, const inventory &inv ) : you( you ),
inv( inv ) {

check_components = true;
auto filter_func = []( const std::string & ) {
return false;
};

append_cell( [ this ]( const item_location & loc ) {
const requirement_data &req = get_recipe( loc ).disassembly_requirements();
if( req.is_empty() ) {
return std::string();
}
const item *i = loc.get_item();
std::vector<item_comp> components = i->get_uncraft_components();
std::sort( components.begin(), components.end() );
auto it = components.begin();
while( ( it = std::adjacent_find( it, components.end() ) ) != components.end() ) {
++( it->count );
components.erase( std::next( it ) );
}
return enumerate_as_string( components.begin(), components.end(),
[]( const decltype( components )::value_type & comps ) {
return comps.to_string();
} );
}, _( "YIELD" ) );
append_cell( highlight_filter_disassembly_components( filter_func ), _( "YIELD" ) );

append_cell( [ this ]( const item_location & loc ) {
return to_string_clipped( get_recipe( loc ).time_to_craft( get_player_character(),
recipe_time_flag::ignore_proficiencies ) );
return colorize( to_string_clipped( get_recipe( loc ).time_to_craft( get_player_character(),
recipe_time_flag::ignore_proficiencies ) ), c_light_gray );
}, _( "TIME" ) );

append_cell( [ this ]( const item_location & loc ) {
return colorize( _get_denial( loc ), c_red );
}, _( "CAN DISASSEMBLE" ) );
}

bool is_shown( const item_location &loc ) const override {
return !get_recipe( loc ).is_null();
}

std::string get_denial( const item_location &loc ) const override {
bool get_enabled( const item_location &loc ) const override {
const auto ret = you.can_disassemble( *loc, inv );
if( !ret.success() ) {
return ret.str();
return false;
}
return std::string();

item_location ancestor = loc;
while( ancestor.has_parent() ) {
ancestor = ancestor.parent_item();
}
return rl_dist( you.pos(), ancestor.position() ) <= 1;
}

void on_filter_change( const std::string &filter ) const override {
auto filter_func = filter_from_string( filter );
replace_cell( highlight_filter_disassembly_components( filter_func ), _( "YIELD" ) );
}

protected:
Expand All @@ -643,13 +707,62 @@ class disassemble_inventory_preset : public inventory_selector_preset

private:
const Character &you;
/// Tools etc. in crafters inventory to use for disassembly.
const inventory &inv;

std::string _get_denial( const item_location &loc ) const {
const auto ret = you.can_disassemble( *loc, inv );
if( !ret.success() ) {
return ret.str();
}

item_location ancestor = loc;
while( ancestor.has_parent() ) {
ancestor = ancestor.parent_item();
}
if( rl_dist( you.pos(), ancestor.position() ) > 1 ) {
return string_format( "%s is too far.",
colorize( direction_suffix( you.pos(), ancestor.position() ), c_red ) );
}

return std::string();
}

std::function<std::string( const item_location &loc )> highlight_filter_disassembly_components(
std::function<bool( const std::string & )> filter_func
) const {
// TODO bug: changing hiearchy `;` twice removes effect of filter
// TODO bug: changing hiearchy `;` doesn't apply effect of filter
return [ this, filter_func ]( const item_location & loc ) {
const requirement_data &req = get_recipe( loc ).disassembly_requirements();
if( req.is_empty() ) {
return std::string();
}
const item *i = loc.get_item();
std::vector<item_comp> components = i->get_uncraft_components();
std::sort( components.begin(), components.end() );
auto it = components.begin();
while( ( it = std::adjacent_find( it, components.end() ) ) != components.end() ) {
++( it->count );
components.erase( std::next( it ) );
}
// TODO this color doesn't work if the text is trimmed (contains …)
return colorize( enumerate_as_string( components.begin(), components.end(),
[&filter_func]( const decltype( components )::value_type & comps ) {
// if it matches, color it
const std::string ret = comps.to_string();
return colorize( ret, filter_func( ret ) ? c_light_red : c_white );
} ), c_light_gray );
};
}
};

item_location game_menus::inv::disassemble( Character &you )
{
// TODO register context for `T`ravel to
// TODO items 3W are visïble in disassembly menu, Is that ok since they are within crafting? probably not
return inv_internal( you, disassemble_inventory_preset( you, you.crafting_inventory() ),
_( "Disassemble item" ), 1,
_( "Disassemble item" ), 60,
_( "You don't have any items you could disassemble." ) );
}

Expand Down
44 changes: 41 additions & 3 deletions src/inventory_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -608,10 +608,10 @@ void inventory_entry::cache_denial( inventory_selector_preset const &preset ) co
{
if( !denial ) {
denial.emplace( preset.get_denial( *this ) );
enabled = denial->empty();
enabled = is_item() && preset.get_enabled( any_item() );
}
if( is_collation_header() ) {
collation_meta->enabled = denial->empty();
collation_meta->enabled = is_item() && preset.get_enabled( any_item() );
}
}

Expand Down Expand Up @@ -719,6 +719,8 @@ std::function<bool( const inventory_entry & )> inventory_selector_preset::get_fi
};
}

void inventory_selector_preset::on_filter_change( const std::string &/*filter*/ ) const {};

std::string inventory_selector_preset::get_caption( const inventory_entry &entry ) const
{
size_t count = entry.get_stack_size();
Expand Down Expand Up @@ -826,6 +828,33 @@ void inventory_selector_preset::append_cell( const
cells.emplace_back( func, title, stub );
}

// TODO should not be const
void inventory_selector_preset::replace_cell( const
std::function<std::string( const item_location & )> &func,
const std::string &title, const std::string &stub ) const
{
// Don't capture by reference here. The func should be able to die earlier than the object itself
replace_cell( std::function<std::string( const inventory_entry & )>( [ func ](
const inventory_entry & entry ) {
return func( entry.any_item() );
} ), title, stub );
}

void inventory_selector_preset::replace_cell( const
std::function<std::string( const inventory_entry & )> &func,
const std::string &title, const std::string &stub ) const
{
const auto iter = std::find_if( cells.begin(), cells.end(), [ &title ]( const cell_t &cell ) {
return cell.title == title;
} );
if( iter == cells.end() ) {
debugmsg( "Tried to replace a non duplicate cell \"%s\": ignored.", title.c_str() );
return;
}
*iter = {func, title, stub};
//reset_entry_cell_cache();
}

std::string inventory_selector_preset::cell_t::get_text( const inventory_entry &entry ) const
{
return replace_colors( func( entry ) );
Expand Down Expand Up @@ -1697,6 +1726,7 @@ void inventory_column::draw( const catacurses::window &win, const point &p,
}
}

// TODO: make denial empty and make extra column for denial
size_t count = denial.empty() ? cells.size() : 1;

for( size_t cell_index = 0; cell_index < count; ++cell_index ) {
Expand Down Expand Up @@ -1739,7 +1769,9 @@ void inventory_column::draw( const catacurses::window &win, const point &p,
trim_and_print( win, point( text_x - 1, yy ), 1, col,
stat ? "▶" : "▼" );
}
if( entry.is_item() && ( selected || !entry.is_selectable() ) ) {
/*if( entry.is_item() && !selected && !entry.is_selectable() && denial.empty() ) {
trim_and_print( win, point( text_x, yy ), text_width, entry_cell_cache.color, text );
} else*/ if( entry.is_item() && ( selected || ( !entry.is_selectable() && !denial.empty() ) ) ) {
trim_and_print( win, point( text_x, yy ), text_width, selected ? h_white : c_dark_gray,
remove_color_tags( text ) );
} else if( entry.is_item() && entry.highlight_as_parent ) {
Expand All @@ -1761,6 +1793,7 @@ void inventory_column::draw( const catacurses::window &win, const point &p,
}
entry.highlight_as_child = false;
} else {
// TODO this screws the colors of `,`, `, and`
trim_and_print( win, point( text_x, yy ), text_width, entry_cell_cache.color, text );
}
}
Expand Down Expand Up @@ -2726,7 +2759,9 @@ void inventory_selector::set_filter( const std::string &str )
filter = str;
for( inventory_column *const elem : columns ) {
elem->set_filter( filter );
// reset_entry_cell_cache();
}
preset.on_filter_change( filter );
}

std::string inventory_selector::get_filter() const
Expand Down Expand Up @@ -2961,6 +2996,7 @@ inventory_input inventory_selector::process_input( const std::string &action, in
if( res.entry != nullptr && res.entry->is_selectable() ) {
return res;
}
// todo denial popup
if( res.entry == nullptr && res.action == "SELECT" ) {
p.x++;
res.entry = find_entry_by_coordinate( p );
Expand Down Expand Up @@ -3077,6 +3113,8 @@ void inventory_selector::on_input( const inventory_input &input )

void inventory_selector::on_change( const inventory_entry &entry )
{
// TODO does not reset. Workaround during testing: use "Toggle language to English" option
entry.reset_entry_cell_cache();
for( inventory_column *&elem : columns ) {
elem->on_change( entry );
}
Expand Down
19 changes: 18 additions & 1 deletion src/inventory_ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,13 @@ class inventory_selector_preset
virtual std::string get_denial( const item_location & ) const {
return std::string();
}
/**
* Can this entry be selected?
* By default, it cannot be if get_denial returns an empty string.
*/
virtual bool get_enabled( const item_location &loc ) const {
return get_denial( loc ).empty();
}
/** Whether the first item is considered to go before the second. */
virtual bool sort_compare( const inventory_entry &lhs, const inventory_entry &rhs ) const;
virtual bool cat_sort_compare( const inventory_entry &lhs, const inventory_entry &rhs ) const;
Expand All @@ -260,6 +267,7 @@ class inventory_selector_preset

virtual std::function<bool( const inventory_entry & )> get_filter( const std::string &filter )
const;
virtual void on_filter_change( const std::string &filter ) const;

bool indent_entries() const {
return _indent_entries;
Expand All @@ -286,6 +294,15 @@ class inventory_selector_preset
void append_cell( const std::function<std::string( const inventory_entry & )> &func,
const std::string &title = std::string(),
const std::string &stub = std::string() );
/**
* Replace an existing cell.
*/
void replace_cell( const std::function<std::string( const item_location & )> &func,
const std::string &title = std::string(),
const std::string &stub = std::string() ) const;
void replace_cell( const std::function<std::string( const inventory_entry & )> &func,
const std::string &title = std::string(),
const std::string &stub = std::string() ) const;
bool check_components = false;

// whether to indent contained entries in the menu
Expand Down Expand Up @@ -313,7 +330,7 @@ class inventory_selector_preset
std::function<std::string( const inventory_entry & )> func;
};

std::vector<cell_t> cells;
mutable std::vector<cell_t> cells;
};

class inventory_holster_preset : public inventory_selector_preset
Expand Down
Loading