Skip to content

Commit

Permalink
Allow player to select from deduped requirements
Browse files Browse the repository at this point in the history
Previously, when deduped_requirements_data contained multiple feasible
selections of components, the game would always arbitrarily pick the
first one, meaning that the player might consume items they had not
intended to.

Now, instead, offer the choice to the player in this situation.  This is
done via a uilist dialogue.  To trim down the amount rendered in the
dialogue, I added a new feature to requirements to format a requirements
list while only showing those options which are available (the green
ones) and use that here.

As a side-effect, the player can now cancel the choice, so all the
places calling this function now need to deal with the potential failure
to make a choice.
  • Loading branch information
jbytheway committed Jan 7, 2020
1 parent e149156 commit dc72640
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 30 deletions.
10 changes: 7 additions & 3 deletions src/basecamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -690,12 +690,16 @@ basecamp_action_components::basecamp_action_components(
bool basecamp_action_components::choose_components()
{
const auto filter = is_crafting_component;
const requirement_data &req = making_.deduped_requirements().select_alternative( g->u, filter );
const requirement_data *req =
making_.deduped_requirements().select_alternative( g->u, base_._inv, filter, batch_size_ );
if( !req ) {
return false;
}
if( !item_selections_.empty() || !tool_selections_.empty() ) {
debugmsg( "Reused basecamp_action_components" );
return false;
}
for( const auto &it : req.get_components() ) {
for( const auto &it : req->get_components() ) {
comp_selection<item_comp> is =
g->u.select_item_component( it, batch_size_, base_._inv, true, filter,
!base_.by_radio );
Expand All @@ -705,7 +709,7 @@ bool basecamp_action_components::choose_components()
item_selections_.push_back( is );
}
// this may consume pseudo-resources from fake items
for( const auto &it : req.get_tools() ) {
for( const auto &it : req->get_tools() ) {
comp_selection<tool_comp> ts =
g->u.select_tool_component( it, batch_size_, base_._inv, DEFAULT_HOTKEYS, true,
!base_.by_radio );
Expand Down
9 changes: 6 additions & 3 deletions src/craft_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,13 @@ void craft_command::execute( const tripoint &new_loc )

item_selections.clear();
const auto filter = rec->get_component_filter( flags );
const requirement_data &needs = rec->deduped_requirements().select_alternative(
const requirement_data *needs = rec->deduped_requirements().select_alternative(
*crafter, filter, batch_size, craft_flags::start_only );
if( !needs ) {
return;
}

for( const auto &it : needs.get_components() ) {
for( const auto &it : needs->get_components() ) {
comp_selection<item_comp> is =
crafter->select_item_component( it, batch_size, map_inv, true, filter );
if( is.use_from == cancel ) {
Expand All @@ -158,7 +161,7 @@ void craft_command::execute( const tripoint &new_loc )
}

tool_selections.clear();
for( const auto &it : needs.get_tools() ) {
for( const auto &it : needs->get_tools() ) {
comp_selection<tool_comp> ts = crafter->select_tool_component(
it, batch_size, map_inv, DEFAULT_HOTKEYS, true, true, []( int charges ) {
return charges / 20 + charges % 20;
Expand Down
35 changes: 30 additions & 5 deletions src/crafting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1321,13 +1321,38 @@ bool player::can_continue_craft( item &craft )

return true;
}
const requirement_data &player::select_requirements(
const std::vector<const requirement_data *> &alternatives, int /*batch*/, const inventory &,
const std::function<bool( const item & )> &/*filter*/ ) const
const requirement_data *player::select_requirements(
const std::vector<const requirement_data *> &alternatives, int batch, const inventory &inv,
const std::function<bool( const item & )> &filter ) const
{
assert( !alternatives.empty() );
// TODO: when this is the avatar, offer the choice to the player
return *alternatives.front();
if( alternatives.size() == 1 || !is_avatar() ) {
return alternatives.front();
}

std::vector<std::string> descriptions;

uilist menu;

for( const requirement_data *req : alternatives ) {
// Write with a large width and then just re-join the lines, because
// uilist does its own wrapping and we want to rely on that.
std::vector<std::string> component_lines =
req->get_folded_components_list( TERMX - 4, c_light_gray, inv, filter, batch, "",
requirement_display_flags::no_unavailable );
menu.addentry_desc( "", join( component_lines, "\n" ) );
}

menu.allow_cancel = true;
menu.desc_enabled = true;
menu.title = _( "Use which selection of components?" );
menu.query();

if( menu.ret < 0 || static_cast<size_t>( menu.ret ) >= alternatives.size() ) {
return nullptr;
}

return alternatives[menu.ret];
}

/* selection of component if a recipe requirement has multiple options (e.g. 'duct tap' or 'welder') */
Expand Down
10 changes: 6 additions & 4 deletions src/crafting_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,13 @@ const recipe *select_crafting_recipe( int &batch_size )
auto all_items_filter = r->get_component_filter( recipe_filter_flags::none );
auto no_rotten_filter = r->get_component_filter( recipe_filter_flags::no_rotten );
const deduped_requirement_data &req = r->deduped_requirements();
can_craft = req.can_make_with_inventory( inv, all_items_filter, batch_size );
can_craft_non_rotten = req.can_make_with_inventory( inv, no_rotten_filter, batch_size );
can_craft = req.can_make_with_inventory(
inv, all_items_filter, batch_size, craft_flags::start_only );
can_craft_non_rotten = req.can_make_with_inventory(
inv, no_rotten_filter, batch_size, craft_flags::start_only );
const requirement_data &simple_req = r->simple_requirements();
apparently_craftable =
simple_req.can_make_with_inventory( inv, all_items_filter, batch_size );
apparently_craftable = simple_req.can_make_with_inventory(
inv, all_items_filter, batch_size, craft_flags::start_only );
}
bool can_craft;
bool can_craft_non_rotten;
Expand Down
8 changes: 6 additions & 2 deletions src/iuse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8718,9 +8718,13 @@ int iuse::multicooker( player *p, item *it, bool t, const tripoint &pos )
}

const auto filter = is_crafting_component;
const requirement_data &reqs =
const requirement_data *reqs =
meal->deduped_requirements().select_alternative( *p, filter );
for( auto it : reqs.get_components() ) {
if( !reqs ) {
return 0;
}

for( auto it : reqs->get_components() ) {
p->consume_items( it, 1, filter );
}

Expand Down
2 changes: 1 addition & 1 deletion src/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ class player : public Character
const inventory &crafting_inventory( bool clear_path );
const inventory &crafting_inventory( const tripoint &src_pos = tripoint_zero,
int radius = PICKUP_RANGE, bool clear_path = true );
const requirement_data &select_requirements(
const requirement_data *select_requirements(
const std::vector<const requirement_data *> &, int batch, const inventory &,
const std::function<bool( const item & )> &filter ) const;
comp_selection<item_comp>
Expand Down
17 changes: 11 additions & 6 deletions src/requirements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ void requirement_data::reset()

std::vector<std::string> requirement_data::get_folded_components_list( int width, nc_color col,
const inventory &crafting_inv, const std::function<bool( const item & )> &filter, int batch,
std::string hilite ) const
std::string hilite, requirement_display_flags flags ) const
{
std::vector<std::string> out_buffer;
if( components.empty() ) {
Expand All @@ -489,7 +489,7 @@ std::vector<std::string> requirement_data::get_folded_components_list( int width
out_buffer.push_back( colorize( _( "Components required:" ), col ) );

std::vector<std::string> folded_buffer =
get_folded_list( width, crafting_inv, filter, components, batch, hilite );
get_folded_list( width, crafting_inv, filter, components, batch, hilite, flags );
out_buffer.insert( out_buffer.end(), folded_buffer.begin(), folded_buffer.end() );

return out_buffer;
Expand All @@ -498,11 +498,14 @@ std::vector<std::string> requirement_data::get_folded_components_list( int width
template<typename T>
std::vector<std::string> requirement_data::get_folded_list( int width,
const inventory &crafting_inv, const std::function<bool( const item & )> &filter,
const std::vector< std::vector<T> > &objs, int batch, const std::string &hilite ) const
const std::vector< std::vector<T> > &objs, int batch, const std::string &hilite,
requirement_display_flags flags ) const
{
// hack: ensure 'cached' availability is up to date
can_make_with_inventory( crafting_inv, filter );

bool no_unavailable = static_cast<bool>( flags & requirement_display_flags::no_unavailable );

std::vector<std::string> out_buffer;
for( const auto &comp_list : objs ) {
const bool has_one = any_marked_available( comp_list );
Expand Down Expand Up @@ -530,7 +533,9 @@ std::vector<std::string> requirement_data::get_folded_list( int width,
color = yellow_background( color );
}

list_as_string.push_back( colorize( text, color ) );
if( !no_unavailable || component.has( crafting_inv, filter, batch ) ) {
list_as_string.push_back( colorize( text, color ) );
}
buffer_has.push_back( text + color_tag );
}
std::sort( list_as_string.begin(), list_as_string.end() );
Expand Down Expand Up @@ -1284,14 +1289,14 @@ std::vector<const requirement_data *> deduped_requirement_data::feasible_alterna
return result;
}

const requirement_data &deduped_requirement_data::select_alternative(
const requirement_data *deduped_requirement_data::select_alternative(
player &crafter, const std::function<bool( const item & )> &filter, int batch,
craft_flags flags ) const
{
return select_alternative( crafter, crafter.crafting_inventory(), filter, batch, flags );
}

const requirement_data &deduped_requirement_data::select_alternative(
const requirement_data *deduped_requirement_data::select_alternative(
player &crafter, const inventory &inv, const std::function<bool( const item & )> &filter,
int batch, craft_flags flags ) const
{
Expand Down
27 changes: 21 additions & 6 deletions src/requirements.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ struct quality_requirement {
}
};

enum class requirement_display_flags {
none = 0,
no_unavailable = 1,
};

inline constexpr requirement_display_flags operator&( requirement_display_flags l,
requirement_display_flags r )
{
return static_cast<requirement_display_flags>(
static_cast<unsigned>( l ) & static_cast<unsigned>( r ) );
}

/**
* The *_vector members represent list of alternatives requirements:
* alter_tool_comp_vector = { * { { a, b }, { c, d } }
Expand Down Expand Up @@ -275,8 +287,9 @@ struct requirement_data {

/** @param filter see @ref can_make_with_inventory */
std::vector<std::string> get_folded_components_list( int width, nc_color col,
const inventory &crafting_inv, const std::function<bool( const item & )> &filter, int batch = 1,
std::string hilite = "" ) const;
const inventory &crafting_inv, const std::function<bool( const item & )> &filter,
int batch = 1, std::string hilite = "",
requirement_display_flags = requirement_display_flags::none ) const;

std::vector<std::string> get_folded_tools_list( int width, nc_color col,
const inventory &crafting_inv, int batch = 1 ) const;
Expand Down Expand Up @@ -328,8 +341,10 @@ struct requirement_data {

template<typename T>
std::vector<std::string> get_folded_list( int width, const inventory &crafting_inv,
const std::function<bool( const item & )> &filter, const std::vector< std::vector<T> > &objs,
int batch = 1, const std::string &hilite = "" ) const;
const std::function<bool( const item & )> &filter,
const std::vector< std::vector<T> > &objs, int batch = 1,
const std::string &hilite = "",
requirement_display_flags = requirement_display_flags::none ) const;

template<typename T>
static bool any_marked_available( const std::vector<T> &comps );
Expand Down Expand Up @@ -378,11 +393,11 @@ class deduped_requirement_data
const inventory &crafting_inv, const std::function<bool( const item & )> &filter,
int batch = 1, craft_flags = craft_flags::none ) const;

const requirement_data &select_alternative(
const requirement_data *select_alternative(
player &, const std::function<bool( const item & )> &filter, int batch = 1,
craft_flags = craft_flags::none ) const;

const requirement_data &select_alternative(
const requirement_data *select_alternative(
player &, const inventory &, const std::function<bool( const item & )> &filter,
int batch = 1, craft_flags = craft_flags::none ) const;

Expand Down

0 comments on commit dc72640

Please sign in to comment.