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

Correctly predict craftability of recipes with overlapping item requirements #36657

Merged
merged 10 commits into from
Jan 10, 2020
27 changes: 13 additions & 14 deletions src/basecamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,8 @@ std::string basecamp::om_upgrade_description( const std::string &bldg, bool trun

std::vector<std::string> component_print_buffer;
const int pane = FULL_SCREEN_WIDTH;
const auto tools = making.requirements().get_folded_tools_list( pane, c_white, _inv, 1 );
const auto comps = making.requirements().get_folded_components_list( pane, c_white, _inv,
const auto tools = making.simple_requirements().get_folded_tools_list( pane, c_white, _inv, 1 );
const auto comps = making.simple_requirements().get_folded_components_list( pane, c_white, _inv,
making.get_component_filter(), 1 );
component_print_buffer.insert( component_print_buffer.end(), tools.begin(), tools.end() );
component_print_buffer.insert( component_print_buffer.end(), comps.begin(), comps.end() );
Expand Down Expand Up @@ -345,7 +345,7 @@ std::vector<basecamp_upgrade> basecamp::available_upgrades( const point &dir )
basecamp_upgrade data;
data.bldg = bldg;
data.name = recp.blueprint_name();
const auto &reqs = recp.requirements();
const auto &reqs = recp.deduped_requirements();
data.avail = reqs.can_make_with_inventory( _inv, recp.get_component_filter(), 1 );
data.in_progress = in_progress;
ret_data.emplace_back( data );
Expand Down 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_.requirements();
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 All @@ -726,23 +730,18 @@ void basecamp_action_components::consume_components()
target_map = map_.get();
}
const tripoint &origin = target_map->getlocal( base_.get_dumping_spot() );
const auto &req = making_.requirements();
if( item_selections_.size() != req.get_components().size() ||
tool_selections_.size() != req.get_tools().size() ) {
debugmsg( "Not all selections have been made for basecamp_action_components" );
}
for( const comp_selection<item_comp> &sel : item_selections_ ) {
g->u.consume_items( *target_map, sel, batch_size_, is_crafting_component, origin,
base_.inv_range );
basecamp::inv_range );
}
// this may consume pseudo-resources from fake items
for( const comp_selection<tool_comp> &sel : tool_selections_ ) {
g->u.consume_tools( *target_map, sel, batch_size_, origin, base_.inv_range, &base_ );
g->u.consume_tools( *target_map, sel, batch_size_, origin, basecamp::inv_range, &base_ );
}
// go back and consume the actual resources
for( basecamp_resource &bcp_r : base_.resources ) {
if( bcp_r.consumed > 0 ) {
target_map->use_charges( origin, base_.inv_range, bcp_r.ammo_id, bcp_r.consumed );
target_map->use_charges( origin, basecamp::inv_range, bcp_r.ammo_id, bcp_r.consumed );
bcp_r.consumed = 0;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/consumption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ std::pair<nutrients, nutrients> player::compute_nutrient_range(
our_extra_flags.insert( "COOKED" );
}

const requirement_data requirements = rec.requirements();
const requirement_data requirements = rec.simple_requirements();
const requirement_data::alter_item_comp_vector &component_requirements =
requirements.get_components();

Expand Down
10 changes: 7 additions & 3 deletions src/craft_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,14 @@ void craft_command::execute( const tripoint &new_loc )
}

item_selections.clear();
const auto needs = rec->requirements();
const auto filter = rec->get_component_filter( flags );
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 @@ -157,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
83 changes: 40 additions & 43 deletions src/crafting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ bool player::making_would_work( const recipe_id &id_to_make, int batch_size )
if( !can_make( &making, batch_size ) ) {
std::string buffer = _( "You can no longer make that craft!" );
buffer += "\n";
buffer += making.requirements().list_missing();
buffer += making.simple_requirements().list_missing();
popup( buffer, PF_NONE );
return false;
}
Expand Down Expand Up @@ -478,8 +478,8 @@ bool player::can_make( const recipe *r, int batch_size )
return false;
}

return r->requirements().can_make_with_inventory( crafting_inv, r->get_component_filter(),
batch_size );
return r->deduped_requirements().can_make_with_inventory(
crafting_inv, r->get_component_filter(), batch_size );
}

bool player::can_start_craft( const recipe *rec, recipe_filter_flags flags, int batch_size )
Expand All @@ -488,45 +488,9 @@ bool player::can_start_craft( const recipe *rec, recipe_filter_flags flags, int
return false;
}

const std::vector<std::vector<tool_comp>> &tool_reqs = rec->requirements().get_tools();

// For tools adjust the reqired charges
std::vector<std::vector<tool_comp>> adjusted_tool_reqs;
for( const std::vector<tool_comp> &alternatives : tool_reqs ) {
std::vector<tool_comp> adjusted_alternatives;
for( const tool_comp &alternative : alternatives ) {
tool_comp adjusted_alternative = alternative;
if( adjusted_alternative.count > 0 ) {
adjusted_alternative.count *= batch_size;
// Only for the first 5% progress
adjusted_alternative.count = std::max( adjusted_alternative.count / 20, 1 );
}
adjusted_alternatives.push_back( adjusted_alternative );
}
adjusted_tool_reqs.push_back( adjusted_alternatives );
}

const std::vector<std::vector<item_comp>> &comp_reqs = rec->requirements().get_components();

// For components we need to multiply by batch size to stay even with tools
std::vector<std::vector<item_comp>> adjusted_comp_reqs;
for( const std::vector<item_comp> &alternatives : comp_reqs ) {
std::vector<item_comp> adjusted_alternatives;
for( const item_comp &alternative : alternatives ) {
item_comp adjusted_alternative = alternative;
adjusted_alternative.count *= batch_size;
adjusted_alternatives.push_back( adjusted_alternative );
}
adjusted_comp_reqs.push_back( adjusted_alternatives );
}

// Qualities don't need adjustment
const requirement_data start_reqs( adjusted_tool_reqs,
rec->requirements().get_qualities(),
adjusted_comp_reqs );

return start_reqs.can_make_with_inventory( crafting_inventory(),
rec->get_component_filter( flags ) );
const inventory &inv = crafting_inventory();
return rec->deduped_requirements().can_make_with_inventory(
inv, rec->get_component_filter( flags ), batch_size, craft_flags::start_only );
}

const inventory &player::crafting_inventory( bool clear_path )
Expand Down Expand Up @@ -1306,7 +1270,7 @@ bool player::can_continue_craft( item &craft )

if( !craft.has_tools_to_continue() ) {

const std::vector<std::vector<tool_comp>> &tool_reqs = rec.requirements().get_tools();
const std::vector<std::vector<tool_comp>> &tool_reqs = rec.simple_requirements().get_tools();
const int batch_size = craft.charges;

std::vector<std::vector<tool_comp>> adjusted_tool_reqs;
Expand Down Expand Up @@ -1357,6 +1321,39 @@ 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 &inv,
const std::function<bool( const item & )> &filter ) const
{
assert( !alternatives.empty() );
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') */
comp_selection<item_comp> player::select_item_component( const std::vector<item_comp> &components,
Expand Down
10 changes: 10 additions & 0 deletions src/crafting.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ class item;
class player;
class recipe;

enum class craft_flags : int {
none = 0,
start_only = 1, // Only require 5% (plus remainder) of tool charges
};

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

// removes any (removable) ammo from the item and stores it in the
// players inventory.
void remove_ammo( item &dis_item, player &p );
Expand Down
29 changes: 22 additions & 7 deletions src/crafting_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,22 @@ const recipe *select_crafting_recipe( int &batch_size )
std::vector<const recipe *> current;

struct availability {
availability( const recipe *r, int batch_size = 1 ) :
can_craft( g->u.can_start_craft( r, recipe_filter_flags::none, batch_size ) ),
can_craft_non_rotten( g->u.can_start_craft( r, recipe_filter_flags::no_rotten,
batch_size ) )
{}
availability( const recipe *r, int batch_size = 1 ) {
const inventory &inv = g->u.crafting_inventory();
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, 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, craft_flags::start_only );
}
bool can_craft;
bool can_craft_non_rotten;
bool apparently_craftable;

nc_color selected_color() const {
return can_craft ? can_craft_non_rotten ? h_white : h_brown : h_dark_gray;
Expand Down Expand Up @@ -507,7 +516,7 @@ const recipe *select_crafting_recipe( int &batch_size )
int count = batch ? line + 1 : 1; // batch size
nc_color col = available[ line ].color();

const auto &req = current[ line ]->requirements();
const auto &req = current[ line ]->simple_requirements();

draw_can_craft_indicator( w_head, 0, *current[line] );
wrefresh( w_head );
Expand Down Expand Up @@ -603,6 +612,12 @@ const recipe *select_crafting_recipe( int &batch_size )
ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col,
_( "<color_red>Will use rotten ingredients</color>" ) );
}
if( !available[line].can_craft && available[line].apparently_craftable ) {
ypos += fold_and_print(
w_data, point( xpos, ypos ), pane, col,
_( "<color_red>Cannot be crafted because the same item is needed "
"for multiple components</color>" ) );
}
ypos += print_items( *current[line], w_data, ypos, xpos, col, batch ? line + 1 : 1 );
}

Expand Down Expand Up @@ -851,7 +866,7 @@ std::string peek_related_recipe( const recipe *current, const recipe_subset &ava
{
// current recipe components
std::vector<std::pair<itype_id, std::string>> related_components;
const requirement_data &req = current->requirements();
const requirement_data &req = current->simple_requirements();
for( const std::vector<item_comp> &comp_list : req.get_components() ) {
for( const item_comp &a : comp_list ) {
related_components.push_back( { a.type, item::nname( a.type, 1 ) } );
Expand Down
22 changes: 12 additions & 10 deletions src/faction_camp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,8 @@ void basecamp::add_available_recipes( mission_data &mission_key, const point &di
const std::string &title_e = dir_abbr + recipe_data.second;
const std::string &entry = craft_description( recipe_data.first );
const recipe &recp = recipe_data.first.obj();
bool craftable = recp.requirements().can_make_with_inventory( _inv,
recp.get_component_filter() );
bool craftable = recp.deduped_requirements().can_make_with_inventory(
_inv, recp.get_component_filter() );
mission_key.add_start( id, title_e, dir, entry, craftable );
}
}
Expand Down Expand Up @@ -1538,7 +1538,8 @@ void basecamp::start_upgrade( const std::string &bldg, const point &dir,
{
const recipe &making = recipe_id( bldg ).obj();
//Stop upgrade if you don't have materials
if( making.requirements().can_make_with_inventory( _inv, making.get_component_filter(), 1 ) ) {
if( making.deduped_requirements().can_make_with_inventory(
_inv, making.get_component_filter() ) ) {
bool must_feed = bldg != "faction_base_camp_1";

basecamp_action_components components( making, 1, *this );
Expand Down Expand Up @@ -1970,7 +1971,7 @@ void basecamp::start_fortifications( std::string &bldg_exp )
if( !query_yn( _( "Trip Estimate:\n%s" ), camp_trip_description( total_time, build_time,
travel_time, dist, trips, need_food ) ) ) {
return;
} else if( !making.requirements().can_make_with_inventory( _inv,
} else if( !making.deduped_requirements().can_make_with_inventory( _inv,
making.get_component_filter(), ( fortify_om.size() * 2 ) - 2 ) ) {
popup( _( "You don't have the material to build the fortification." ) );
return;
Expand Down Expand Up @@ -2036,8 +2037,8 @@ void basecamp::start_crafting( const std::string &cur_id, const point &cur_dir,
if( it != recipes.end() ) {
const recipe &making = it->first.obj();

if( !making.requirements().can_make_with_inventory( _inv,
making.get_component_filter(), 1 ) ) {
if( !making.deduped_requirements().can_make_with_inventory(
_inv, making.get_component_filter() ) ) {
popup( _( "You don't have the materials to craft that" ) );
return;
}
Expand Down Expand Up @@ -2851,8 +2852,8 @@ int basecamp::recipe_batch_max( const recipe &making ) const
time_duration work_days = base_camps::to_workdays( making.batch_duration(
max_batch + batch_size ) );
int food_req = time_to_food( work_days );
bool can_make = making.requirements().can_make_with_inventory( _inv,
making.get_component_filter(), max_batch + batch_size );
bool can_make = making.deduped_requirements().can_make_with_inventory(
_inv, making.get_component_filter(), max_batch + batch_size );
if( can_make && camp_food_supply() > food_req ) {
max_batch += batch_size;
} else {
Expand Down Expand Up @@ -3472,8 +3473,9 @@ std::string basecamp::craft_description( const recipe_id &itm )

std::vector<std::string> component_print_buffer;
int pane = FULL_SCREEN_WIDTH;
auto tools = making.requirements().get_folded_tools_list( pane, c_white, _inv, 1 );
auto comps = making.requirements().get_folded_components_list( pane, c_white, _inv,
const requirement_data &req = making.simple_requirements();
auto tools = req.get_folded_tools_list( pane, c_white, _inv, 1 );
auto comps = req.get_folded_components_list( pane, c_white, _inv,
making.get_component_filter(), 1 );

component_print_buffer.insert( component_print_buffer.end(), tools.begin(), tools.end() );
Expand Down
3 changes: 2 additions & 1 deletion src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3234,7 +3234,8 @@ void item::final_info( std::vector<iteminfo> &info, const iteminfo_query *parts,
} else {
const std::string recipes = enumerate_as_string( known_recipes.begin(), known_recipes.end(),
[ &inv ]( const recipe * r ) {
if( r->requirements().can_make_with_inventory( inv, r->get_component_filter() ) ) {
if( r->deduped_requirements().can_make_with_inventory(
inv, r->get_component_filter() ) ) {
return r->result_name();
} else {
return string_format( "<dark>%s</dark>", r->result_name() );
Expand Down
Loading