diff --git a/src/craft_command.cpp b/src/craft_command.cpp index 73018b9fdae28..0800bf519e065 100644 --- a/src/craft_command.cpp +++ b/src/craft_command.cpp @@ -122,7 +122,7 @@ void craft_command::execute( const tripoint &new_loc ) if( need_selections ) { if( !crafter->can_make( rec, batch_size ) ) { - if( crafter->can_start_craft( rec, batch_size ) ) { + if( crafter->can_start_craft( rec, recipe_filter_flags::none, batch_size ) ) { if( !query_yn( _( "You don't have enough charges to complete the %s.\n" "Start crafting anyway?" ), rec->result_name() ) ) { return; @@ -133,13 +133,23 @@ void craft_command::execute( const tripoint &new_loc ) } } + flags = recipe_filter_flags::no_rotten; + + if( !crafter->can_start_craft( rec, flags, batch_size ) ) { + if( !query_yn( _( "This craft will use rotten components.\n" + "Start crafting anyway?" ) ) ) { + return; + } + flags = recipe_filter_flags::none; + } + item_selections.clear(); const auto needs = rec->requirements(); - const auto filter = rec->get_component_filter(); + const auto filter = rec->get_component_filter( flags ); for( const auto &it : needs.get_components() ) { - comp_selection is = crafter->select_item_component( it, batch_size, map_inv, true, - filter ); + comp_selection is = + crafter->select_item_component( it, batch_size, map_inv, true, filter ); if( is.use_from == cancel ) { return; } @@ -228,7 +238,7 @@ item craft_command::create_in_progress_craft() return item(); } - const auto filter = rec->get_component_filter(); + const auto filter = rec->get_component_filter( flags ); for( const auto &it : item_selections ) { std::list tmp = crafter->consume_items( it, batch_size, filter ); @@ -273,7 +283,7 @@ std::vector> craft_command::check_item_components_miss { std::vector> missing; - const auto filter = rec->get_component_filter(); + const auto filter = rec->get_component_filter( flags ); for( const auto &item_sel : item_selections ) { itype_id type = item_sel.comp.type; diff --git a/src/craft_command.h b/src/craft_command.h index a0071e8e41634..ac38b16a9350e 100644 --- a/src/craft_command.h +++ b/src/craft_command.h @@ -6,6 +6,7 @@ #include #include "point.h" +#include "recipe.h" #include "requirements.h" class inventory; @@ -96,6 +97,8 @@ class craft_command // This is mainly here for maintainability reasons. player *crafter; + recipe_filter_flags flags = recipe_filter_flags::none; + // Location of the workbench to place the item on // zero_tripoint indicates crafting without a workbench tripoint loc = tripoint_zero; diff --git a/src/crafting.cpp b/src/crafting.cpp index 65e81f1b2aff0..e91141033871d 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -482,7 +482,7 @@ bool player::can_make( const recipe *r, int batch_size ) batch_size ); } -bool player::can_start_craft( const recipe *rec, int batch_size ) +bool player::can_start_craft( const recipe *rec, recipe_filter_flags flags, int batch_size ) { if( !rec ) { return false; @@ -525,7 +525,8 @@ bool player::can_start_craft( const recipe *rec, int batch_size ) rec->requirements().get_qualities(), adjusted_comp_reqs ); - return start_reqs.can_make_with_inventory( crafting_inventory(), rec->get_component_filter() ); + return start_reqs.can_make_with_inventory( crafting_inventory(), + rec->get_component_filter( flags ) ); } const inventory &player::crafting_inventory( bool clear_path ) @@ -1254,7 +1255,9 @@ bool player::can_continue_craft( item &craft ) // Avoid building an inventory from the map if we don't have to, as it is expensive if( !continue_reqs.is_empty() ) { - const std::function filter = rec.get_component_filter(); + std::function filter = rec.get_component_filter(); + const std::function no_rotten_filter = + rec.get_component_filter( recipe_filter_flags::no_rotten ); // continue_reqs are for all batches at once const int batch_size = 1; @@ -1273,6 +1276,16 @@ bool player::can_continue_craft( item &craft ) return false; } + if( continue_reqs.can_make_with_inventory( crafting_inventory(), no_rotten_filter, + batch_size ) ) { + filter = no_rotten_filter; + } else { + if( !query_yn( _( "Some components required to continue are rotten.\n" + "Continue crafting anyway?" ) ) ) { + return false; + } + } + inventory map_inv; map_inv.form_from_map( pos(), PICKUP_RANGE, this ); diff --git a/src/crafting_gui.cpp b/src/crafting_gui.cpp index 41c7f84b3ee10..d3b055b0704b9 100644 --- a/src/crafting_gui.cpp +++ b/src/crafting_gui.cpp @@ -192,7 +192,26 @@ const recipe *select_crafting_recipe( int &batch_size ) list_circularizer tab( craft_cat_list ); list_circularizer subtab( craft_subcat_list[tab.cur()] ); std::vector current; - std::vector available; + + 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 ) ) + {} + bool can_craft; + bool can_craft_non_rotten; + + nc_color selected_color() const { + return can_craft ? can_craft_non_rotten ? h_white : h_brown : h_dark_gray; + } + + nc_color color() const { + return can_craft ? can_craft_non_rotten ? c_white : c_brown : c_dark_gray; + } + }; + + std::vector available; const int componentPrintHeight = dataHeight - tailHeight - 1; //preserves component color printout between mode rotations nc_color rotated_color = c_white; @@ -237,7 +256,7 @@ const recipe *select_crafting_recipe( int &batch_size ) std::string filterstring; const auto &available_recipes = g->u.get_available_recipes( crafting_inv, &helpers ); - std::map availability_cache; + std::map availability_cache; do { if( redraw ) { @@ -264,7 +283,7 @@ const recipe *select_crafting_recipe( int &batch_size ) current.clear(); for( int i = 1; i <= 20; i++ ) { current.push_back( chosen ); - available.push_back( g->u.can_start_craft( chosen, i ) ); + available.push_back( availability( chosen, i ) ); } } else { std::vector picking; @@ -363,23 +382,26 @@ const recipe *select_crafting_recipe( int &batch_size ) // cache recipe availability on first display for( const auto e : current ) { if( !availability_cache.count( e ) ) { - availability_cache.emplace( e, g->u.can_start_craft( e ) ); + availability_cache.emplace( e, availability( e ) ); } } if( subtab.cur() != "CSC_*_RECENT" ) { - std::stable_sort( current.begin(), current.end(), []( const recipe * a, const recipe * b ) { + std::stable_sort( current.begin(), current.end(), + []( const recipe * a, const recipe * b ) { return b->difficulty < a->difficulty; } ); - std::stable_sort( current.begin(), current.end(), [&]( const recipe * a, const recipe * b ) { - return availability_cache[a] && !availability_cache[b]; + std::stable_sort( current.begin(), current.end(), + [&]( const recipe * a, const recipe * b ) { + return availability_cache.at( a ).can_craft && + !availability_cache.at( b ).can_craft; } ); } std::transform( current.begin(), current.end(), std::back_inserter( available ), [&]( const recipe * e ) { - return availability_cache[e]; + return availability_cache.at( e ); } ); } @@ -443,13 +465,8 @@ const recipe *select_crafting_recipe( int &batch_size ) tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name ); } mvwprintz( w_data, point( 2, i - recmin ), c_dark_gray, "" ); // Clear the line - if( i == line ) { - mvwprintz( w_data, point( 2, i - recmin ), ( available[i] ? h_white : h_dark_gray ), - utf8_truncate( tmp_name, 28 ) ); - } else { - mvwprintz( w_data, point( 2, i - recmin ), ( available[i] ? c_white : c_dark_gray ), - utf8_truncate( tmp_name, 28 ) ); - } + nc_color col = i == line ? available[i].selected_color() : available[i].color(); + mvwprintz( w_data, point( 2, i - recmin ), col, utf8_truncate( tmp_name, 28 ) ); } } else if( line >= recmax - dataHalfLines ) { for( int i = recmax - dataLines; i < recmax; ++i ) { @@ -458,15 +475,9 @@ const recipe *select_crafting_recipe( int &batch_size ) tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name ); } mvwprintz( w_data, point( 2, dataLines + i - recmax ), c_light_gray, "" ); // Clear the line - if( i == line ) { - mvwprintz( w_data, point( 2, dataLines + i - recmax ), - ( available[i] ? h_white : h_dark_gray ), - utf8_truncate( tmp_name, 28 ) ); - } else { - mvwprintz( w_data, point( 2, dataLines + i - recmax ), - ( available[i] ? c_white : c_dark_gray ), - utf8_truncate( tmp_name, 28 ) ); - } + nc_color col = i == line ? available[i].selected_color() : available[i].color(); + mvwprintz( w_data, point( 2, dataLines + i - recmax ), col, + utf8_truncate( tmp_name, 28 ) ); } } else { for( int i = line - dataHalfLines; i < line - dataHalfLines + dataLines; ++i ) { @@ -475,37 +486,26 @@ const recipe *select_crafting_recipe( int &batch_size ) tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name ); } mvwprintz( w_data, point( 2, dataHalfLines + i - line ), c_light_gray, "" ); // Clear the line - if( i == line ) { - mvwprintz( w_data, point( 2, dataHalfLines + i - line ), - ( available[i] ? h_white : h_dark_gray ), - utf8_truncate( tmp_name, 28 ) ); - } else { - mvwprintz( w_data, point( 2, dataHalfLines + i - line ), - ( available[i] ? c_white : c_dark_gray ), - utf8_truncate( tmp_name, 28 ) ); - } + nc_color col = i == line ? available[i].selected_color() : available[i].color(); + mvwprintz( w_data, point( 2, dataHalfLines + i - line ), col, + utf8_truncate( tmp_name, 28 ) ); } } } else { - for( size_t i = 0; i < current.size() && i < static_cast( dataHeight ) + 1; ++i ) { + for( int i = 0; i < static_cast( current.size() ) && i < dataHeight + 1; ++i ) { std::string tmp_name = current[i]->result_name(); if( batch ) { - tmp_name = string_format( _( "%2dx %s" ), static_cast( i ) + 1, tmp_name ); - } - if( static_cast( i ) == line ) { - mvwprintz( w_data, point( 2, i ), ( available[i] ? h_white : h_dark_gray ), - utf8_truncate( tmp_name, 28 ) ); - } else { - mvwprintz( w_data, point( 2, i ), ( available[i] ? c_white : c_dark_gray ), - utf8_truncate( tmp_name, 28 ) ); + tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name ); } + nc_color col = i == line ? available[i].selected_color() : available[i].color(); + mvwprintz( w_data, point( 2, i ), col, utf8_truncate( tmp_name, 28 ) ); } } if( !current.empty() ) { int pane = FULL_SCREEN_WIDTH - 30 - 1; int count = batch ? line + 1 : 1; // batch size - nc_color col = available[ line ] ? c_white : c_light_gray; + nc_color col = available[ line ].color(); const auto &req = current[ line ]->requirements(); @@ -599,6 +599,10 @@ const recipe *select_crafting_recipe( int &batch_size ) current[line]->has_flag( "BLIND_EASY" ) ? _( "Easy" ) : current[line]->has_flag( "BLIND_HARD" ) ? _( "Hard" ) : _( "Impossible" ) ) ); + if( available[line].can_craft && !available[line].can_craft_non_rotten ) { + ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, + _( "Will use rotten ingredients" ) ); + } ypos += print_items( *current[line], w_data, ypos, xpos, col, batch ? line + 1 : 1 ); } @@ -693,7 +697,7 @@ const recipe *select_crafting_recipe( int &batch_size ) } else if( action == "UP" ) { line--; } else if( action == "CONFIRM" ) { - if( available.empty() || !available[line] ) { + if( available.empty() || !available[line].can_craft ) { popup( _( "You can't do that!" ) ); } else if( !g->u.check_eligible_containers_for_crafting( *current[line], ( batch ) ? line + 1 : 1 ) ) { diff --git a/src/player.h b/src/player.h index b6f4a5d873257..469ebf77180a1 100644 --- a/src/player.h +++ b/src/player.h @@ -48,6 +48,7 @@ class map; class npc; struct pathfinding_settings; class recipe; +enum class recipe_filter_flags : int; struct islot_comestible; struct itype; class monster; @@ -1045,7 +1046,7 @@ class player : public Character * The player is not required to have enough tool charges to finish crafting, only to * complete the first step (total / 20 + total % 20 charges) */ - bool can_start_craft( const recipe *rec, int batch_size = 1 ); + bool can_start_craft( const recipe *rec, recipe_filter_flags, int batch_size = 1 ); bool making_would_work( const recipe_id &id_to_make, int batch_size ); /** diff --git a/src/recipe.cpp b/src/recipe.cpp index c17b6d77a9260..e0ac262b4ddc3 100644 --- a/src/recipe.cpp +++ b/src/recipe.cpp @@ -500,14 +500,19 @@ bool recipe::will_be_blacklisted() const return any_is_blacklisted( reqs_internal ) || any_is_blacklisted( reqs_external ); } -std::function recipe::get_component_filter() const +std::function recipe::get_component_filter( + const recipe_filter_flags flags ) const { const item result = create_result(); // Disallow crafting of non-perishables with rotten components // Make an exception for items with the ALLOW_ROTTEN flag such as seeds + const bool recipe_forbids_rotten = + result.is_food() && !result.goes_bad() && !has_flag( "ALLOW_ROTTEN" ); + const bool flags_forbid_rotten = + static_cast( flags & recipe_filter_flags::no_rotten ) && result.goes_bad(); std::function rotten_filter = return_true; - if( result.is_food() && !result.goes_bad() && !has_flag( "ALLOW_ROTTEN" ) ) { + if( recipe_forbids_rotten || flags_forbid_rotten ) { rotten_filter = []( const item & component ) { return !component.rotten(); }; diff --git a/src/recipe.h b/src/recipe.h index c5170b370e963..89374a519f572 100644 --- a/src/recipe.h +++ b/src/recipe.h @@ -21,6 +21,17 @@ class time_duration; using itype_id = std::string; // From itype.h class Character; +enum class recipe_filter_flags : int { + none = 0, + no_rotten = 1, +}; + +inline constexpr recipe_filter_flags operator&( recipe_filter_flags l, recipe_filter_flags r ) +{ + return static_cast( + static_cast( l ) & static_cast( r ) ); +} + class recipe { friend class recipe_dictionary; @@ -66,7 +77,8 @@ class recipe // recipe finalization happens bool will_be_blacklisted() const; - std::function get_component_filter() const; + std::function get_component_filter( + recipe_filter_flags = recipe_filter_flags::none ) const; /** Prevent this recipe from ever being added to the player's learned recipies ( used for special NPC crafting ) */ bool never_learn = false;