std::string mod_ui::get_information( MOD_INFORMATION *mod ) { if( mod == NULL ) { return ""; } std::ostringstream info; if( !mod->authors.empty() ) { info << "<color_ltblue>" << ngettext( "Author", "Authors", mod->authors.size() ) << "</color>: " << enumerate_as_string( mod->authors ) << "\n"; } if( !mod->maintainers.empty() ) { info << "<color_ltblue>" << ngettext( "Maintainer", "Maintainers", mod->maintainers.size() ) << "</color>: " << enumerate_as_string( mod->maintainers ) << "\n"; } if( !mod->dependencies.empty() ) { const auto &deps = mod->dependencies; auto str = enumerate_as_string( deps.begin(), deps.end(), [&]( const std::string & e ) { if( active_manager->mod_map.find( e ) != active_manager->mod_map.end() ) { return string_format( "[%s]", active_manager->mod_map[e]->name.c_str() ); } else { return string_format( "[<color_red>%s</color>]", e.c_str() ); } } ); info << "<color_ltblue>" << ngettext( "Dependency", "Dependencies", deps.size() ) << "</color>: " << str << "\n"; } if( !mod->description.empty() ) { info << _( mod->description.c_str() ) << "\n"; } std::string note = !mm_tree->is_available( mod->ident ) ? mm_tree->get_node( mod->ident )->s_errors() : ""; if( !note.empty() ) { info << "<color_red>" << note << "</color>"; } #ifndef LUA if( mod->need_lua ) { std::string lua_msg = ""; lua_msg += "<color_red>"; lua_msg += _( "This mod requires Lua but your CDDA build doesn't support it!" ); lua_msg += "</color>"; info << lua_msg; } #endif return info.str(); }
//builds the power usage string of a given bionic std::string build_bionic_poweronly_string( bionic const &bio ) { const bionic_data &bio_data = bio.id.obj(); std::vector<std::string> properties; if( bio_data.charge_time > 0 ) { if( bio_data.power_over_time > 0 ) { properties.push_back( bio_data.charge_time == 1 ? string_format( _( "%d PU/turn" ), bio_data.power_over_time ) : string_format( _( "%d PU/%d turns" ), bio_data.power_over_time, bio_data.charge_time ) ); } if( bio_data.power_activate > 0 ) { properties.push_back( string_format( _( "%d PU act" ), bio_data.power_activate ) ); } if( bio_data.power_deactivate > 0 ) { properties.push_back( string_format( _( "%d PU deact" ), bio_data.power_deactivate ) ); } } if( bio_data.toggled ) { properties.push_back( bio.powered ? _( "ON" ) : _( "OFF" ) ); } return enumerate_as_string( properties, false ); }
std::string input_context::get_conflicts( const input_event &event ) const { return enumerate_as_string( registered_actions.begin(), registered_actions.end(), [ this, &event ]( const std::string & action ) { return action_uses_input( action, event ) ? get_action_name( action ) : std::string(); } ); }
std::string faction::describe() const { std::string ret = _( desc.c_str() ); ret = ret + "\n\n" + string_format( _( "%1$s have the ultimate goal of %2$s." ), _( name.c_str() ), pgettext( "faction_goal", facgoal_data[goal].name.c_str() ) ); if( job2 == FACJOB_NULL ) { ret += string_format( _( " Their primary concern is %s." ), pgettext( "faction_job", facjob_data[job1].name.c_str() ) ); } else { ret += string_format( _( " Their primary concern is %1$s, but they are also involved in %2$s." ), pgettext( "faction_job", facjob_data[job1].name.c_str() ), pgettext( "faction_job", facjob_data[job2].name.c_str() ) ); } if( values == 0 ) { return ret; } std::vector<faction_value> vals; vals.reserve( NUM_FACVALS ); for( int i = 0; i < NUM_FACVALS; i++ ) { vals.push_back( faction_value( i ) ); } const std::string known_vals = enumerate_as_string( vals.begin(), vals.end(), [ this ]( const faction_value val ) { return has_value( val ) ? pgettext( "faction_value", facval_data[val].name.c_str() ) : ""; } ); if( !known_vals.empty() ) { ret += _( " They are known for " ) + known_vals + "."; } return ret; }
static void component_list_string( std::stringstream &str, const std::vector<comp_selection<T>> &components ) { str << enumerate_as_string( components.begin(), components.end(), []( const comp_selection<T> &comp ) { return comp.nname(); } ); }
std::string dependency_node::s_errors() { std::stringstream ret; for( auto &elem : all_errors ) { ret << error_keyvals[( unsigned )( elem.first )]; ret << enumerate_as_string( elem.second, enumeration_conjunction::none ); } return ret.str(); }
std::string map_data_common_t::extended_description() const { std::stringstream ss; ss << "<header>" << string_format( _( "That is a %s." ), name().c_str() ) << "</header>" << '\n'; ss << description << std::endl; bool has_any_harvest = std::any_of( harvest_by_season.begin(), harvest_by_season.end(), []( const harvest_id & hv ) { return !hv.obj().empty(); } ); if( has_any_harvest ) { ss << "--" << std::endl; int player_skill = g->u.get_skill_level( skill_survival ); ss << _( "You could harvest the following things from it:" ) << std::endl; // Group them by identical ids to avoid repeating same blocks of data // First, invert the mapping: season->id to id->seasons std::multimap<harvest_id, season_type> identical_harvest; for( size_t season = SPRING; season <= WINTER; season++ ) { const auto &hv = harvest_by_season[ season ]; if( hv.obj().empty() ) { continue; } identical_harvest.insert( std::make_pair( hv, static_cast<season_type>( season ) ) ); } // Now print them in order of seasons // @todo: Highlight current season for( size_t season = SPRING; season <= WINTER; season++ ) { const auto range = identical_harvest.equal_range( harvest_by_season[ season ] ); if( range.first == range.second ) { continue; } // List the seasons first ss << enumerate_as_string( range.first, range.second, []( const std::pair<harvest_id, season_type> &pr ) { if( pr.second == season_of_year( calendar::turn ) ) { return "<good>" + calendar::name_season( pr.second ) + "</good>"; } return "<dark>" + calendar::name_season( pr.second ) + "</dark>"; } ); ss << ":" << std::endl; // List the drops // They actually describe what player can get from it now, so it isn't spoily // @todo: Allow spoily listing of everything ss << range.first->first.obj().describe( player_skill ) << std::endl; // Remove the range from the multimap so that it isn't listed twice identical_harvest.erase( range.first, range.second ); } ss << std::endl; } return replace_colors( ss.str() ); }
std::string recipe::required_skills_string() const { if( required_skills.empty() ) { return _( "N/A" ); } return enumerate_as_string( required_skills.begin(), required_skills.end(), []( const std::pair<skill_id, int> &skill ) { return string_format( "%s (%d)", skill.first.obj().name().c_str(), skill.second ); } ); }
void harvest_list::check_consistency() { for( const auto &pr : harvest_all ) { const auto &hl = pr.second; const std::string errors = enumerate_as_string( hl.entries_.begin(), hl.entries_.end(), []( const harvest_entry &entry ) { return item::type_is_defined( entry.drop ) ? "" : entry.drop; } ); if( !errors.empty() ) { debugmsg( "Harvest list %s has invalid drop(s): %s", hl.id_.c_str(), errors.c_str() ); } } }
std::string recipe::required_skills_string( const Character *c, bool print_skill_level ) const { if( required_skills.empty() ) { return _( "<color_cyan>none</color>" ); } return enumerate_as_string( required_skills.begin(), required_skills.end(), [&]( const std::pair<skill_id, int> &skill ) { const auto player_skill = c ? c->get_skill_level( skill.first ) : 0; std::string difficulty_color = skill.second > player_skill ? "yellow" : "green"; std::string skill_level_string = print_skill_level ? "" : ( std::to_string( player_skill ) + "/" ); skill_level_string += std::to_string( skill.second ); return string_format( "<color_cyan>%s</color> <color_%s>(%s)</color>", skill.first.obj().name(), difficulty_color, skill_level_string ); } ); }
disassemble_inventory_preset( const player &p, const inventory &inv ) : pickup_inventory_preset( p ), p( p ), inv( inv ) { append_cell( [ this ]( const item_location & loc ) { const auto &req = get_recipe( loc ).disassembly_requirements(); if( req.is_empty() ) { return std::string(); } const auto components = req.get_components(); return enumerate_as_string( components.begin(), components.end(), []( const decltype( components )::value_type & comps ) { return comps.front().to_string(); } ); }, _( "YIELD" ) ); append_cell( [ this ]( const item_location & loc ) { return to_string_clipped( time_duration::from_turns( get_recipe( loc ).time / 100 ) ); }, _( "TIME" ) ); }
std::string harvest_list::describe( int at_skill ) const { if( empty() ) { return ""; } return enumerate_as_string( entries().begin(), entries().end(), [at_skill]( const harvest_entry & en ) { float min_f = en.base_num.first; float max_f = en.base_num.second; if( at_skill >= 0 ) { min_f += en.scale_num.first * at_skill; max_f += en.scale_num.second * at_skill; } else { max_f = en.max; } // TODO: Avoid repetition here by making a common harvest drop function int max_drops = std::min<int>( en.max, std::round( std::max( 0.0f, max_f ) ) ); int min_drops = std::max<int>( 0.0f, std::round( std::min( min_f, max_f ) ) ); if( max_drops <= 0 ) { return std::string(); } std::stringstream ss; ss << "<bold>" << item::nname( en.drop, max_drops ) << "</bold>"; // If the number is unspecified, just list the type if( max_drops >= 1000 && min_drops <= 0 ) { return ss.str(); } ss << ": "; if( min_drops == max_drops ) { ss << "<stat>" << min_drops << "</stat>"; } else if( max_drops < 1000 ) { ss << "<stat>" << min_drops << "-" << max_drops << "</stat>"; } else { ss << "<stat>" << min_drops << "+" << "</stat>"; } return ss.str(); } ); }
void harvest_list::check_consistency() { for( const auto &pr : harvest_all ) { const auto &hl = pr.second; const std::string hl_id = hl.id().c_str(); auto error_func = [&]( const harvest_entry & entry ) { std::string errorlist; bool item_valid = true; if( !( item::type_is_defined( entry.drop ) || ( entry.type == "bionic_group" && item_group::group_is_defined( entry.drop ) ) ) ) { item_valid = false; errorlist += entry.drop; } // non butchery harvests need to be excluded if( hl_id.substr( 0, 14 ) != "harvest_inline" ) { if( entry.type == "null" ) { if( !item_valid ) { errorlist += ", "; } errorlist += "null type"; } else if( !( entry.type == "flesh" || entry.type == "bone" || entry.type == "skin" || entry.type == "offal" || entry.type == "bionic" || entry.type == "bionic_group" ) ) { if( !item_valid ) { errorlist += ", "; } errorlist += entry.type; } } return errorlist; }; const std::string errors = enumerate_as_string( hl.entries_.begin(), hl.entries_.end(), error_func ); if( !errors.empty() ) { debugmsg( "Harvest list %s has invalid entry: %s", hl_id, errors ); } } }
void vpart_info::check() { for( auto &vp : vpart_info_all ) { auto &part = vp.second; // handle legacy parts without requirement data // @todo deprecate once requirements are entirely loaded from JSON if( part.legacy ) { part.install_skills.emplace( skill_mechanics, part.difficulty ); part.removal_skills.emplace( skill_mechanics, std::max( part.difficulty - 2, 2 ) ); part.repair_skills.emplace ( skill_mechanics, std::min( part.difficulty + 1, MAX_SKILL ) ); if( part.has_flag( "TOOL_WRENCH" ) || part.has_flag( "WHEEL" ) ) { part.install_reqs = { { requirement_id( "vehicle_bolt" ), 1 } }; part.removal_reqs = { { requirement_id( "vehicle_bolt" ), 1 } }; part.repair_reqs = { { requirement_id( "welding_standard" ), 5 } }; } else if( part.has_flag( "TOOL_SCREWDRIVER" ) ) { part.install_reqs = { { requirement_id( "vehicle_screw" ), 1 } }; part.removal_reqs = { { requirement_id( "vehicle_screw" ), 1 } }; part.repair_reqs = { { requirement_id( "adhesive" ), 1 } }; } else if( part.has_flag( "NAILABLE" ) ) { part.install_reqs = { { requirement_id( "vehicle_nail_install" ), 1 } }; part.removal_reqs = { { requirement_id( "vehicle_nail_removal" ), 1 } }; part.repair_reqs = { { requirement_id( "adhesive" ), 2 } }; } else if( part.has_flag( "TOOL_NONE" ) ) { // no-op } else { part.install_reqs = { { requirement_id( "welding_standard" ), 5 } }; part.removal_reqs = { { requirement_id( "vehicle_weld_removal" ), 1 } }; part.repair_reqs = { { requirement_id( "welding_standard" ), 5 } }; } } else { if( part.has_flag( "REVERSIBLE" ) ) { if( !part.removal_reqs.empty() ) { debugmsg( "vehicle part %s specifies both REVERSIBLE and removal", part.id.c_str() ); } part.removal_reqs = part.install_reqs; } } // add the base item to the installation requirements // @todo support multiple/alternative base items requirement_data ins; ins.components.push_back( { { { part.item, 1 } } } ); std::string ins_id = std::string( "inline_vehins_base_" ) += part.id.str(); requirement_data::save_requirement( ins, ins_id ); part.install_reqs.emplace_back( requirement_id( ins_id ), 1 ); if( part.removal_moves < 0 ) { part.removal_moves = part.install_moves / 2; } for( auto &e : part.install_skills ) { if( !e.first.is_valid() ) { debugmsg( "vehicle part %s has unknown install skill %s", part.id.c_str(), e.first.c_str() ); } } for( auto &e : part.removal_skills ) { if( !e.first.is_valid() ) { debugmsg( "vehicle part %s has unknown removal skill %s", part.id.c_str(), e.first.c_str() ); } } for( auto &e : part.repair_skills ) { if( !e.first.is_valid() ) { debugmsg( "vehicle part %s has unknown repair skill %s", part.id.c_str(), e.first.c_str() ); } } for( const auto &e : part.install_reqs ) { if( !e.first.is_valid() || e.second <= 0 ) { debugmsg( "vehicle part %s has unknown or incorrectly specified install requirements %s", part.id.c_str(), e.first.c_str() ); } } for( const auto &e : part.install_reqs ) { if( !( e.first.is_null() || e.first.is_valid() ) || e.second < 0 ) { debugmsg( "vehicle part %s has unknown or incorrectly specified removal requirements %s", part.id.c_str(), e.first.c_str() ); } } for( const auto &e : part.repair_reqs ) { if( !( e.first.is_null() || e.first.is_valid() ) || e.second < 0 ) { debugmsg( "vehicle part %s has unknown or incorrectly specified repair requirements %s", part.id.c_str(), e.first.c_str() ); } } if( part.install_moves < 0 ) { debugmsg( "vehicle part %s has negative installation time", part.id.c_str() ); } if( part.removal_moves < 0 ) { debugmsg( "vehicle part %s has negative removal time", part.id.c_str() ); } if( !item_group::group_is_defined( part.breaks_into_group ) ) { debugmsg( "Vehicle part %s breaks into non-existent item group %s.", part.id.c_str(), part.breaks_into_group.c_str() ); } if( part.sym == 0 ) { debugmsg( "vehicle part %s does not define a symbol", part.id.c_str() ); } if( part.sym_broken == 0 ) { debugmsg( "vehicle part %s does not define a broken symbol", part.id.c_str() ); } if( part.durability <= 0 ) { debugmsg( "vehicle part %s has zero or negative durability", part.id.c_str() ); } if( part.dmg_mod < 0 ) { debugmsg( "vehicle part %s has negative damage modifier", part.id.c_str() ); } if( part.folded_volume < 0 ) { debugmsg( "vehicle part %s has negative folded volume", part.id.c_str() ); } if( part.has_flag( "FOLDABLE" ) && part.folded_volume == 0 ) { debugmsg( "vehicle part %s has folding part with zero folded volume", part.name().c_str() ); } if( !item::type_is_defined( part.default_ammo ) ) { debugmsg( "vehicle part %s has undefined default ammo %s", part.id.c_str(), part.item.c_str() ); } if( part.size < 0 ) { debugmsg( "vehicle part %s has negative size", part.id.c_str() ); } if( !item::type_is_defined( part.item ) ) { debugmsg( "vehicle part %s uses undefined item %s", part.id.c_str(), part.item.c_str() ); } const itype &base_item_type = *item::find_type( part.item ); // Fuel type errors are serious and need fixing now if( !item::type_is_defined( part.fuel_type ) ) { debugmsg( "vehicle part %s uses undefined fuel %s", part.id.c_str(), part.item.c_str() ); part.fuel_type = "null"; } else if( part.fuel_type != "null" && item::find_type( part.fuel_type )->fuel == nullptr && ( base_item_type.container == nullptr || !base_item_type.container->watertight ) ) { // Tanks are allowed to specify non-fuel "fuel", // because currently legacy blazemod uses it as a hack to restrict content types debugmsg( "non-tank vehicle part %s uses non-fuel item %s as fuel, setting to null", part.id.c_str(), part.fuel_type.c_str() ); part.fuel_type = "null"; } if( part.has_flag( "TURRET" ) && base_item_type.gun == nullptr ) { debugmsg( "vehicle part %s has the TURRET flag, but is not made from a gun item", part.id.c_str() ); } for( auto &q : part.qualities ) { if( !q.first.is_valid() ) { debugmsg( "vehicle part %s has undefined tool quality %s", part.id.c_str(), q.first.c_str() ); } } if( part.has_flag( VPFLAG_ENABLED_DRAINS_EPOWER ) && part.epower == 0 ) { debugmsg( "%s is set to drain epower, but has epower == 0", part.id.c_str() ); } // Parts with non-zero epower must have a flag that affects epower usage static const std::vector<std::string> handled = {{ "ENABLED_DRAINS_EPOWER", "SECURITY", "ENGINE", "ALTERNATOR", "SOLAR_PANEL", "POWER_TRANSFER", "REACTOR" }}; if( part.epower != 0 && std::none_of( handled.begin(), handled.end(), [&part]( const std::string &flag ) { return part.has_flag( flag ); } ) ) { std::string warnings_are_good_docs = enumerate_as_string( handled ); debugmsg( "%s has non-zero epower, but lacks a flag that would make it affect epower (one of %s)", part.id.c_str(), warnings_are_good_docs.c_str() ); } } }
const recipe *select_crafting_recipe( int &batch_size ) { if( normalized_names.empty() ) { translate_all(); } const int headHeight = 3; const int subHeadHeight = 2; const int freeWidth = TERMX - FULL_SCREEN_WIDTH; bool isWide = ( TERMX > FULL_SCREEN_WIDTH && freeWidth > 15 ); const int width = isWide ? ( freeWidth > FULL_SCREEN_WIDTH ? FULL_SCREEN_WIDTH * 2 : TERMX ) : FULL_SCREEN_WIDTH; const int wStart = ( TERMX - width ) / 2; const int tailHeight = isWide ? 3 : 4; const int dataLines = TERMY - ( headHeight + subHeadHeight ) - tailHeight; const int dataHalfLines = dataLines / 2; const int dataHeight = TERMY - ( headHeight + subHeadHeight ); const int infoWidth = width - FULL_SCREEN_WIDTH - 1; const recipe *last_recipe = nullptr; catacurses::window w_head = catacurses::newwin( headHeight, width, 0, wStart ); catacurses::window w_subhead = catacurses::newwin( subHeadHeight, width, 3, wStart ); catacurses::window w_data = catacurses::newwin( dataHeight, width, headHeight + subHeadHeight, wStart ); int item_info_x = infoWidth; int item_info_y = dataHeight - 3; int item_info_width = wStart + width - infoWidth; int item_info_height = headHeight + subHeadHeight; if( !isWide ) { item_info_x = 1; item_info_y = 1; item_info_width = 1; item_info_height = 1; } catacurses::window w_iteminfo = catacurses::newwin( item_info_y, item_info_x, item_info_height, item_info_width ); 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; const int componentPrintHeight = dataHeight - tailHeight - 1; //preserves component color printout between mode rotations nc_color rotated_color = c_white; int previous_item_line = -1; std::string previous_tab; std::string previous_subtab; item tmp; int line = 0; int ypos = 0; int scroll_pos = 0; bool redraw = true; bool keepline = false; bool done = false; bool batch = false; bool show_hidden = false; int batch_line = 0; int display_mode = 0; const recipe *chosen = nullptr; std::vector<iteminfo> thisItem; std::vector<iteminfo> dummy; input_context ctxt( "CRAFTING" ); ctxt.register_cardinal(); ctxt.register_action( "QUIT" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "CYCLE_MODE" ); ctxt.register_action( "SCROLL_UP" ); ctxt.register_action( "SCROLL_DOWN" ); ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "FILTER" ); ctxt.register_action( "RESET_FILTER" ); ctxt.register_action( "TOGGLE_FAVORITE" ); ctxt.register_action( "HELP_RECIPE" ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "CYCLE_BATCH" ); ctxt.register_action( "RELATED_RECIPES" ); ctxt.register_action( "HIDE_SHOW_RECIPE" ); const inventory &crafting_inv = g->u.crafting_inventory(); const std::vector<npc *> helpers = g->u.get_crafting_helpers(); std::string filterstring; const auto &available_recipes = g->u.get_available_recipes( crafting_inv, &helpers ); std::map<const recipe *, bool> availability_cache; do { if( redraw ) { // When we switch tabs, redraw the header redraw = false; if( ! keepline ) { line = 0; } else { keepline = false; } if( display_mode > 2 ) { display_mode = 2; } TAB_MODE m = ( batch ) ? BATCH : ( filterstring.empty() ) ? NORMAL : FILTERED; draw_recipe_tabs( w_head, tab.cur(), m ); draw_recipe_subtabs( w_subhead, tab.cur(), subtab.cur(), available_recipes, m ); show_hidden = false; available.clear(); if( batch ) { current.clear(); for( int i = 1; i <= 20; i++ ) { current.push_back( chosen ); available.push_back( chosen->requirements().can_make_with_inventory( crafting_inv, i ) ); } } else { std::vector<const recipe *> picking; if( !filterstring.empty() ) { auto qry = trim( filterstring ); if( qry.size() > 2 && qry[1] == ':' ) { switch( qry[0] ) { case 't': picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::tool ); break; case 'c': picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::component ); break; case 's': picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::skill ); break; case 'p': picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::primary_skill ); break; case 'Q': picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::quality ); break; case 'q': picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::quality_result ); break; case 'd': picking = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::description_result ); break; case 'm': { auto &learned = g->u.get_learned_recipes(); if( query_is_yes( qry ) ) { std::set_intersection( available_recipes.begin(), available_recipes.end(), learned.begin(), learned.end(), std::back_inserter( picking ) ); } else { std::set_difference( available_recipes.begin(), available_recipes.end(), learned.begin(), learned.end(), std::back_inserter( picking ) ); } break; } case 'h': { std::copy( available_recipes.begin(), available_recipes.end(), std::back_inserter( picking ) ); if( query_is_yes( qry ) ) { show_hidden = true; } break; } default: current.clear(); } } else { picking = available_recipes.search( qry ); } } else if( subtab.cur() == "CSC_*_FAVORITE" ) { picking = available_recipes.favorite(); } else if( subtab.cur() == "CSC_*_RECENT" ) { picking = available_recipes.recent(); } else { picking = available_recipes.in_category( tab.cur(), subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ); } current.clear(); for( auto i : picking ) { if( ( uistate.hidden_recipes.find( i->ident() ) != uistate.hidden_recipes.end() ) == show_hidden ) { current.push_back( i ); } } if( !show_hidden ) { draw_hidden_amount( w_head, 0, picking.size() - current.size() ); } available.reserve( current.size() ); // cache recipe availability on first display for( const auto e : current ) { if( !availability_cache.count( e ) ) { availability_cache.emplace( e, e->requirements().can_make_with_inventory( crafting_inv ) ); } } if( subtab.cur() != "CSC_*_RECENT" ) { 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::transform( current.begin(), current.end(), std::back_inserter( available ), [&]( const recipe * e ) { return availability_cache[e]; } ); } // current/available have been rebuilt, make sure our cursor is still in range if( current.empty() ) { line = 0; } else { line = std::min( line, static_cast<int>( current.size() ) - 1 ); } } // Clear the screen of recipe data, and draw it anew werase( w_data ); if( isWide ) { werase( w_iteminfo ); } if( isWide ) { mvwprintz( w_data, dataLines + 1, 5, c_white, _( "Press <ENTER> to attempt to craft object." ) ); wprintz( w_data, c_white, " " ); if( !filterstring.empty() ) { wprintz( w_data, c_white, _( "[E]: Describe, [F]ind, [R]eset, [m]ode, [s]how/hide, Re[L]ated, [*]Favorite, %s [?] keybindings" ), ( batch ) ? _( "cancel [b]atch" ) : _( "[b]atch" ) ); } else { wprintz( w_data, c_white, _( "[E]: Describe, [F]ind, [m]ode, [s]how/hide, Re[L]ated, [*]Favorite, %s [?] keybindings" ), ( batch ) ? _( "cancel [b]atch" ) : _( "[b]atch" ) ); } } else { if( !filterstring.empty() ) { mvwprintz( w_data, dataLines + 1, 5, c_white, _( "[E]: Describe, [F]ind, [R]eset, [m]ode, [s]how/hide, Re[L]ated, [*]Favorite, [b]atch [?] keybindings" ) ); } else { mvwprintz( w_data, dataLines + 1, 5, c_white, _( "[E]: Describe, [F]ind, [m]ode, [s]how/hide, Re[L]ated, [*]Favorite, [b]atch [?] keybindings" ) ); } mvwprintz( w_data, dataLines + 2, 5, c_white, _( "Press <ENTER> to attempt to craft object." ) ); } // Draw borders for( int i = 1; i < width - 1; ++i ) { // _ mvwputch( w_data, dataHeight - 1, i, BORDER_COLOR, LINE_OXOX ); } for( int i = 0; i < dataHeight - 1; ++i ) { // | mvwputch( w_data, i, 0, BORDER_COLOR, LINE_XOXO ); mvwputch( w_data, i, width - 1, BORDER_COLOR, LINE_XOXO ); } mvwputch( w_data, dataHeight - 1, 0, BORDER_COLOR, LINE_XXOO ); // _| mvwputch( w_data, dataHeight - 1, width - 1, BORDER_COLOR, LINE_XOOX ); // |_ int recmin = 0, recmax = current.size(); if( recmax > dataLines ) { if( line <= recmin + dataHalfLines ) { for( int i = recmin; i < recmin + dataLines; ++i ) { std::string tmp_name = current[i]->result_name(); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, i - recmin, 2, c_dark_gray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, i - recmin, 2, ( available[i] ? h_white : h_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, i - recmin, 2, ( available[i] ? c_white : c_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } else if( line >= recmax - dataHalfLines ) { for( int i = recmax - dataLines; i < recmax; ++i ) { std::string tmp_name = current[i]->result_name(); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, dataLines + i - recmax, 2, c_light_gray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, dataLines + i - recmax, 2, ( available[i] ? h_white : h_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, dataLines + i - recmax, 2, ( available[i] ? c_white : c_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } else { for( int i = line - dataHalfLines; i < line - dataHalfLines + dataLines; ++i ) { std::string tmp_name = current[i]->result_name(); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, dataHalfLines + i - line, 2, c_light_gray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, dataHalfLines + i - line, 2, ( available[i] ? h_white : h_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, dataHalfLines + i - line, 2, ( available[i] ? c_white : c_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } } else { for( size_t i = 0; i < current.size() && i < static_cast<size_t>( 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.c_str() ); } if( static_cast<int>( i ) == line ) { mvwprintz( w_data, i, 2, ( available[i] ? h_white : h_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, i, 2, ( available[i] ? c_white : c_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } 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; const auto &req = current[ line ]->requirements(); draw_can_craft_indicator( w_head, 0, *current[line] ); wrefresh( w_head ); ypos = 0; auto qry = trim( filterstring ); std::string qry_comps; if( qry.compare( 0, 2, "c:" ) == 0 ) { qry_comps = qry.substr( 2 ); } std::vector<std::string> component_print_buffer; auto tools = req.get_folded_tools_list( pane, col, crafting_inv, count ); auto comps = req.get_folded_components_list( pane, col, crafting_inv, count, qry_comps ); component_print_buffer.insert( component_print_buffer.end(), tools.begin(), tools.end() ); component_print_buffer.insert( component_print_buffer.end(), comps.begin(), comps.end() ); if( !g->u.knows_recipe( current[line] ) ) { component_print_buffer.push_back( _( "Recipe not memorized yet" ) ); auto books_with_recipe = g->u.get_books_for_recipe( crafting_inv, current[line] ); std::string enumerated_books = enumerate_as_string( books_with_recipe.begin(), books_with_recipe.end(), []( itype_id type_id ) { return item::find_type( type_id )->nname( 1 ); } ); const std::string text = string_format( _( "Written in: %s" ), enumerated_books.c_str() ); std::vector<std::string> folded_lines = foldstring( text, pane ); component_print_buffer.insert( component_print_buffer.end(), folded_lines.begin(), folded_lines.end() ); } //handle positioning of component list if it needed to be scrolled int componentPrintOffset = 0; if( display_mode > 2 ) { componentPrintOffset = ( display_mode - 2 ) * componentPrintHeight; } if( component_print_buffer.size() < static_cast<size_t>( componentPrintOffset ) ) { componentPrintOffset = 0; if( previous_tab != tab.cur() || previous_subtab != subtab.cur() || previous_item_line != line ) { display_mode = 2; } else { display_mode = 0; } } //only used to preserve mode position on components when //moving to another item and the view is already scrolled previous_tab = tab.cur(); previous_subtab = subtab.cur(); previous_item_line = line; const int xpos = 30; if( display_mode == 0 ) { const int width = getmaxx( w_data ) - xpos - item_info_x; print_colored_text( w_data, ypos++, xpos, col, col, string_format( _( "Primary skill used: <color_cyan>%s</color>" ), ( !current[line]->skill_used ? _( "N/A" ) : current[line]->skill_used.obj().name() ) ) ); auto player_skill = g->u.get_skill_level( current[line]->skill_used ); std::string difficulty_color = current[ line ]->difficulty > player_skill ? "yellow" : "green"; print_colored_text( w_data, ypos++, xpos, col, col, string_format( _( "Difficulty: <color_%s>%d</color>" ), difficulty_color, current[ line ]->difficulty ) ); std::string skill_level_string = current[line]->skill_used ? string_format( _( "Your skill level: <color_%s>%d</color>" ), difficulty_color, player_skill ) : _( "Your skill level: <color_yellow>N/A</color>" ); print_colored_text( w_data, ypos++, xpos, col, col, skill_level_string ); ypos += fold_and_print( w_data, ypos, xpos, width, col, _( "Other skills used: %s" ), current[line]->required_skills_string( &g->u ) ); const int expected_turns = g->u.expected_time_to_craft( *current[line], count ) / to_moves<int>( 1_turns ); ypos += fold_and_print( w_data, ypos, xpos, pane, col, _( "Time to complete: <color_cyan>%s</color>" ), to_string( time_duration::from_turns( expected_turns ) ) ); print_colored_text( w_data, ypos++, xpos, col, col, string_format( _( "Dark craftable? <color_cyan>%s</color>" ), current[line]->has_flag( "BLIND_EASY" ) ? _( "Easy" ) : current[line]->has_flag( "BLIND_HARD" ) ? _( "Hard" ) : _( "Impossible" ) ) ); ypos += print_items( *current[line], w_data, ypos, xpos, col, batch ? line + 1 : 1 ); } //color needs to be preserved in case part of the previous page was cut off nc_color stored_color = col; if( display_mode > 2 ) { stored_color = rotated_color; } else { rotated_color = col; } int components_printed = 0; for( size_t i = static_cast<size_t>( componentPrintOffset ); i < component_print_buffer.size(); i++ ) { if( ypos >= componentPrintHeight ) { break; } components_printed++; print_colored_text( w_data, ypos++, xpos, stored_color, col, component_print_buffer[i] ); } if( ypos >= componentPrintHeight && component_print_buffer.size() > static_cast<size_t>( components_printed ) ) { mvwprintz( w_data, ypos++, xpos, col, _( "v (more)" ) ); rotated_color = stored_color; } if( isWide ) { if( last_recipe != current[line] ) { last_recipe = current[line]; tmp = current[line]->create_result(); } tmp.info( true, thisItem, count ); draw_item_info( w_iteminfo, tmp.tname(), tmp.type_name(), thisItem, dummy, scroll_pos, true, true, true, false, true ); } } draw_scrollbar( w_data, line, dataLines, recmax, 0 ); wrefresh( w_data ); if( isWide ) { wrefresh( w_iteminfo ); } const std::string action = ctxt.handle_input(); if( action == "CYCLE_MODE" ) { display_mode = display_mode + 1; if( display_mode <= 0 ) { display_mode = 0; } } else if( action == "LEFT" ) { std::string start = subtab.cur(); do { subtab.prev(); } while( subtab.cur() != start && available_recipes.empty_category( tab.cur(), subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ) ); redraw = true; } else if( action == "SCROLL_UP" ) { scroll_pos--; } else if( action == "SCROLL_DOWN" ) { scroll_pos++; } else if( action == "PREV_TAB" ) { tab.prev(); subtab = list_circularizer<std::string>( craft_subcat_list[tab.cur()] );//default ALL redraw = true; } else if( action == "RIGHT" ) { std::string start = subtab.cur(); do { subtab.next(); } while( subtab.cur() != start && available_recipes.empty_category( tab.cur(), subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ) ); redraw = true; } else if( action == "NEXT_TAB" ) { tab.next(); subtab = list_circularizer<std::string>( craft_subcat_list[tab.cur()] );//default ALL redraw = true; } else if( action == "DOWN" ) { line++; } else if( action == "UP" ) { line--; } else if( action == "CONFIRM" ) { if( available.empty() || !available[line] ) { popup( _( "You can't do that!" ) ); } else if( !g->u.check_eligible_containers_for_crafting( *current[line], ( batch ) ? line + 1 : 1 ) ) { // popup is already inside check } else { chosen = current[line]; batch_size = ( batch ) ? line + 1 : 1; done = true; } } else if( action == "HELP_RECIPE" ) { if( current.empty() ) { popup( _( "Nothing selected!" ) ); redraw = true; continue; } tmp = current[line]->create_result(); full_screen_popup( "%s\n%s", tmp.type_name( 1 ).c_str(), tmp.info( true ).c_str() ); redraw = true; keepline = true; } else if( action == "FILTER" ) { struct SearchPrefix { char key; std::string example; std::string description; }; std::vector<SearchPrefix> prefixes = { { 'q', _( "metal sawing" ), _( "<color_cyan>quality</color> of resulting item" ) }, //~ Example result description search term { 'd', _( "reach attack" ), _( "<color_cyan>full description</color> of resulting item (slow)" ) }, { 'c', _( "two by four" ), _( "<color_cyan>component</color> required to craft" ) }, { 'p', _( "tailoring" ), _( "<color_cyan>primary skill</color> used to craft" ) }, { 's', _( "cooking" ), _( "<color_cyan>any skill</color> used to craft" ) }, { 'Q', _( "fine bolt turning" ), _( "<color_cyan>quality</color> required to craft" ) }, { 't', _( "soldering iron" ), _( "<color_cyan>tool</color> required to craft" ) }, { 'h', _( "yes" ), _( "recipes which are <color_cyan>hidden</color> or not" ) }, { 'm', _( "no" ), _( "recipes which are <color_cyan>memorized</color> or not" ) }, }; int max_example_length = 0; for( const auto &prefix : prefixes ) { max_example_length = std::max( max_example_length, utf8_width( prefix.example ) ); } std::string spaces( max_example_length, ' ' ); std::string description = _( "The default is to search result names. Some single-character prefixes " "can be used with a colon (:) to search in other ways.\n" "\n" "<color_white>Examples:</color>\n" ); { std::string example_name = _( "shirt" ); auto padding = max_example_length - utf8_width( example_name ); description += string_format( _( " <color_white>%s</color>%.*s %s\n" ), example_name, padding, spaces, _( "<color_cyan>name</color> of resulting item" ) ); } for( const auto &prefix : prefixes ) { auto padding = max_example_length - utf8_width( prefix.example ); description += string_format( _( " <color_yellow>%c</color><color_white>:%s</color>%.*s %s\n" ), prefix.key, prefix.example, padding, spaces, prefix.description ); } string_input_popup() .title( _( "Search:" ) ) .width( 85 ) .description( description ) .desc_color( c_light_gray ) .edit( filterstring ); redraw = true; } else if( action == "QUIT" ) { chosen = nullptr; done = true; } else if( action == "RESET_FILTER" ) { filterstring.clear(); redraw = true; } else if( action == "CYCLE_BATCH" ) { if( current.empty() ) { popup( _( "Nothing selected!" ) ); redraw = true; continue; } batch = !batch; if( batch ) { batch_line = line; chosen = current[batch_line]; } else { line = batch_line; keepline = true; } redraw = true; } else if( action == "TOGGLE_FAVORITE" ) { keepline = true; redraw = true; if( current.empty() ) { popup( _( "Nothing selected!" ) ); continue; } if( uistate.favorite_recipes.find( current[line]->ident() ) != uistate.favorite_recipes.end() ) { uistate.favorite_recipes.erase( current[line]->ident() ); } else { uistate.favorite_recipes.insert( current[line]->ident() ); } } else if( action == "HIDE_SHOW_RECIPE" ) { if( current.empty() ) { popup( _( "Nothing selected!" ) ); redraw = true; continue; } if( show_hidden ) { uistate.hidden_recipes.erase( current[line]->ident() ); } else { uistate.hidden_recipes.insert( current[line]->ident() ); } redraw = true; } else if( action == "RELATED_RECIPES" ) { if( current.empty() ) { popup( _( "Nothing selected!" ) ); redraw = true; continue; } std::string recipe_name = peek_related_recipe( current[ line ], available_recipes ); if( recipe_name.empty() ) { keepline = true; } else { filterstring = recipe_name; } redraw = true; } if( line < 0 ) { line = current.size() - 1; } else if( line >= static_cast<int>( current.size() ) ) { line = 0; } } while( !done ); return chosen; }
const recipe *select_crafting_recipe( int &batch_size ) { if( normalized_names.empty() ) { translate_all(); } const int headHeight = 3; const int subHeadHeight = 2; const int freeWidth = TERMX - FULL_SCREEN_WIDTH; bool isWide = ( TERMX > FULL_SCREEN_WIDTH && freeWidth > 15 ); const int width = isWide ? ( freeWidth > FULL_SCREEN_WIDTH ? FULL_SCREEN_WIDTH * 2 : TERMX ) : FULL_SCREEN_WIDTH; const int wStart = ( TERMX - width ) / 2; const int tailHeight = isWide ? 3 : 4; const int dataLines = TERMY - ( headHeight + subHeadHeight ) - tailHeight; const int dataHalfLines = dataLines / 2; const int dataHeight = TERMY - ( headHeight + subHeadHeight ); const int infoWidth = width - FULL_SCREEN_WIDTH - 1; const recipe *last_recipe = nullptr; catacurses::window w_head = catacurses::newwin( headHeight, width, 0, wStart ); catacurses::window w_subhead = catacurses::newwin( subHeadHeight, width, 3, wStart ); catacurses::window w_data = catacurses::newwin( dataHeight, width, headHeight + subHeadHeight, wStart ); int item_info_x = infoWidth; int item_info_y = dataHeight - 3; int item_info_width = wStart + width - infoWidth; int item_info_height = headHeight + subHeadHeight; if( !isWide ) { item_info_x = 1; item_info_y = 1; item_info_width = 1; item_info_height = 1; } catacurses::window w_iteminfo = catacurses::newwin( item_info_y, item_info_x, item_info_height, item_info_width ); 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; const int componentPrintHeight = dataHeight - tailHeight - 1; //preserves component color printout between mode rotations nc_color rotated_color = c_white; int previous_item_line = -1; std::string previous_tab = ""; std::string previous_subtab = ""; item tmp; int line = 0, ypos, scroll_pos = 0; bool redraw = true; bool keepline = false; bool done = false; bool batch = false; int batch_line = 0; int display_mode = 0; const recipe *chosen = NULL; std::vector<iteminfo> thisItem, dummy; input_context ctxt( "CRAFTING" ); ctxt.register_cardinal(); ctxt.register_action( "QUIT" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "CYCLE_MODE" ); ctxt.register_action( "SCROLL_UP" ); ctxt.register_action( "SCROLL_DOWN" ); ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "FILTER" ); ctxt.register_action( "RESET_FILTER" ); ctxt.register_action( "HELP_RECIPE" ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "CYCLE_BATCH" ); const inventory &crafting_inv = g->u.crafting_inventory(); const std::vector<npc *> helpers = g->u.get_crafting_helpers(); std::string filterstring = ""; const auto &available_recipes = g->u.get_available_recipes( crafting_inv, &helpers ); std::map<const recipe *, bool> availability_cache; do { if( redraw ) { // When we switch tabs, redraw the header redraw = false; if( ! keepline ) { line = 0; } else { keepline = false; } if( display_mode > 2 ) { display_mode = 2; } TAB_MODE m = ( batch ) ? BATCH : ( filterstring.empty() ) ? NORMAL : FILTERED; draw_recipe_tabs( w_head, tab.cur(), m ); draw_recipe_subtabs( w_subhead, tab.cur(), subtab.cur(), available_recipes, m ); available.clear(); if( batch ) { current.clear(); for( int i = 1; i <= 20; i++ ) { current.push_back( chosen ); available.push_back( chosen->requirements().can_make_with_inventory( crafting_inv, i ) ); } } else { if( filterstring.empty() ) { current = available_recipes.in_category( tab.cur(), subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ); } else { auto qry = trim( filterstring ); if( qry.size() > 2 && qry[1] == ':' ) { switch( qry[0] ) { case 't': current = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::tool ); break; case 'c': current = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::component ); break; case 's': current = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::skill ); break; case 'q': current = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::quality ); break; case 'Q': current = available_recipes.search( qry.substr( 2 ), recipe_subset::search_type::quality_result ); break; case 'm': { auto &learned = g->u.get_learned_recipes(); current.clear(); if( ( qry.substr( 2 ) == "yes" ) || ( qry.substr( 2 ) == "y" ) || ( qry.substr( 2 ) == "1" ) || ( qry.substr( 2 ) == "true" ) || ( qry.substr( 2 ) == "t" ) || ( qry.substr( 2 ) == "on" ) ) { std::set_intersection( available_recipes.begin(), available_recipes.end(), learned.begin(), learned.end(), std::back_inserter( current ) ); } else { std::set_difference( available_recipes.begin(), available_recipes.end(), learned.begin(), learned.end(), std::back_inserter( current ) ); } } break; default: current.clear(); } } else { current = available_recipes.search( qry ); } } available.reserve( current.size() ); // cache recipe availability on first display for( const auto e : current ) { if( !availability_cache.count( e ) ) { availability_cache.emplace( e, e->requirements().can_make_with_inventory( crafting_inv ) ); } } 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::transform( current.begin(), current.end(), std::back_inserter( available ), [&]( const recipe * e ) { return availability_cache[e]; } ); } // current/available have been rebuilt, make sure our cursor is still in range if( current.empty() ) { line = 0; } else { line = std::min( line, ( int )current.size() - 1 ); } } // Clear the screen of recipe data, and draw it anew werase( w_data ); if( isWide ) { werase( w_iteminfo ); } if( isWide ) { mvwprintz( w_data, dataLines + 1, 5, c_white, _( "Press <ENTER> to attempt to craft object." ) ); wprintz( w_data, c_white, " " ); if( !filterstring.empty() ) { wprintz( w_data, c_white, _( "[E]: Describe, [F]ind, [R]eset, [m]ode, %s [?] keybindings" ), ( batch ) ? _( "cancel [b]atch" ) : _( "[b]atch" ) ); } else { wprintz( w_data, c_white, _( "[E]: Describe, [F]ind, [m]ode, %s [?] keybindings" ), ( batch ) ? _( "cancel [b]atch" ) : _( "[b]atch" ) ); } } else { if( !filterstring.empty() ) { mvwprintz( w_data, dataLines + 1, 5, c_white, _( "[E]: Describe, [F]ind, [R]eset, [m]ode, [b]atch [?] keybindings" ) ); } else { mvwprintz( w_data, dataLines + 1, 5, c_white, _( "[E]: Describe, [F]ind, [m]ode, [b]atch [?] keybindings" ) ); } mvwprintz( w_data, dataLines + 2, 5, c_white, _( "Press <ENTER> to attempt to craft object." ) ); } // Draw borders for( int i = 1; i < width - 1; ++i ) { // _ mvwputch( w_data, dataHeight - 1, i, BORDER_COLOR, LINE_OXOX ); } for( int i = 0; i < dataHeight - 1; ++i ) { // | mvwputch( w_data, i, 0, BORDER_COLOR, LINE_XOXO ); mvwputch( w_data, i, width - 1, BORDER_COLOR, LINE_XOXO ); } mvwputch( w_data, dataHeight - 1, 0, BORDER_COLOR, LINE_XXOO ); // _| mvwputch( w_data, dataHeight - 1, width - 1, BORDER_COLOR, LINE_XOOX ); // |_ int recmin = 0, recmax = current.size(); if( recmax > dataLines ) { if( line <= recmin + dataHalfLines ) { for( int i = recmin; i < recmin + dataLines; ++i ) { std::string tmp_name = current[i]->result_name(); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, i - recmin, 2, c_dark_gray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, i - recmin, 2, ( available[i] ? h_white : h_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, i - recmin, 2, ( available[i] ? c_white : c_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } else if( line >= recmax - dataHalfLines ) { for( int i = recmax - dataLines; i < recmax; ++i ) { std::string tmp_name = current[i]->result_name(); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, dataLines + i - recmax, 2, c_light_gray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, dataLines + i - recmax, 2, ( available[i] ? h_white : h_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, dataLines + i - recmax, 2, ( available[i] ? c_white : c_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } else { for( int i = line - dataHalfLines; i < line - dataHalfLines + dataLines; ++i ) { std::string tmp_name = current[i]->result_name(); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, dataHalfLines + i - line, 2, c_light_gray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, dataHalfLines + i - line, 2, ( available[i] ? h_white : h_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, dataHalfLines + i - line, 2, ( available[i] ? c_white : c_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } } else { for( size_t i = 0; i < current.size() && i < ( size_t )dataHeight + 1; ++i ) { std::string tmp_name = current[i]->result_name(); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), ( int )i + 1, tmp_name.c_str() ); } if( ( int )i == line ) { mvwprintz( w_data, i, 2, ( available[i] ? h_white : h_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, i, 2, ( available[i] ? c_white : c_dark_gray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } 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; const auto &req = current[ line ]->requirements(); draw_can_craft_indicator( w_head, 0, *current[line] ); wrefresh( w_head ); ypos = 0; auto qry = trim( filterstring ); std::string qry_comps; if( qry.compare( 0, 2, "c:" ) == 0 ) { qry_comps = qry.substr( 2 ); } std::vector<std::string> component_print_buffer; auto tools = req.get_folded_tools_list( pane, col, crafting_inv, count ); auto comps = req.get_folded_components_list( pane, col, crafting_inv, count, qry_comps ); component_print_buffer.insert( component_print_buffer.end(), tools.begin(), tools.end() ); component_print_buffer.insert( component_print_buffer.end(), comps.begin(), comps.end() ); if( !g->u.knows_recipe( current[line] ) ) { component_print_buffer.push_back( _( "Recipe not memorized yet" ) ); auto books_with_recipe = g->u.get_books_for_recipe( crafting_inv, current[line] ); std::string enumerated_books = enumerate_as_string( books_with_recipe.begin(), books_with_recipe.end(), []( itype_id type_id ) { return item::find_type( type_id )->nname( 1 ); } ); const std::string text = string_format( _( "Written in: %s" ), enumerated_books.c_str() ); std::vector<std::string> folded_lines = foldstring( text, pane ); component_print_buffer.insert( component_print_buffer.end(), folded_lines.begin(), folded_lines.end() ); } //handle positioning of component list if it needed to be scrolled int componentPrintOffset = 0; if( display_mode > 2 ) { componentPrintOffset = ( display_mode - 2 ) * componentPrintHeight; } if( component_print_buffer.size() < static_cast<size_t>( componentPrintOffset ) ) { componentPrintOffset = 0; if( previous_tab != tab.cur() || previous_subtab != subtab.cur() || previous_item_line != line ) { display_mode = 2; } else { display_mode = 0; } } //only used to preserve mode position on components when //moving to another item and the view is already scrolled previous_tab = tab.cur(); previous_subtab = subtab.cur(); previous_item_line = line; const int xpos = 30; if( display_mode == 0 ) { const int width = getmaxx( w_data ) - xpos - item_info_x; mvwprintz( w_data, ypos++, xpos, col, _( "Skills used: %s" ), ( !current[line]->skill_used ? _( "N/A" ) : current[line]->skill_used.obj().name().c_str() ) ); ypos += fold_and_print( w_data, ypos, xpos, width, col, _( "Required skills: %s" ), current[line]->required_skills_string().c_str() ); mvwprintz( w_data, ypos++, xpos, col, _( "Difficulty: %d" ), current[ line ]->difficulty ); if( !current[line]->skill_used ) { mvwprintz( w_data, ypos++, xpos, col, _( "Your skill level: N/A" ) ); } else { mvwprintz( w_data, ypos++, xpos, col, _( "Your skill level: %d" ), g->u.get_skill_level( current[line]->skill_used ) ); } const int expected_turns = g->u.expected_time_to_craft( *current[line], count ) / MOVES( 1 ); ypos += fold_and_print( w_data, ypos, xpos, pane, col, _( "Time to complete: %s" ), to_string( time_duration::from_turns( expected_turns ) ) ); mvwprintz( w_data, ypos++, xpos, col, _( "Dark craftable? %s" ), current[line]->has_flag( "BLIND_EASY" ) ? _( "Easy" ) : current[line]->has_flag( "BLIND_HARD" ) ? _( "Hard" ) : _( "Impossible" ) ); ypos += print_items( *current[line], w_data, ypos, xpos, col, batch ? line + 1 : 1 ); } //color needs to be preserved in case part of the previous page was cut off nc_color stored_color = col; if( display_mode > 2 ) { stored_color = rotated_color; } else { rotated_color = col; } int components_printed = 0; for( size_t i = static_cast<size_t>( componentPrintOffset ); i < component_print_buffer.size(); i++ ) { if( ypos >= componentPrintHeight ) { break; } components_printed++; print_colored_text( w_data, ypos++, xpos, stored_color, col, component_print_buffer[i] ); } if( ypos >= componentPrintHeight && component_print_buffer.size() > static_cast<size_t>( components_printed ) ) { mvwprintz( w_data, ypos++, xpos, col, _( "v (more)" ) ); rotated_color = stored_color; } if( isWide ) { if( last_recipe != current[line] ) { last_recipe = current[line]; tmp = current[line]->create_result(); } tmp.info( true, thisItem, count ); draw_item_info( w_iteminfo, tmp.tname(), tmp.type_name(), thisItem, dummy, scroll_pos, true, true, true, false, true ); } } draw_scrollbar( w_data, line, dataLines, recmax, 0 ); wrefresh( w_data ); if( isWide ) { wrefresh( w_iteminfo ); } const std::string action = ctxt.handle_input(); if( action == "CYCLE_MODE" ) { display_mode = display_mode + 1; if( display_mode <= 0 ) { display_mode = 0; } } else if( action == "LEFT" ) { std::string start = subtab.cur(); do { subtab.prev(); } while( subtab.cur() != start && available_recipes.empty_category( tab.cur(), subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ) ); redraw = true; } else if( action == "SCROLL_UP" ) { scroll_pos--; } else if( action == "SCROLL_DOWN" ) { scroll_pos++; } else if( action == "PREV_TAB" ) { tab.prev(); subtab = list_circularizer<std::string>( craft_subcat_list[tab.cur()] );//default ALL redraw = true; } else if( action == "RIGHT" ) { std::string start = subtab.cur(); do { subtab.next(); } while( subtab.cur() != start && available_recipes.empty_category( tab.cur(), subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ) ); redraw = true; } else if( action == "NEXT_TAB" ) { tab.next(); subtab = list_circularizer<std::string>( craft_subcat_list[tab.cur()] );//default ALL redraw = true; } else if( action == "DOWN" ) { line++; } else if( action == "UP" ) { line--; } else if( action == "CONFIRM" ) { if( available.empty() || !available[line] ) { popup( _( "You can't do that!" ) ); } else if( !g->u.check_eligible_containers_for_crafting( *current[line], ( batch ) ? line + 1 : 1 ) ) { ; // popup is already inside check } else { chosen = current[line]; batch_size = ( batch ) ? line + 1 : 1; done = true; } } else if( action == "HELP_RECIPE" ) { if( current.empty() ) { popup( _( "Nothing selected!" ) ); redraw = true; continue; } tmp = current[line]->create_result(); full_screen_popup( "%s\n%s", tmp.type_name( 1 ).c_str(), tmp.info( true ).c_str() ); redraw = true; keepline = true; } else if( action == "FILTER" ) { string_input_popup() .title( _( "Search:" ) ) .width( 85 ) .description( _( "Special prefixes for requirements:\n" " [t] search tools\n" " [c] search components\n" " [q] search qualities\n" " [s] search skills\n" "Special prefixes for results:\n" " [Q] search qualities\n" "Other:\n" " [m] search for memorized or not\n" "Examples:\n" " t:soldering iron\n" " c:two by four\n" " q:metal sawing\n" " s:cooking\n" " Q:fine bolt turning\n" " m:no" ) ) .edit( filterstring ); redraw = true; } else if( action == "QUIT" ) { chosen = nullptr; done = true; } else if( action == "RESET_FILTER" ) { filterstring.clear(); redraw = true; } else if( action == "CYCLE_BATCH" ) { if( current.empty() ) { popup( _( "Nothing selected!" ) ); redraw = true; continue; } batch = !batch; if( batch ) { batch_line = line; chosen = current[batch_line]; } else { line = batch_line; keepline = true; } redraw = true; } if( line < 0 ) { line = current.size() - 1; } else if( line >= ( int )current.size() ) { line = 0; } } while( !done ); return chosen; }