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

Detect and warn about crafting with rotten food #36610

Merged
merged 1 commit into from
Jan 2, 2020
Merged
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
22 changes: 16 additions & 6 deletions src/craft_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<item_comp> is = crafter->select_item_component( it, batch_size, map_inv, true,
filter );
comp_selection<item_comp> is =
crafter->select_item_component( it, batch_size, map_inv, true, filter );
if( is.use_from == cancel ) {
return;
}
Expand Down Expand Up @@ -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<item> tmp = crafter->consume_items( it, batch_size, filter );
Expand Down Expand Up @@ -273,7 +283,7 @@ std::vector<comp_selection<item_comp>> craft_command::check_item_components_miss
{
std::vector<comp_selection<item_comp>> 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;
Expand Down
3 changes: 3 additions & 0 deletions src/craft_command.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <string>

#include "point.h"
#include "recipe.h"
#include "requirements.h"

class inventory;
Expand Down Expand Up @@ -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;
Expand Down
19 changes: 16 additions & 3 deletions src/crafting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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<bool( const item & )> filter = rec.get_component_filter();
std::function<bool( const item & )> filter = rec.get_component_filter();
const std::function<bool( const item & )> 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;

Expand All @@ -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 );

Expand Down
92 changes: 48 additions & 44 deletions src/crafting_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,26 @@ const recipe *select_crafting_recipe( int &batch_size )
list_circularizer<std::string> tab( craft_cat_list );
list_circularizer<std::string> subtab( craft_subcat_list[tab.cur()] );
std::vector<const recipe *> current;
std::vector<bool> 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<availability> available;
const int componentPrintHeight = dataHeight - tailHeight - 1;
//preserves component color printout between mode rotations
nc_color rotated_color = c_white;
Expand Down Expand Up @@ -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<const recipe *, bool> availability_cache;
std::map<const recipe *, availability> availability_cache;

do {
if( redraw ) {
Expand All @@ -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<const recipe *> picking;
Expand Down Expand Up @@ -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 );
} );
}

Expand Down Expand Up @@ -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 ) {
Expand All @@ -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 ) {
Expand All @@ -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<size_t>( dataHeight ) + 1; ++i ) {
for( int i = 0; i < static_cast<int>( current.size() ) && i < dataHeight + 1; ++i ) {
std::string tmp_name = current[i]->result_name();
if( batch ) {
tmp_name = string_format( _( "%2dx %s" ), static_cast<int>( i ) + 1, tmp_name );
}
if( static_cast<int>( 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();

Expand Down Expand Up @@ -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,
_( "<color_red>Will use rotten ingredients</color>" ) );
}
ypos += print_items( *current[line], w_data, ypos, xpos, col, batch ? line + 1 : 1 );
}

Expand Down Expand Up @@ -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 ) ) {
Expand Down
3 changes: 2 additions & 1 deletion src/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 );

/**
Expand Down
9 changes: 7 additions & 2 deletions src/recipe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -500,14 +500,19 @@ bool recipe::will_be_blacklisted() const
return any_is_blacklisted( reqs_internal ) || any_is_blacklisted( reqs_external );
}

std::function<bool( const item & )> recipe::get_component_filter() const
std::function<bool( const item & )> 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<bool>( flags & recipe_filter_flags::no_rotten ) && result.goes_bad();
std::function<bool( const item & )> rotten_filter = return_true<item>;
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();
};
Expand Down
14 changes: 13 additions & 1 deletion src/recipe.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<recipe_filter_flags>(
static_cast<unsigned>( l ) & static_cast<unsigned>( r ) );
}

class recipe
{
friend class recipe_dictionary;
Expand Down Expand Up @@ -66,7 +77,8 @@ class recipe
// recipe finalization happens
bool will_be_blacklisted() const;

std::function<bool( const item & )> get_component_filter() const;
std::function<bool( const item & )> 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;
Expand Down