//note that passing in iteminfo instances with sType == "MENU" or "DESCRIPTION" does special things //if sType == "MENU", sFmt == "iOffsetY" or "iOffsetX" also do special things //otherwise if sType == "MENU", dValue can be used to control color //all this should probably be cleaned up at some point, rather than using a function for things it wasn't meant for // well frack, half the game uses it so: optional (int)selected argument causes entry highlight, and enter to return entry's key. Also it now returns int //@param without_getch don't wait getch, return = (int)' '; int draw_item_info(const int iLeft, const int iWidth, const int iTop, const int iHeight, const std::string sItemName, std::vector<iteminfo> &vItemDisplay, std::vector<iteminfo> &vItemCompare, const int selected, const bool without_getch, const bool without_border) { WINDOW *win = newwin(iHeight, iWidth, iTop + VIEW_OFFSET_Y, iLeft + VIEW_OFFSET_X); return draw_item_info(win, sItemName, vItemDisplay, vItemCompare, selected, without_getch, without_border); }
void game::compare( const tripoint &offset ) { const tripoint examp = u.pos3() + offset; std::vector<std::list<item>> grounditems; indexed_invslice grounditems_slice; if( !m.has_flag( "SEALED", g->u.pos() ) ) { auto here = m.i_at( examp ); //Filter out items with the same name (keep only one of them) std::set<std::string> dups; for (size_t i = 0; i < here.size(); i++) { if (dups.count(here[i].tname()) == 0) { grounditems.push_back(std::list<item>(1, here[i])); //Only the first 10 items get a invlet if ( grounditems.size() <= 10 ) { // invlet: '0' ... '9' grounditems.back().front().invlet = '0' + grounditems.size() - 1; } dups.insert(here[i].tname()); } } for (size_t a = 0; a < grounditems.size(); a++) { // avoid INT_MIN, as it can be confused with "no item at all" grounditems_slice.push_back(indexed_invslice::value_type(&grounditems[a], INT_MIN + a + 1)); } } static const item_category category_on_ground( "GROUND:", _("GROUND:"), -1000 ); u.inv.restack(&u); u.inv.sort(); const indexed_invslice stacks = u.inv.slice_filter(); inventory_selector inv_s(false, true, _("Compare:")); inv_s.make_item_list(grounditems_slice, &category_on_ground); inv_s.make_item_list(stacks); inv_s.prepare_paging(); inventory_selector::drop_map prev_droppings; while(true) { inv_s.display(); const std::string action = inv_s.ctxt.handle_input(); const long ch = inv_s.ctxt.get_raw_input().get_first_input(); const int item_pos = g->u.invlet_to_position( ch ); if (item_pos != INT_MIN) { inv_s.set_to_drop(item_pos, 0); } else if (ch >= '0' && ch <= '9' && (size_t) (ch - '0') < grounditems_slice.size()) { const int ip = ch - '0'; inv_s.set_drop_count(INT_MIN + 1 + ip, 0, grounditems_slice[ip].first->front()); } else if (inv_s.handle_movement(action)) { // continue with comparison below } else if (action == "QUIT") { break; } else if (action == "RIGHT") { inv_s.set_selected_to_drop(0); } if (inv_s.second_item != NULL) { std::vector<iteminfo> vItemLastCh, vItemCh; std::string sItemLastCh, sItemCh, sItemTn; inv_s.first_item->info(true, vItemCh); sItemCh = inv_s.first_item->tname(); sItemTn = inv_s.first_item->type_name(); inv_s.second_item->info(true, vItemLastCh); sItemLastCh = inv_s.second_item->tname(); int iScrollPos = 0; int iScrollPosLast = 0; int ch = (int)' '; do { draw_item_info(0, (TERMX - VIEW_OFFSET_X * 2) / 2, 0, TERMY - VIEW_OFFSET_Y * 2, sItemLastCh, sItemTn, vItemLastCh, vItemCh, iScrollPosLast, true); //without getch() ch = draw_item_info((TERMX - VIEW_OFFSET_X * 2) / 2, (TERMX - VIEW_OFFSET_X * 2) / 2, 0, TERMY - VIEW_OFFSET_Y * 2, sItemCh, sItemTn, vItemCh, vItemLastCh, iScrollPos); if ( ch == KEY_PPAGE ) { iScrollPos--; iScrollPosLast--; } else if ( ch == KEY_NPAGE ) { iScrollPos++; iScrollPosLast++; } } while (ch == KEY_PPAGE || ch == KEY_NPAGE); inv_s.dropping = prev_droppings; inv_s.second_item = NULL; } else { prev_droppings = inv_s.dropping; } } }
void game::compare( const tripoint &offset ) { u.inv.restack( &u ); u.inv.sort(); inventory_compare_selector inv_s( u ); inv_s.add_character_items( u ); inv_s.set_title( _( "Compare" ) ); inv_s.set_hint( _( "Select two items to compare them." ) ); if( offset != tripoint_min ) { inv_s.add_map_items( u.pos() + offset ); inv_s.add_vehicle_items( u.pos() + offset ); } else { inv_s.add_nearby_items(); } if( inv_s.empty() ) { popup( std::string( _( "There are no items to compare." ) ), PF_GET_KEY ); return; } do { const auto to_compare = inv_s.execute(); if( to_compare.first == nullptr || to_compare.second == nullptr ) { break; } std::vector<iteminfo> vItemLastCh, vItemCh; std::string sItemLastCh, sItemCh, sItemLastTn, sItemTn; to_compare.first->info( true, vItemCh ); sItemCh = to_compare.first->tname(); sItemTn = to_compare.first->type_name(); to_compare.second->info( true, vItemLastCh ); sItemLastCh = to_compare.second->tname(); sItemLastTn = to_compare.second->type_name(); int iScrollPos = 0; int iScrollPosLast = 0; int ch = ( int ) ' '; do { draw_item_info( 0, ( TERMX - VIEW_OFFSET_X * 2 ) / 2, 0, TERMY - VIEW_OFFSET_Y * 2, sItemLastCh, sItemLastTn, vItemLastCh, vItemCh, iScrollPosLast, true ); //without getch( ch = draw_item_info( ( TERMX - VIEW_OFFSET_X * 2 ) / 2, ( TERMX - VIEW_OFFSET_X * 2 ) / 2, 0, TERMY - VIEW_OFFSET_Y * 2, sItemCh, sItemTn, vItemCh, vItemLastCh, iScrollPos ); if( ch == KEY_PPAGE ) { iScrollPos--; iScrollPosLast--; } else if( ch == KEY_NPAGE ) { iScrollPos++; iScrollPosLast++; } refresh_all(); } while( ch == KEY_PPAGE || ch == KEY_NPAGE ); } while( true ); }
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; }
void game::compare(int iCompareX, int iCompareY) { int examx, examy; if (iCompareX != -999 && iCompareY != -999) { examx = u.posx + iCompareX; examy = u.posy + iCompareY; } else if (!choose_adjacent(_("Compare where?"), examx, examy)) { return; } std::vector <item> &here = m.i_at(examx, examy); typedef std::vector< std::list<item> > pseudo_inventory; pseudo_inventory grounditems; indexed_invslice grounditems_slice; //Filter out items with the same name (keep only one of them) //Only the first 10 Items due to numbering 0-9 std::set<std::string> dups; for (size_t i = 0; i < here.size() && grounditems.size() < 10; i++) { if (dups.count(here[i].tname()) == 0) { grounditems.push_back(std::list<item>(1, here[i])); // invlet: '0' ... '9' grounditems.back().front().invlet = '0' + grounditems.size() - 1; dups.insert(here[i].tname()); } } for (size_t a = 0; a < grounditems.size(); a++) { // avoid INT_MIN, as it can be confused with "no item at all" grounditems_slice.push_back(indexed_invslice::value_type(&grounditems[a], INT_MIN + a + 1)); } static const item_category category_on_ground( "GROUND:", _("GROUND:"), -1000 ); u.inv.restack(&u); u.inv.sort(); const indexed_invslice stacks = u.inv.slice_filter(); inventory_selector inv_s(false, true, _("Compare:")); inv_s.make_item_list(grounditems_slice, &category_on_ground); inv_s.make_item_list(stacks); inv_s.prepare_paging(); inventory_selector::drop_map prev_droppings; while(true) { inv_s.display(); const std::string action = inv_s.ctxt.handle_input(); const long ch = inv_s.ctxt.get_raw_input().get_first_input(); const int item_pos = g->u.invlet_to_position(static_cast<char>(ch)); if (item_pos != INT_MIN) { inv_s.set_to_drop(item_pos, 0); } else if (ch >= '0' && ch <= '9' && (size_t) (ch - '0') < grounditems_slice.size()) { const int ip = ch - '0'; inv_s.set_drop_count(INT_MIN + 1 + ip, 0, grounditems_slice[ip].first->front()); } else if (inv_s.handle_movement(action)) { // continue with comparison below } else if (action == "QUIT") { break; } else if (action == "RIGHT") { inv_s.set_selected_to_drop(0); } if (inv_s.second_item != NULL) { std::vector<iteminfo> vItemLastCh, vItemCh; std::string sItemLastCh, sItemCh; inv_s.first_item->info(true, &vItemCh); sItemCh = inv_s.first_item->tname(); inv_s.second_item->info(true, &vItemLastCh); sItemLastCh = inv_s.second_item->tname(); draw_item_info(0, (TERMX - VIEW_OFFSET_X * 2) / 2, 0, TERMY - VIEW_OFFSET_Y * 2, sItemLastCh, vItemLastCh, vItemCh, -1, true); //without getch() draw_item_info((TERMX - VIEW_OFFSET_X * 2) / 2, (TERMX - VIEW_OFFSET_X * 2) / 2, 0, TERMY - VIEW_OFFSET_Y * 2, sItemCh, vItemCh, vItemLastCh); inv_s.dropping = prev_droppings; inv_s.second_item = NULL; } else { prev_droppings = inv_s.dropping; } } }
// Pick up items at (pos). void Pickup::pick_up( const tripoint &pos, int min ) { int veh_root_part = 0; int cargo_part = -1; vehicle *veh = g->m.veh_at( pos, veh_root_part ); bool from_vehicle = false; if( min != -1 ) { switch( interact_with_vehicle( veh, pos, veh_root_part ) ) { case DONE: return; case ITEMS_FROM_CARGO: cargo_part = veh->part_with_feature( veh_root_part, "CARGO", false ); from_vehicle = cargo_part >= 0; break; case ITEMS_FROM_GROUND: // Nothing to change, default is to pick from ground anyway. if( g->m.has_flag( "SEALED", pos ) ) { return; } break; } } if( !from_vehicle ) { bool isEmpty = ( g->m.i_at( pos ).empty() ); // Hide the pickup window if this is a toilet and there's nothing here // but water. if( ( !isEmpty ) && g->m.furn( pos ) == f_toilet ) { isEmpty = true; for( auto maybe_water : g->m.i_at( pos ) ) { if( maybe_water.typeId() != "water" ) { isEmpty = false; break; } } } if( isEmpty && ( min != -1 || !OPTIONS["AUTO_PICKUP_ADJACENT"] ) ) { return; } } // which items are we grabbing? std::vector<item> here; if( from_vehicle ) { auto vehitems = veh->get_items( cargo_part ); here.resize( vehitems.size() ); std::copy( vehitems.rbegin(), vehitems.rend(), here.begin() ); } else { auto mapitems = g->m.i_at( pos ); here.resize( mapitems.size() ); std::copy( mapitems.rbegin(), mapitems.rend(), here.begin() ); } if( min == -1 ) { if( g->check_zone( "NO_AUTO_PICKUP", pos ) ) { here.clear(); } // Recursively pick up adjacent items if that option is on. if( OPTIONS["AUTO_PICKUP_ADJACENT"] && g->u.pos() == pos ) { //Autopickup adjacent direction adjacentDir[8] = {NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST}; for( auto &elem : adjacentDir ) { tripoint apos = tripoint( direction_XY( elem ), 0 ); apos += pos; if( g->m.has_flag( "SEALED", apos ) ) { continue; } if( g->check_zone( "NO_AUTO_PICKUP", apos ) ) { continue; } pick_up( apos, min ); } } } // Not many items, just grab them if( ( int )here.size() <= min && min != -1 ) { g->u.assign_activity( ACT_PICKUP, 0 ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); // Only one item means index is 0. g->u.activity.values.push_back( 0 ); // auto-pickup means pick up all. g->u.activity.values.push_back( 0 ); return; } if( min != -1 ) { // don't bother if we're just autopickup-ing g->temp_exit_fullscreen(); } bool sideStyle = use_narrow_sidebar(); // Otherwise, we have Autopickup, 2 or more items and should list them, etc. int maxmaxitems = sideStyle ? TERMY : getmaxy( g->w_messages ) - 3; int itemsH = std::min( 25, TERMY / 2 ); int pickupBorderRows = 3; // The pickup list may consume the entire terminal, minus space needed for its // header/footer and the item info window. int minleftover = itemsH + pickupBorderRows; if( maxmaxitems > TERMY - minleftover ) { maxmaxitems = TERMY - minleftover; } const int minmaxitems = sideStyle ? 6 : 9; std::vector<pickup_count> getitem( here.size() ); int maxitems = here.size(); maxitems = ( maxitems < minmaxitems ? minmaxitems : ( maxitems > maxmaxitems ? maxmaxitems : maxitems ) ); int itemcount = 0; if( min == -1 ) { //Auto Pickup, select matching items if( !select_autopickup_items( here, getitem ) ) { // If we didn't find anything, bail out now. return; } } else { int pickupH = maxitems + pickupBorderRows; int pickupW = getmaxx( g->w_messages ); int pickupY = VIEW_OFFSET_Y; int pickupX = getbegx( g->w_messages ); int itemsW = pickupW; int itemsY = sideStyle ? pickupY + pickupH : TERMY - itemsH; int itemsX = pickupX; WINDOW *w_pickup = newwin( pickupH, pickupW, pickupY, pickupX ); WINDOW *w_item_info = newwin( itemsH, itemsW, itemsY, itemsX ); WINDOW_PTR w_pickupptr( w_pickup ); WINDOW_PTR w_item_infoptr( w_item_info ); std::string action; long raw_input_char = ' '; input_context ctxt( "PICKUP" ); ctxt.register_action( "UP" ); ctxt.register_action( "DOWN" ); ctxt.register_action( "RIGHT" ); ctxt.register_action( "LEFT" ); ctxt.register_action( "NEXT_TAB", _( "Next page" ) ); ctxt.register_action( "PREV_TAB", _( "Previous page" ) ); ctxt.register_action( "SCROLL_UP" ); ctxt.register_action( "SCROLL_DOWN" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "SELECT_ALL" ); ctxt.register_action( "QUIT", _( "Cancel" ) ); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); int start = 0, cur_it; player pl_copy = g->u; pl_copy.set_fake( true ); bool update = true; mvwprintw( w_pickup, 0, 0, _( "PICK UP" ) ); int selected = 0; int iScrollPos = 0; if( g->was_fullscreen ) { g->draw_ter(); } // Now print the two lists; those on the ground and about to be added to inv // Continue until we hit return or space do { const std::string pickup_chars = ctxt.get_available_single_char_hotkeys( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:;" ); int idx = -1; for( int i = 1; i < pickupH; i++ ) { mvwprintw( w_pickup, i, 0, " " ); } if( action == "ANY_INPUT" && raw_input_char >= '0' && raw_input_char <= '9' ) { int raw_input_char_value = ( char )raw_input_char - '0'; itemcount *= 10; itemcount += raw_input_char_value; if( itemcount < 0 ) { itemcount = 0; } } else if( action == "SCROLL_UP" ) { iScrollPos--; } else if( action == "SCROLL_DOWN" ) { iScrollPos++; } else if( action == "PREV_TAB" ) { if( start > 0 ) { start -= maxitems; } else { start = ( int )( ( here.size() - 1 ) / maxitems ) * maxitems; } selected = start; mvwprintw( w_pickup, maxitems + 2, 0, " " ); } else if( action == "NEXT_TAB" ) { if( start + maxitems < ( int )here.size() ) { start += maxitems; } else { start = 0; } iScrollPos = 0; selected = start; mvwprintw( w_pickup, maxitems + 2, pickupH, " " ); } else if( action == "UP" ) { selected--; iScrollPos = 0; if( selected < 0 ) { selected = here.size() - 1; start = ( int )( here.size() / maxitems ) * maxitems; if( start >= ( int )here.size() ) { start -= maxitems; } } else if( selected < start ) { start -= maxitems; } } else if( action == "DOWN" ) { selected++; iScrollPos = 0; if( selected >= ( int )here.size() ) { selected = 0; start = 0; } else if( selected >= start + maxitems ) { start += maxitems; } } else if( selected >= 0 && ( ( action == "RIGHT" && !getitem[selected] ) || ( action == "LEFT" && getitem[selected] ) ) ) { idx = selected; } else if( action == "ANY_INPUT" && raw_input_char == '`' ) { std::string ext = string_input_popup( _( "Enter 2 letters (case sensitive):" ), 3, "", "", "", 2 ); if( ext.size() == 2 ) { int p1 = pickup_chars.find( ext.at( 0 ) ); int p2 = pickup_chars.find( ext.at( 1 ) ); if( p1 != -1 && p2 != -1 ) { idx = pickup_chars.size() + ( p1 * pickup_chars.size() ) + p2; } } } else if( action == "ANY_INPUT" ) { idx = ( raw_input_char <= 127 ) ? pickup_chars.find( raw_input_char ) : -1; iScrollPos = 0; } if( idx >= 0 && idx < ( int )here.size() ) { if( getitem[idx] ) { if( here[idx].count_by_charges() ) { if( getitem[idx].count == 0 ) { pl_copy.inv.find_item( getitem[idx].position ).charges -= here[idx].charges; } else { pl_copy.inv.find_item( getitem[idx].position ).charges -= getitem[idx].count; } } else { unsigned stack_size = pl_copy.inv.const_stack( getitem[idx].position ).size(); pl_copy.i_rem( getitem[idx].position ); //if the stack_was emptied, removing the item invalidated later positions- fix them if( stack_size == 1 ) { for( unsigned i = 0; i < here.size(); i++ ) { if( getitem[i] && getitem[i].position > getitem[idx].position ) { getitem[i].position--; } } } } } //end if getitem[idx] if( itemcount != 0 || getitem[idx].count == 0 ) { if( itemcount >= here[idx].charges || !here[idx].count_by_charges() ) { // Ignore the count if we pickup the whole stack anyway // or something that is not counted by charges (tools) itemcount = 0; } getitem[idx].count = itemcount; itemcount = 0; } // Note: this might not change the value of getitem[idx] at all! getitem[idx].pick = ( action == "RIGHT" ? true : ( action == "LEFT" ? false : !getitem[idx] ) ); if( action != "RIGHT" && action != "LEFT" ) { selected = idx; start = ( int )( idx / maxitems ) * maxitems; } if( getitem[idx] ) { item temp = here[idx]; if( getitem[idx].count != 0 && getitem[idx].count < here[idx].charges ) { temp.charges = getitem[idx].count; } item *added = &( pl_copy.i_add( temp ) ); getitem[idx].position = pl_copy.inv.position_by_item( added ); } else { getitem[idx].count = 0; } update = true; } werase( w_item_info ); if( selected >= 0 && selected <= ( int )here.size() - 1 ) { std::vector<iteminfo> vThisItem, vDummy; here[selected].info( true, vThisItem ); draw_item_info( w_item_info, "", "", vThisItem, vDummy, iScrollPos, true, true ); } draw_custom_border( w_item_info, false ); mvwprintw( w_item_info, 0, 2, "< " ); trim_and_print( w_item_info, 0, 4, itemsW - 8, c_white, "%s >", here[selected].display_name().c_str() ); wrefresh( w_item_info ); if( action == "SELECT_ALL" ) { int count = 0; for( size_t i = 0; i < here.size(); i++ ) { if( getitem[i] ) { count++; } else { item *added = &( pl_copy.i_add( here[i] ) ); getitem[i].position = pl_copy.inv.position_by_item( added ); } getitem[i].pick = true; } if( count == ( int )here.size() ) { for( size_t i = 0; i < here.size(); i++ ) { getitem[i].pick = false; } pl_copy = g->u; pl_copy.set_fake( true ); } update = true; } for( cur_it = start; cur_it < start + maxitems; cur_it++ ) { mvwprintw( w_pickup, 1 + ( cur_it % maxitems ), 0, " " ); if( cur_it < ( int )here.size() ) { nc_color icolor = here[cur_it].color_in_inventory(); if( cur_it == selected ) { icolor = hilite( icolor ); } if( cur_it < ( int )pickup_chars.size() ) { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, char( pickup_chars[cur_it] ) ); } else if( cur_it < ( int )pickup_chars.size() + ( int )pickup_chars.size() * ( int )pickup_chars.size() ) { int p = cur_it - pickup_chars.size(); int p1 = p / pickup_chars.size(); int p2 = p % pickup_chars.size(); mvwprintz( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, "`%c%c", char( pickup_chars[p1] ), char( pickup_chars[p2] ) ); } else { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, ' ' ); } if( getitem[cur_it] ) { if( getitem[cur_it].count == 0 ) { wprintz( w_pickup, c_ltblue, " + " ); } else { wprintz( w_pickup, c_ltblue, " # " ); } } else { wprintw( w_pickup, " - " ); } std::string item_name = here[cur_it].display_name(); if( OPTIONS["ITEM_SYMBOLS"] ) { item_name = string_format( "%s %s", here[cur_it].symbol().c_str(), item_name.c_str() ); } trim_and_print( w_pickup, 1 + ( cur_it % maxitems ), 6, pickupW - 4, icolor, "%s", item_name.c_str() ); } } mvwprintw( w_pickup, maxitems + 1, 0, _( "[%s] Unmark" ), ctxt.get_desc( "LEFT", 1 ).c_str() ); center_print( w_pickup, maxitems + 1, c_ltgray, _( "[%s] Help" ), ctxt.get_desc( "HELP_KEYBINDINGS", 1 ).c_str() ); right_print( w_pickup, maxitems + 1, 0, c_ltgray, _( "[%s] Mark" ), ctxt.get_desc( "RIGHT", 1 ).c_str() ); mvwprintw( w_pickup, maxitems + 2, 0, _( "[%s] Prev" ), ctxt.get_desc( "PREV_TAB", 1 ).c_str() ); center_print( w_pickup, maxitems + 2, c_ltgray, _( "[%s] All" ), ctxt.get_desc( "SELECT_ALL", 1 ).c_str() ); right_print( w_pickup, maxitems + 2, 0, c_ltgray, _( "[%s] Next" ), ctxt.get_desc( "NEXT_TAB", 1 ).c_str() ); if( update ) { // Update weight & volume information update = false; for( int i = 9; i < pickupW; ++i ) { mvwaddch( w_pickup, 0, i, ' ' ); } mvwprintz( w_pickup, 0, 9, ( pl_copy.weight_carried() > g->u.weight_capacity() ? c_red : c_white ), _( "Wgt %.1f" ), convert_weight( pl_copy.weight_carried() ) + 0.05 ); // +0.05 to round up wprintz( w_pickup, c_white, "/%.1f", convert_weight( g->u.weight_capacity() ) ); mvwprintz( w_pickup, 0, 24, ( pl_copy.volume_carried() > g->u.volume_capacity() ? c_red : c_white ), _( "Vol %d" ), pl_copy.volume_carried() ); wprintz( w_pickup, c_white, "/%d", g->u.volume_capacity() ); } wrefresh( w_pickup ); action = ctxt.handle_input(); raw_input_char = ctxt.get_raw_input().get_first_input(); } while( action != "QUIT" && action != "CONFIRM" ); bool item_selected = false; // Check if we have selected an item. for( auto selection : getitem ) { if( selection ) { item_selected = true; } } if( action != "CONFIRM" || !item_selected ) { w_pickupptr.reset(); w_item_infoptr.reset(); add_msg( _( "Never mind." ) ); g->reenter_fullscreen(); g->refresh_all(); return; } } // At this point we've selected our items, register an activity to pick them up. g->u.assign_activity( ACT_PICKUP, 0 ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); if( min == -1 ) { // Auto pickup will need to auto resume since there can be several of them on the stack. g->u.activity.auto_resume = true; } std::reverse( getitem.begin(), getitem.end() ); for( size_t i = 0; i < here.size(); i++ ) { if( getitem[i] ) { g->u.activity.values.push_back( i ); g->u.activity.values.push_back( getitem[i].count ); } } g->reenter_fullscreen(); }
// Pick up items at (pos). void Pickup::pick_up( const tripoint &pos, int min ) { int veh_root_part = 0; int cargo_part = -1; vehicle *veh = g->m.veh_at (pos, veh_root_part); bool from_vehicle = false; if( min != -1 ) { switch( interact_with_vehicle( veh, pos, veh_root_part ) ) { case DONE: return; case ITEMS_FROM_CARGO: cargo_part = veh->part_with_feature( veh_root_part, "CARGO", false ); from_vehicle = cargo_part >= 0; break; case ITEMS_FROM_GROUND: // Nothing to change, default is to pick from ground anyway. break; } } if (g->m.has_flag("SEALED", pos)) { return; } //min == -1 is Autopickup if (!g->u.can_pickup(min != -1)) { // no message on autopickup (-1) return; } if( !from_vehicle ) { bool isEmpty = (g->m.i_at(pos).empty()); // Hide the pickup window if this is a toilet and there's nothing here // but water. if ((!isEmpty) && g->m.furn(pos) == f_toilet) { isEmpty = true; for( auto maybe_water : g->m.i_at(pos) ) { if( maybe_water.typeId() != "water") { isEmpty = false; break; } } } if (isEmpty && (min != -1 || !OPTIONS["AUTO_PICKUP_ADJACENT"] )) { return; } } // which items are we grabbing? std::vector<item> here; if( from_vehicle ) { auto vehitems = veh->get_items(cargo_part); here.resize( vehitems.size() ); std::copy( vehitems.begin(), vehitems.end(), here.begin() ); } else { auto mapitems = g->m.i_at(pos); here.resize( mapitems.size() ); std::copy( mapitems.begin(), mapitems.end(), here.begin() ); } if (min == -1) { if( g->check_zone( "NO_AUTO_PICKUP", pos ) ) { here.clear(); } // Recursively pick up adjacent items if that option is on. if( OPTIONS["AUTO_PICKUP_ADJACENT"] && g->u.pos() == pos ) { //Autopickup adjacent direction adjacentDir[8] = {NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST}; for( auto &elem : adjacentDir ) { tripoint apos = tripoint( direction_XY( elem ), 0 ); apos += pos; if( g->m.has_flag( "SEALED", apos ) ) { continue; } if( g->check_zone( "NO_AUTO_PICKUP", apos ) ) { continue; } pick_up( apos, min ); } } } // Not many items, just grab them if ((int)here.size() <= min && min != -1) { g->u.assign_activity( ACT_PICKUP, 0 ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); // Only one item means index is 0. g->u.activity.values.push_back( 0 ); // auto-pickup means pick up all. g->u.activity.values.push_back( 0 ); return; } if(min != -1) { // don't bother if we're just autopickup-ing g->temp_exit_fullscreen(); } bool sideStyle = use_narrow_sidebar(); // Otherwise, we have Autopickup, 2 or more items and should list them, etc. int maxmaxitems = sideStyle ? TERMY : getmaxy(g->w_messages) - 3; int itemsH = std::min(25, TERMY / 2); int pickupBorderRows = 3; // The pickup list may consume the entire terminal, minus space needed for its // header/footer and the item info window. int minleftover = itemsH + pickupBorderRows; if(maxmaxitems > TERMY - minleftover) { maxmaxitems = TERMY - minleftover; } const int minmaxitems = sideStyle ? 6 : 9; std::vector<bool> getitem; getitem.resize(here.size(), false); int maxitems = here.size(); maxitems = (maxitems < minmaxitems ? minmaxitems : (maxitems > maxmaxitems ? maxmaxitems : maxitems )); int itemcount = 0; std::map<int, unsigned int> pickup_count; // Count of how many we'll pick up from each stack if (min == -1) { //Auto Pickup, select matching items if( !select_autopickup_items( here, getitem) ) { // If we didn't find anything, bail out now. return; } } else { int pickupH = maxitems + pickupBorderRows; int pickupW = getmaxx(g->w_messages); int pickupY = VIEW_OFFSET_Y; int pickupX = getbegx(g->w_messages); int itemsW = pickupW; int itemsY = sideStyle ? pickupY + pickupH : TERMY - itemsH; int itemsX = pickupX; WINDOW *w_pickup = newwin(pickupH, pickupW, pickupY, pickupX); WINDOW *w_item_info = newwin(itemsH, itemsW, itemsY, itemsX); WINDOW_PTR w_pickupptr( w_pickup ); WINDOW_PTR w_item_infoptr( w_item_info ); int ch = ' '; int start = 0, cur_it; int new_weight = g->u.weight_carried(), new_volume = g->u.volume_carried(); bool update = true; mvwprintw(w_pickup, 0, 0, _("PICK UP")); int selected = 0; int iScrollPos = 0; if(g->was_fullscreen) { g->draw_ter(); } // Now print the two lists; those on the ground and about to be added to inv // Continue until we hit return or space do { static const std::string pickup_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:;"; int idx = -1; for (int i = 1; i < pickupH; i++) { mvwprintw(w_pickup, i, 0, " "); } if (ch >= '0' && ch <= '9') { ch = (char)ch - '0'; itemcount *= 10; itemcount += ch; if( itemcount < 0 ) { itemcount = 0; } } else if ( ch == KEY_PPAGE) { iScrollPos--; } else if ( ch == KEY_NPAGE) { iScrollPos++; } else if ( ch == '<' ) { if ( start > 0 ) { start -= maxitems; } else { start = (int)( (here.size()-1) / maxitems ) * maxitems; } selected = start; mvwprintw(w_pickup, maxitems + 2, 0, " "); } else if ( ch == '>' ) { if ( start + maxitems < (int)here.size() ) { start += maxitems; } else { start = 0; } iScrollPos = 0; selected = start; mvwprintw(w_pickup, maxitems + 2, pickupH, " "); } else if ( ch == KEY_UP ) { selected--; iScrollPos = 0; if ( selected < 0 ) { selected = here.size() - 1; start = (int)( here.size() / maxitems ) * maxitems; if (start >= (int)here.size()) { start -= maxitems; } } else if ( selected < start ) { start -= maxitems; } } else if ( ch == KEY_DOWN ) { selected++; iScrollPos = 0; if ( selected >= (int)here.size() ) { selected = 0; start = 0; } else if ( selected >= start + maxitems ) { start += maxitems; } } else if ( selected >= 0 && ( ( ch == KEY_RIGHT && !getitem[selected]) || ( ch == KEY_LEFT && getitem[selected] ) ) ) { idx = selected; } else if ( ch == '`' ) { std::string ext = string_input_popup( _("Enter 2 letters (case sensitive):"), 3, "", "", "", 2); if(ext.size() == 2) { int p1 = pickup_chars.find(ext.at(0)); int p2 = pickup_chars.find(ext.at(1)); if ( p1 != -1 && p2 != -1 ) { idx = pickup_chars.size() + ( p1 * pickup_chars.size() ) + p2; } } } else { idx = ( ch <= 127 ) ? pickup_chars.find(ch) : -1; iScrollPos = 0; } if( idx >= 0 && idx < (int)here.size()) { if( getitem[idx] ) { if( pickup_count[idx] != 0 && (int)pickup_count[idx] < here[idx].charges ) { item temp = here[idx]; temp.charges = pickup_count[idx]; new_weight -= temp.weight(); new_volume -= temp.volume(); } else { new_weight -= here[idx].weight(); new_volume -= here[idx].volume(); } } if (itemcount != 0 || pickup_count[idx] == 0) { if (itemcount >= here[idx].charges || !here[idx].count_by_charges()) { // Ignore the count if we pickup the whole stack anyway // or something that is not counted by charges (tools) itemcount = 0; } pickup_count[idx] = itemcount; itemcount = 0; } // Note: this might not change the value of getitem[idx] at all! getitem[idx] = ( ch == KEY_RIGHT ? true : ( ch == KEY_LEFT ? false : !getitem[idx] ) ); if ( ch != KEY_RIGHT && ch != KEY_LEFT) { selected = idx; start = (int)( idx / maxitems ) * maxitems; } if (getitem[idx]) { if (pickup_count[idx] != 0 && (int)pickup_count[idx] < here[idx].charges) { item temp = here[idx]; temp.charges = pickup_count[idx]; new_weight += temp.weight(); new_volume += temp.volume(); } else { new_weight += here[idx].weight(); new_volume += here[idx].volume(); } } else { pickup_count[idx] = 0; } update = true; } werase(w_item_info); if ( selected >= 0 && selected <= (int)here.size() - 1 ) { std::vector<iteminfo> vThisItem, vDummy; here[selected].info(true, vThisItem); draw_item_info(w_item_info, "", "", vThisItem, vDummy, iScrollPos, true, true); } draw_custom_border(w_item_info, false); mvwprintw(w_item_info, 0, 2, "< "); trim_and_print(w_item_info, 0, 4, itemsW - 8, c_white, "%s >", here[selected].display_name().c_str()); wrefresh(w_item_info); if (ch == ',') { int count = 0; for (size_t i = 0; i < here.size(); i++) { if (getitem[i]) { count++; } else { new_weight += here[i].weight(); new_volume += here[i].volume(); } getitem[i] = true; } if (count == (int)here.size()) { for (size_t i = 0; i < here.size(); i++) { getitem[i] = false; } new_weight = g->u.weight_carried(); new_volume = g->u.volume_carried(); } update = true; } for (cur_it = start; cur_it < start + maxitems; cur_it++) { mvwprintw(w_pickup, 1 + (cur_it % maxitems), 0, " "); if (cur_it < (int)here.size()) { nc_color icolor = here[cur_it].color_in_inventory(); if (cur_it == selected) { icolor = hilite(icolor); } if (cur_it < (int)pickup_chars.size() ) { mvwputch(w_pickup, 1 + (cur_it % maxitems), 0, icolor, char(pickup_chars[cur_it])); } else { int p = cur_it - pickup_chars.size(); int p1 = p / pickup_chars.size(); int p2 = p % pickup_chars.size(); mvwprintz(w_pickup, 1 + (cur_it % maxitems), 0, icolor, "`%c%c", char(pickup_chars[p1]), char(pickup_chars[p2])); } if (getitem[cur_it]) { if (pickup_count[cur_it] == 0) { wprintz(w_pickup, c_ltblue, " + "); } else { wprintz(w_pickup, c_ltblue, " # "); } } else { wprintw(w_pickup, " - "); } std::string item_name = here[cur_it].display_name(); if (OPTIONS["ITEM_SYMBOLS"]) { item_name = string_format("%c %s", here[cur_it].symbol(), item_name.c_str()); } trim_and_print(w_pickup, 1 + (cur_it % maxitems), 6, pickupW - 4, icolor, "%s", item_name.c_str()); } } int pw = pickupW; const char *unmark = _("[left] Unmark"); const char *scroll = _("[up/dn] Scroll"); const char *mark = _("[right] Mark"); mvwprintw(w_pickup, maxitems + 1, 0, unmark); mvwprintw(w_pickup, maxitems + 1, (pw - std::strlen(scroll)) / 2, scroll); mvwprintw(w_pickup, maxitems + 1, pw - std::strlen(mark), mark); const char *prev = _("[<] Prev"); const char *all = _("[,] All"); const char *next = _("[>] Next"); mvwprintw(w_pickup, maxitems + 2, 0, prev); mvwprintw(w_pickup, maxitems + 2, (pw - std::strlen(all)) / 2, all); mvwprintw(w_pickup, maxitems + 2, pw - std::strlen(next), next); if (update) { // Update weight & volume information update = false; for (int i = 9; i < pickupW; ++i) { mvwaddch(w_pickup, 0, i, ' '); } mvwprintz(w_pickup, 0, 9, (new_weight > g->u.weight_capacity() ? c_red : c_white), _("Wgt %.1f"), g->u.convert_weight(new_weight) + 0.05); // +0.05 to round up wprintz(w_pickup, c_white, "/%.1f", g->u.convert_weight(g->u.weight_capacity())); mvwprintz(w_pickup, 0, 24, (new_volume > g->u.volume_capacity() ? c_red : c_white), _("Vol %d"), new_volume); wprintz(w_pickup, c_white, "/%d", g->u.volume_capacity()); } wrefresh(w_pickup); ch = (int)getch(); } while (ch != ' ' && ch != '\n' && ch != KEY_ENTER && ch != KEY_ESCAPE); bool item_selected = false; // Check if we have selected an item. for( auto selection : getitem ) { if( selection ) { item_selected = true; } } if( (ch != '\n' && ch != KEY_ENTER) || !item_selected ) { w_pickupptr.reset(); w_item_infoptr.reset(); add_msg(_("Never mind.")); g->reenter_fullscreen(); g->refresh_all(); return; } } // At this point we've selected our items, register an activity to pick them up. g->u.assign_activity( ACT_PICKUP, 0 ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); if( min == -1 ) { // Auto pickup will need to auto resume since there can be several of them on the stack. g->u.activity.auto_resume = true; } for (size_t i = 0; i < here.size(); i++) { if( getitem[i] ) { g->u.activity.values.push_back( i ); g->u.activity.values.push_back( pickup_count[i] ); } } g->reenter_fullscreen(); }
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; WINDOW *w_head = newwin( headHeight, width, 0, wStart ); WINDOW_PTR w_head_ptr( w_head ); WINDOW *w_subhead = newwin( subHeadHeight, width, 3, wStart ); WINDOW_PTR w_subhead_ptr( w_subhead ); WINDOW *w_data = newwin( dataHeight, width, headHeight + subHeadHeight, wStart ); WINDOW_PTR w_data_ptr( w_data ); 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; } WINDOW *w_iteminfo = newwin( item_info_y, item_info_x, item_info_height, item_info_width ); WINDOW_PTR w_iteminfo_ptr( w_iteminfo ); 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 = ""; 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 == "" ) ? NORMAL : FILTERED; draw_recipe_tabs( w_head, tab.cur(), m ); draw_recipe_subtabs( w_subhead, tab.cur(), subtab.cur(), m ); current.clear(); available.clear(); if( batch ) { batch_recipes( crafting_inv, helpers, current, available, chosen ); } else { // Set current to all recipes in the current tab; available are possible to make pick_recipes( crafting_inv, helpers, current, available, tab.cur(), subtab.cur(), filterstring ); } } // 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 != "" ) { 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 != "" ) { 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 = item::nname( current[i]->result ); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, i - recmin, 2, c_dkgray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, i - recmin, 2, ( available[i] ? h_white : h_dkgray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, i - recmin, 2, ( available[i] ? c_white : c_dkgray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } else if( line >= recmax - dataHalfLines ) { for( int i = recmax - dataLines; i < recmax; ++i ) { std::string tmp_name = item::nname( current[i]->result ); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, dataLines + i - recmax, 2, c_ltgray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, dataLines + i - recmax, 2, ( available[i] ? h_white : h_dkgray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, dataLines + i - recmax, 2, ( available[i] ? c_white : c_dkgray ), utf8_truncate( tmp_name, 28 ).c_str() ); } } } else { for( int i = line - dataHalfLines; i < line - dataHalfLines + dataLines; ++i ) { std::string tmp_name = item::nname( current[i]->result ); if( batch ) { tmp_name = string_format( _( "%2dx %s" ), i + 1, tmp_name.c_str() ); } mvwprintz( w_data, dataHalfLines + i - line, 2, c_ltgray, "" ); // Clear the line if( i == line ) { mvwprintz( w_data, dataHalfLines + i - line, 2, ( available[i] ? h_white : h_dkgray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, dataHalfLines + i - line, 2, ( available[i] ? c_white : c_dkgray ), 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 = item::nname( current[i]->result ); 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_dkgray ), utf8_truncate( tmp_name, 28 ).c_str() ); } else { mvwprintz( w_data, i, 2, ( available[i] ? c_white : c_dkgray ), 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_ltgray; const auto &req = current[ line ]->requirements(); draw_can_craft_indicator( w_head, 0, *current[line] ); wrefresh( w_head ); ypos = 0; 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 ); 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" ) ); } //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; if( display_mode == 0 ) { mvwprintz( w_data, ypos++, 30, col, _( "Skills used: %s" ), ( !current[line]->skill_used ? _( "N/A" ) : current[line]->skill_used.obj().name().c_str() ) ); mvwprintz( w_data, ypos++, 30, col, _( "Required skills: %s" ), ( current[line]->required_skills_string().c_str() ) ); mvwprintz( w_data, ypos++, 30, col, _( "Difficulty: %d" ), current[line]->difficulty ); if( !current[line]->skill_used ) { mvwprintz( w_data, ypos++, 30, col, _( "Your skill level: N/A" ) ); } else { mvwprintz( w_data, ypos++, 30, col, _( "Your skill level: %d" ), // Macs don't seem to like passing this as a class, so force it to int ( int )g->u.get_skill_level( current[line]->skill_used ) ); } ypos += current[line]->print_time( w_data, ypos, 30, pane, col, count ); mvwprintz( w_data, ypos++, 30, col, _( "Dark craftable? %s" ), current[line]->has_flag( "BLIND_EASY" ) ? _( "Easy" ) : current[line]->has_flag( "BLIND_HARD" ) ? _( "Hard" ) : _( "Impossible" ) ); ypos += current[line]->print_items( w_data, ypos, 30, 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++, 30, stored_color, col, component_print_buffer[i] ); } if( ypos >= componentPrintHeight && component_print_buffer.size() > static_cast<size_t>( components_printed ) ) { mvwprintz( w_data, ypos++, 30, 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 ); } 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" ) { subtab.prev(); 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" ) { subtab.next(); 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( !current[line]->check_eligible_containers_for_crafting( ( 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" ) { filterstring = string_input_popup( _( "Search:" ), 85, filterstring, _( "Special prefixes for requirements:\n" " [t] search tools\n" " [c] search components\n" " [q] search qualities\n" " [s] search skills\n" " [S] search skill used only\n" "Special prefixes for results:\n" " [Q] search qualities\n" "Examples:\n" " t:soldering iron\n" " c:two by four\n" " q:metal sawing\n" " s:cooking\n" " Q:fine bolt turning" ) ); redraw = true; } else if( action == "QUIT" ) { chosen = nullptr; done = true; } else if( action == "RESET_FILTER" ) { filterstring = ""; 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; }
// Pick up items at (pos). void Pickup::pick_up( const tripoint &pos, int min ) { int cargo_part = -1; const optional_vpart_position vp = g->m.veh_at( pos ); vehicle *const veh = veh_pointer_or_null( vp ); bool from_vehicle = false; if( min != -1 ) { switch( interact_with_vehicle( veh, pos, vp ? vp->part_index() : -1 ) ) { case DONE: return; case ITEMS_FROM_CARGO: { const cata::optional<vpart_reference> carg = vp.part_with_feature( "CARGO", false ); cargo_part = carg ? carg->part_index() : -1; } from_vehicle = cargo_part >= 0; break; case ITEMS_FROM_GROUND: // Nothing to change, default is to pick from ground anyway. if( g->m.has_flag( "SEALED", pos ) ) { return; } break; } } if( !from_vehicle ) { bool isEmpty = ( g->m.i_at( pos ).empty() ); // Hide the pickup window if this is a toilet and there's nothing here // but water. if( ( !isEmpty ) && g->m.furn( pos ) == f_toilet ) { isEmpty = true; for( auto maybe_water : g->m.i_at( pos ) ) { if( maybe_water.typeId() != "water" ) { isEmpty = false; break; } } } if( isEmpty && ( min != -1 || !get_option<bool>( "AUTO_PICKUP_ADJACENT" ) ) ) { return; } } // which items are we grabbing? std::vector<item> here; if( from_vehicle ) { auto vehitems = veh->get_items( cargo_part ); here.resize( vehitems.size() ); std::copy( vehitems.begin(), vehitems.end(), here.begin() ); } else { auto mapitems = g->m.i_at( pos ); here.resize( mapitems.size() ); std::copy( mapitems.begin(), mapitems.end(), here.begin() ); } if( min == -1 ) { // Recursively pick up adjacent items if that option is on. if( get_option<bool>( "AUTO_PICKUP_ADJACENT" ) && g->u.pos() == pos ) { //Autopickup adjacent direction adjacentDir[8] = {NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST}; for( auto &elem : adjacentDir ) { tripoint apos = tripoint( direction_XY( elem ), 0 ); apos += pos; pick_up( apos, min ); } } // Bail out if this square cannot be auto-picked-up if( g->check_zone( zone_type_id( "NO_AUTO_PICKUP" ), pos ) ) { return; } else if( g->m.has_flag( "SEALED", pos ) ) { return; } } // Not many items, just grab them if( ( int )here.size() <= min && min != -1 ) { g->u.assign_activity( activity_id( "ACT_PICKUP" ) ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); // Only one item means index is 0. g->u.activity.values.push_back( 0 ); // auto-pickup means pick up all. g->u.activity.values.push_back( 0 ); return; } std::vector<std::list<item_idx>> stacked_here; for( size_t i = 0; i < here.size(); i++ ) { item &it = here[i]; bool found_stack = false; for( auto &stack : stacked_here ) { if( stack.begin()->_item.stacks_with( it ) ) { item_idx el = { it, i }; stack.push_back( el ); found_stack = true; break; } } if( !found_stack ) { std::list<item_idx> newstack; newstack.push_back( { it, i } ); stacked_here.push_back( newstack ); } } std::reverse( stacked_here.begin(), stacked_here.end() ); if( min != -1 ) { // don't bother if we're just autopickuping g->temp_exit_fullscreen(); } bool sideStyle = use_narrow_sidebar(); // Otherwise, we have Autopickup, 2 or more items and should list them, etc. int maxmaxitems = sideStyle ? TERMY : getmaxy( g->w_messages ) - 3; int itemsH = std::min( 25, TERMY / 2 ); int pickupBorderRows = 3; // The pickup list may consume the entire terminal, minus space needed for its // header/footer and the item info window. int minleftover = itemsH + pickupBorderRows; if( maxmaxitems > TERMY - minleftover ) { maxmaxitems = TERMY - minleftover; } const int minmaxitems = sideStyle ? 6 : 9; std::vector<pickup_count> getitem( stacked_here.size() ); int maxitems = stacked_here.size(); maxitems = ( maxitems < minmaxitems ? minmaxitems : ( maxitems > maxmaxitems ? maxmaxitems : maxitems ) ); int itemcount = 0; if( min == -1 ) { //Auto Pickup, select matching items if( !select_autopickup_items( stacked_here, getitem ) ) { // If we didn't find anything, bail out now. return; } } else { int pickupH = maxitems + pickupBorderRows; int pickupW = getmaxx( g->w_messages ); int pickupY = VIEW_OFFSET_Y; int pickupX = getbegx( g->w_messages ); int itemsW = pickupW; int itemsY = sideStyle ? pickupY + pickupH : TERMY - itemsH; int itemsX = pickupX; catacurses::window w_pickup = catacurses::newwin( pickupH, pickupW, pickupY, pickupX ); catacurses::window w_item_info = catacurses::newwin( itemsH, itemsW, itemsY, itemsX ); std::string action; long raw_input_char = ' '; input_context ctxt( "PICKUP" ); ctxt.register_action( "UP" ); ctxt.register_action( "DOWN" ); ctxt.register_action( "RIGHT" ); ctxt.register_action( "LEFT" ); ctxt.register_action( "NEXT_TAB", _( "Next page" ) ); ctxt.register_action( "PREV_TAB", _( "Previous page" ) ); ctxt.register_action( "SCROLL_UP" ); ctxt.register_action( "SCROLL_DOWN" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "SELECT_ALL" ); ctxt.register_action( "QUIT", _( "Cancel" ) ); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "HELP_KEYBINDINGS" ); ctxt.register_action( "FILTER" ); int start = 0; int cur_it = 0; bool update = true; mvwprintw( w_pickup, 0, 0, _( "PICK UP" ) ); int selected = 0; int iScrollPos = 0; std::string filter; std::string new_filter; std::vector<int> matches;//Indexes of items that match the filter bool filter_changed = true; if( g->was_fullscreen ) { g->draw_ter(); } // Now print the two lists; those on the ground and about to be added to inv // Continue until we hit return or space do { const std::string pickup_chars = ctxt.get_available_single_char_hotkeys( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:;" ); int idx = -1; for( int i = 1; i < pickupH; i++ ) { mvwprintw( w_pickup, i, 0, " " ); } if( action == "ANY_INPUT" && raw_input_char >= '0' && raw_input_char <= '9' ) { int raw_input_char_value = ( char )raw_input_char - '0'; itemcount *= 10; itemcount += raw_input_char_value; if( itemcount < 0 ) { itemcount = 0; } } else if( action == "SCROLL_UP" ) { iScrollPos--; } else if( action == "SCROLL_DOWN" ) { iScrollPos++; } else if( action == "PREV_TAB" ) { if( start > 0 ) { start -= maxitems; } else { start = ( int )( ( matches.size() - 1 ) / maxitems ) * maxitems; } selected = start; mvwprintw( w_pickup, maxitems + 2, 0, " " ); } else if( action == "NEXT_TAB" ) { if( start + maxitems < ( int )matches.size() ) { start += maxitems; } else { start = 0; } iScrollPos = 0; selected = start; mvwprintw( w_pickup, maxitems + 2, pickupH, " " ); } else if( action == "UP" ) { selected--; iScrollPos = 0; if( selected < 0 ) { selected = matches.size() - 1; start = ( int )( matches.size() / maxitems ) * maxitems; if( start >= ( int )matches.size() ) { start -= maxitems; } } else if( selected < start ) { start -= maxitems; } } else if( action == "DOWN" ) { selected++; iScrollPos = 0; if( selected >= ( int )matches.size() ) { selected = 0; start = 0; } else if( selected >= start + maxitems ) { start += maxitems; } } else if( selected >= 0 && selected < int( matches.size() ) && ( ( action == "RIGHT" && !getitem[matches[selected]].pick ) || ( action == "LEFT" && getitem[matches[selected]].pick ) ) ) { idx = selected; } else if( action == "FILTER" ) { new_filter = filter; string_input_popup popup; popup .title( _( "Set filter" ) ) .width( 30 ) .edit( new_filter ); if( !popup.canceled() ) { filter_changed = true; } else { wrefresh( g->w_terrain ); } } else if( action == "ANY_INPUT" && raw_input_char == '`' ) { std::string ext = string_input_popup() .title( _( "Enter 2 letters (case sensitive):" ) ) .width( 3 ) .max_length( 2 ) .query_string(); if( ext.size() == 2 ) { int p1 = pickup_chars.find( ext.at( 0 ) ); int p2 = pickup_chars.find( ext.at( 1 ) ); if( p1 != -1 && p2 != -1 ) { idx = pickup_chars.size() + ( p1 * pickup_chars.size() ) + p2; } } } else if( action == "ANY_INPUT" ) { idx = ( raw_input_char <= 127 ) ? pickup_chars.find( raw_input_char ) : -1; iScrollPos = 0; } if( idx >= 0 && idx < ( int )matches.size() ) { size_t true_idx = matches[idx]; if( itemcount != 0 || getitem[true_idx].count == 0 ) { item &temp = stacked_here[true_idx].begin()->_item; int amount_available = temp.count_by_charges() ? temp.charges : stacked_here[true_idx].size(); if( itemcount >= amount_available ) { itemcount = 0; } getitem[true_idx].count = itemcount; itemcount = 0; } // Note: this might not change the value of getitem[idx] at all! getitem[true_idx].pick = ( action == "RIGHT" ? true : ( action == "LEFT" ? false : !getitem[true_idx].pick ) ); if( action != "RIGHT" && action != "LEFT" ) { selected = idx; start = ( int )( idx / maxitems ) * maxitems; } if( !getitem[true_idx].pick ) { getitem[true_idx].count = 0; } update = true; } if( filter_changed ) { matches.clear(); while( matches.empty() ) { auto filter_func = item_filter_from_string( new_filter ); for( size_t index = 0; index < stacked_here.size(); index++ ) { if( filter_func( stacked_here[index].begin()->_item ) ) { matches.push_back( index ); } } if( matches.empty() ) { popup( _( "Your filter returned no results" ) ); wrefresh( g->w_terrain ); // The filter must have results, or simply be emptied or canceled, // as this screen can't be reached without there being // items available string_input_popup popup; popup .title( _( "Set filter" ) ) .width( 30 ) .edit( new_filter ); if( popup.canceled() ) { new_filter = filter; filter_changed = false; } } } if( filter_changed ) { filter = new_filter; filter_changed = false; selected = 0; start = 0; iScrollPos = 0; } wrefresh( g->w_terrain ); } item &selected_item = stacked_here[matches[selected]].begin()->_item; werase( w_item_info ); if( selected >= 0 && selected <= ( int )stacked_here.size() - 1 ) { std::vector<iteminfo> vThisItem; std::vector<iteminfo> vDummy; selected_item.info( true, vThisItem ); draw_item_info( w_item_info, "", "", vThisItem, vDummy, iScrollPos, true, true ); } draw_custom_border( w_item_info, false ); mvwprintw( w_item_info, 0, 2, "< " ); trim_and_print( w_item_info, 0, 4, itemsW - 8, c_white, "%s >", selected_item.display_name().c_str() ); wrefresh( w_item_info ); if( action == "SELECT_ALL" ) { int count = 0; for( auto i : matches ) { if( getitem[i].pick ) { count++; } getitem[i].pick = true; } if( count == ( int )stacked_here.size() ) { for( size_t i = 0; i < stacked_here.size(); i++ ) { getitem[i].pick = false; } } update = true; } for( cur_it = start; cur_it < start + maxitems; cur_it++ ) { mvwprintw( w_pickup, 1 + ( cur_it % maxitems ), 0, " " ); if( cur_it < ( int )matches.size() ) { int true_it = matches[cur_it]; item &this_item = stacked_here[ true_it ].begin()->_item; nc_color icolor = this_item.color_in_inventory(); if( cur_it == selected ) { icolor = hilite( icolor ); } if( cur_it < ( int )pickup_chars.size() ) { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, char( pickup_chars[cur_it] ) ); } else if( cur_it < ( int )pickup_chars.size() + ( int )pickup_chars.size() * ( int )pickup_chars.size() ) { int p = cur_it - pickup_chars.size(); int p1 = p / pickup_chars.size(); int p2 = p % pickup_chars.size(); mvwprintz( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, "`%c%c", char( pickup_chars[p1] ), char( pickup_chars[p2] ) ); } else { mvwputch( w_pickup, 1 + ( cur_it % maxitems ), 0, icolor, ' ' ); } if( getitem[true_it].pick ) { if( getitem[true_it].count == 0 ) { wprintz( w_pickup, c_light_blue, " + " ); } else { wprintz( w_pickup, c_light_blue, " # " ); } } else { wprintw( w_pickup, " - " ); } std::string item_name; if( stacked_here[true_it].begin()->_item.ammo_type() == "money" ) { //Count charges //TODO: transition to the item_location system used for the inventory unsigned long charges_total = 0; for( const auto item : stacked_here[true_it] ) { charges_total += item._item.charges; } //Picking up none or all the cards in a stack if( !getitem[true_it].pick || getitem[true_it].count == 0 ) { item_name = stacked_here[true_it].begin()->_item.display_money( stacked_here[true_it].size(), charges_total ); } else { unsigned long charges = 0; int c = getitem[true_it].count; for( auto it = stacked_here[true_it].begin(); it != stacked_here[true_it].end() && c > 0; ++it, --c ) { charges += it->_item.charges; } item_name = string_format( _( "%s of %s" ), stacked_here[true_it].begin()->_item.display_money( getitem[true_it].count, charges ), format_money( charges_total ) ); } } else { item_name = this_item.display_name( stacked_here[true_it].size() ); } if( stacked_here[true_it].size() > 1 ) { item_name = string_format( "%d %s", stacked_here[true_it].size(), item_name.c_str() ); } if( get_option<bool>( "ITEM_SYMBOLS" ) ) { item_name = string_format( "%s %s", this_item.symbol().c_str(), item_name.c_str() ); } trim_and_print( w_pickup, 1 + ( cur_it % maxitems ), 6, pickupW - 4, icolor, item_name ); } } mvwprintw( w_pickup, maxitems + 1, 0, _( "[%s] Unmark" ), ctxt.get_desc( "LEFT", 1 ).c_str() ); center_print( w_pickup, maxitems + 1, c_light_gray, string_format( _( "[%s] Help" ), ctxt.get_desc( "HELP_KEYBINDINGS", 1 ).c_str() ) ); right_print( w_pickup, maxitems + 1, 0, c_light_gray, string_format( _( "[%s] Mark" ), ctxt.get_desc( "RIGHT", 1 ).c_str() ) ); mvwprintw( w_pickup, maxitems + 2, 0, _( "[%s] Prev" ), ctxt.get_desc( "PREV_TAB", 1 ).c_str() ); center_print( w_pickup, maxitems + 2, c_light_gray, string_format( _( "[%s] All" ), ctxt.get_desc( "SELECT_ALL", 1 ).c_str() ) ); right_print( w_pickup, maxitems + 2, 0, c_light_gray, string_format( _( "[%s] Next" ), ctxt.get_desc( "NEXT_TAB", 1 ).c_str() ) ); if( update ) { // Update weight & volume information update = false; for( int i = 9; i < pickupW; ++i ) { mvwaddch( w_pickup, 0, i, ' ' ); } units::mass weight_picked_up = 0; units::volume volume_picked_up = 0; for( size_t i = 0; i < getitem.size(); i++ ) { if( getitem[i].pick ) { item temp = stacked_here[i].begin()->_item; if( temp.count_by_charges() && getitem[i].count < temp.charges && getitem[i].count != 0 ) { temp.charges = getitem[i].count; } int num_picked = std::min( stacked_here[i].size(), getitem[i].count == 0 ? stacked_here[i].size() : getitem[i].count ); weight_picked_up += temp.weight() * num_picked; volume_picked_up += temp.volume() * num_picked; } } auto weight_predict = g->u.weight_carried() + weight_picked_up; auto volume_predict = g->u.volume_carried() + volume_picked_up; mvwprintz( w_pickup, 0, 9, weight_predict > g->u.weight_capacity() ? c_red : c_white, _( "Wgt %.1f" ), round_up( convert_weight( weight_predict ), 1 ) ); wprintz( w_pickup, c_white, "/%.1f", round_up( convert_weight( g->u.weight_capacity() ), 1 ) ); std::string fmted_volume_predict = format_volume( volume_predict ); mvwprintz( w_pickup, 0, 24, volume_predict > g->u.volume_capacity() ? c_red : c_white, _( "Vol %s" ), fmted_volume_predict.c_str() ); std::string fmted_volume_capacity = format_volume( g->u.volume_capacity() ); wprintz( w_pickup, c_white, "/%s", fmted_volume_capacity.c_str() ); }; wrefresh( w_pickup ); action = ctxt.handle_input(); raw_input_char = ctxt.get_raw_input().get_first_input(); } while( action != "QUIT" && action != "CONFIRM" ); bool item_selected = false; // Check if we have selected an item. for( auto selection : getitem ) { if( selection.pick ) { item_selected = true; } } if( action != "CONFIRM" || !item_selected ) { w_pickup = catacurses::window(); w_item_info = catacurses::window(); add_msg( _( "Never mind." ) ); g->reenter_fullscreen(); g->refresh_all(); return; } } // At this point we've selected our items, register an activity to pick them up. g->u.assign_activity( activity_id( "ACT_PICKUP" ) ); g->u.activity.placement = pos - g->u.pos(); g->u.activity.values.push_back( from_vehicle ); if( min == -1 ) { // Auto pickup will need to auto resume since there can be several of them on the stack. g->u.activity.auto_resume = true; } std::vector<std::pair<int, int>> pick_values; for( size_t i = 0; i < stacked_here.size(); i++ ) { const auto &selection = getitem[i]; if( !selection.pick ) { continue; } const auto &stack = stacked_here[i]; // Note: items can be both charged and stacked // For robustness, let's assume they can be both in the same stack bool pick_all = selection.count == 0; size_t count = selection.count; for( const item_idx &it : stack ) { if( !pick_all && count == 0 ) { break; } if( it._item.count_by_charges() ) { size_t num_picked = std::min( ( size_t )it._item.charges, count ); pick_values.push_back( { static_cast<int>( it.idx ), static_cast<int>( num_picked ) } ); count -= num_picked; } else { size_t num_picked = 1; pick_values.push_back( { static_cast<int>( it.idx ), 0 } ); count -= num_picked; } } } // The pickup activity picks up items last-to-first from its values list, so make sure the // higher indices are at the end. std::sort( pick_values.begin(), pick_values.end() ); for( auto &it : pick_values ) { g->u.activity.values.push_back( it.first ); g->u.activity.values.push_back( it.second ); } g->reenter_fullscreen(); }
void inventory_selector::execute_compare( const std::string &title ) { insert_selection_column( "ITEMS TO COMPARE", _( "ITEMS TO COMPARE" ) ); prepare_columns( true ); std::vector<inventory_entry *> compared; const auto toggle_entry = [ this, &compared ]( inventory_entry *entry ) { const auto iter = std::find( compared.begin(), compared.end(), entry ); entry->chosen_count = ( iter == compared.end() ) ? 1 : 0; if( entry->chosen_count != 0 ) { compared.push_back( entry ); } else { compared.erase( iter ); } on_change( *entry ); }; while(true) { display( title, SM_COMPARE ); const std::string action = ctxt.handle_input(); const long ch = ctxt.get_raw_input().get_first_input(); const auto entry = find_entry_by_invlet( ch ); if( entry != nullptr ) { toggle_entry( entry ); } else if(action == "RIGHT") { const auto selection( get_active_column().get_all_selected() ); for( auto &entry : selection ) { if( entry->chosen_count == 0 || selection.size() == 1 ) { toggle_entry( entry ); if( compared.size() == 2 ) { break; } } } } else if( action == "QUIT" ) { break; } else { on_action( action ); } if( compared.size() == 2 ) { const item *first_item = compared.back()->it; const item *second_item = compared.front()->it; std::vector<iteminfo> vItemLastCh, vItemCh; std::string sItemLastCh, sItemCh, sItemTn; first_item->info( true, vItemCh ); sItemCh = first_item->tname(); sItemTn = first_item->type_name(); second_item->info(true, vItemLastCh); sItemLastCh = second_item->tname(); int iScrollPos = 0; int iScrollPosLast = 0; int ch = ( int ) ' '; do { draw_item_info( 0, ( TERMX - VIEW_OFFSET_X * 2 ) / 2, 0, TERMY - VIEW_OFFSET_Y * 2, sItemLastCh, sItemTn, vItemLastCh, vItemCh, iScrollPosLast, true ); //without getch() ch = draw_item_info( ( TERMX - VIEW_OFFSET_X * 2) / 2, (TERMX - VIEW_OFFSET_X * 2 ) / 2, 0, TERMY - VIEW_OFFSET_Y * 2, sItemCh, sItemTn, vItemCh, vItemLastCh, iScrollPos ); if( ch == KEY_PPAGE ) { iScrollPos--; iScrollPosLast--; } else if( ch == KEY_NPAGE ) { iScrollPos++; iScrollPosLast++; } } while ( ch == KEY_PPAGE || ch == KEY_NPAGE ); toggle_entry( compared.back() ); } } }
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; }