bool player::create(game *g, character_type type, std::string tempname) { weapon = item(g->itypes[0], 0); int iMaxX = (g->VIEWX < 12) ? 80 : (g->VIEWX*2)+56; int iMaxY = (g->VIEWY < 12) ? 25 : (g->VIEWY*2)+1; WINDOW* w = newwin(25, 80, (iMaxY > 25) ? (iMaxY-25)/2 : 0, (iMaxX > 80) ? (iMaxX-80)/2 : 0); int tab = 0, points = 38; if (type != PLTYPE_CUSTOM) { switch (type) { case PLTYPE_RANDOM: { str_max = rng(6, 12); dex_max = rng(6, 12); int_max = rng(6, 12); per_max = rng(6, 12); points = points - str_max - dex_max - int_max - per_max; if (str_max > HIGH_STAT) points -= (str_max - HIGH_STAT); if (dex_max > HIGH_STAT) points -= (dex_max - HIGH_STAT); if (int_max > HIGH_STAT) points -= (int_max - HIGH_STAT); if (per_max > HIGH_STAT) points -= (per_max - HIGH_STAT); int num_gtraits = 0, num_btraits = 0, rn, tries; while (points < 0 || rng(-3, 20) > points) { if (num_btraits < MAX_TRAIT_POINTS && one_in(3)) { tries = 0; do { rn = random_bad_trait(); tries++; } while ((has_trait(rn) || num_btraits - traits[rn].points > MAX_TRAIT_POINTS) && tries < 5); if (tries < 5) { toggle_trait(rn); points -= traits[rn].points; num_btraits -= traits[rn].points; } } else { switch (rng(1, 4)) { case 1: if (str_max > 5) { str_max--; points++; } break; case 2: if (dex_max > 5) { dex_max--; points++; } break; case 3: if (int_max > 5) { int_max--; points++; } break; case 4: if (per_max > 5) { per_max--; points++; } break; } } } while (points > 0) { switch (rng((num_gtraits < MAX_TRAIT_POINTS ? 1 : 5), 9)) { case 1: case 2: case 3: case 4: rn = random_good_trait(); if (!has_trait(rn) && points >= traits[rn].points && num_gtraits + traits[rn].points <= MAX_TRAIT_POINTS) { toggle_trait(rn); points -= traits[rn].points; num_gtraits += traits[rn].points; } break; case 5: switch (rng(1, 4)) { case 1: if (str_max < HIGH_STAT) { str_max++; points--; } break; case 2: if (dex_max < HIGH_STAT) { dex_max++; points--; } break; case 3: if (int_max < HIGH_STAT) { int_max++; points--; } break; case 4: if (per_max < HIGH_STAT) { per_max++; points--; } break; } break; case 6: case 7: case 8: case 9: rn = random_skill(); Skill *aSkill = Skill::skill(rn); int level = skillLevel(aSkill); if (level < points) { points -= level + 1; skillLevel(aSkill).level(level + 2); } break; } } } break; case PLTYPE_TEMPLATE: { std::ifstream fin; std::stringstream filename; filename << "data/" << tempname << ".template"; fin.open(filename.str().c_str()); if (!fin.is_open()) { debugmsg("Couldn't open %s!", filename.str().c_str()); return false; } std::string(data); getline(fin, data); load_info(g, data); points = 0; } break; } tab = 3; } else points = OPTIONS[OPT_INITIAL_POINTS]; do { werase(w); wrefresh(w); switch (tab) { case 0: tab += set_stats (w, this, points); break; case 1: tab += set_traits (w, this, points); break; case 2: tab += set_skills (w, this, points); break; case 3: tab += set_description(w, this, points); break; } } while (tab >= 0 && tab < 4); delwin(w); if (tab < 0) return false; // Character is finalized. Now just set up HP, &c for (int i = 0; i < num_hp_parts; i++) { hp_max[i] = calc_HP(str_max, has_trait(PF_TOUGH)); hp_cur[i] = hp_max[i]; } if (has_trait(PF_GLASSJAW)) { hp_max[hp_head] = int(hp_max[hp_head] * .85); hp_cur[hp_head] = hp_max[hp_head]; } if (has_trait(PF_SMELLY)) scent = 800; if (has_trait(PF_ANDROID)) { add_bionic(bionic_id(rng(bio_memory, max_bio_start - 1)));// Other if (bionics[my_bionics[0].id].power_cost > 0) { add_bionic(bionic_id(rng(1, bio_ethanol))); // Power Source max_power_level = 10; power_level = 10; } else { bionic_id tmpbio; do tmpbio = bionic_id(rng(bio_ethanol + 1, bio_armor_legs)); while (bionics[tmpbio].power_cost > 0); add_bionic(tmpbio); max_power_level = 0; power_level = 0; } /* CHEATER'S STUFF add_bionic(bionic_id(rng(0, bio_ethanol))); // Power Source for (int i = 0; i < 5; i++) add_bionic(bionic_id(rng(bio_memory, max_bio_start - 1)));// Other max_power_level = 80; power_level = 80; End of cheatery */ } if (has_trait(PF_MARTIAL_ARTS)) { itype_id ma_type; do { int choice = menu("Pick your style:", "Karate", "Judo", "Aikido", "Tai Chi", "Taekwando", NULL); if (choice == 1) ma_type = itm_style_karate; if (choice == 2) ma_type = itm_style_judo; if (choice == 3) ma_type = itm_style_aikido; if (choice == 4) ma_type = itm_style_tai_chi; if (choice == 5) ma_type = itm_style_taekwando; item tmpitem = item(g->itypes[ma_type], 0); full_screen_popup(tmpitem.info(true).c_str()); } while (!query_yn("Use this style?")); styles.push_back(ma_type); } ret_null = item(g->itypes[0], 0); if (!styles.empty()) weapon = item(g->itypes[ styles[0] ], 0, ':'); else weapon = item(g->itypes[0], 0); // Nice to start out less than naked. item tmp(g->itypes[itm_jeans_fit], 0, 'a'); worn.push_back(tmp); tmp = item(g->itypes[itm_tshirt_fit], 0, 'b'); worn.push_back(tmp); tmp = item(g->itypes[itm_sneakers_fit], 0, 'c'); worn.push_back(tmp); // The near-sighted get to start with glasses. if (has_trait(PF_MYOPIC)) { tmp = item(g->itypes[itm_glasses_eye], 0, 'd'); worn.push_back(tmp); } // Likewise, the asthmatic start with their medication. if (has_trait(PF_ASTHMA)) { tmp = item(g->itypes[itm_inhaler], 0, 'a' + worn.size()); inv.push_back(tmp); } // Basic starter gear, added independently of profession. tmp = item(g->itypes[itm_pockknife], 0,'a' + worn.size()); inv.push_back(tmp); tmp = item(g->itypes[itm_lighter], 0,'a' + worn.size()); inv.push_back(tmp); // make sure we have no mutations for (int i = 0; i < PF_MAX2; i++) my_mutations[i] = false; return 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; }
bool player::create(game *g, character_type type, std::string tempname) { weapon = item(g->itypes["null"], 0); g->u.prof = profession::generic(); WINDOW* w = newwin(25, 80, (TERMY > 25) ? (TERMY-25)/2 : 0, (TERMX > 80) ? (TERMX-80)/2 : 0); int tab = 0, points = 38; if (type != PLTYPE_CUSTOM) { switch (type) { case PLTYPE_RANDOM: { str_max = rng(6, 12); dex_max = rng(6, 12); int_max = rng(6, 12); per_max = rng(6, 12); points = points - str_max - dex_max - int_max - per_max; if (str_max > HIGH_STAT) points -= (str_max - HIGH_STAT); if (dex_max > HIGH_STAT) points -= (dex_max - HIGH_STAT); if (int_max > HIGH_STAT) points -= (int_max - HIGH_STAT); if (per_max > HIGH_STAT) points -= (per_max - HIGH_STAT); int num_gtraits = 0, num_btraits = 0, rn, tries; while (points < 0 || rng(-3, 20) > points) { if (num_btraits < MAX_TRAIT_POINTS && one_in(3)) { tries = 0; do { rn = random_bad_trait(); tries++; } while ((has_trait(rn) || num_btraits - traits[rn].points > MAX_TRAIT_POINTS) && tries < 5); if (tries < 5) { toggle_trait(rn); points -= traits[rn].points; num_btraits -= traits[rn].points; } } else { switch (rng(1, 4)) { case 1: if (str_max > 5) { str_max--; points++; } break; case 2: if (dex_max > 5) { dex_max--; points++; } break; case 3: if (int_max > 5) { int_max--; points++; } break; case 4: if (per_max > 5) { per_max--; points++; } break; } } } while (points > 0) { switch (rng((num_gtraits < MAX_TRAIT_POINTS ? 1 : 5), 9)) { case 1: case 2: case 3: case 4: rn = random_good_trait(); if (!has_trait(rn) && points >= traits[rn].points && num_gtraits + traits[rn].points <= MAX_TRAIT_POINTS) { toggle_trait(rn); points -= traits[rn].points; num_gtraits += traits[rn].points; } break; case 5: switch (rng(1, 4)) { case 1: if (str_max < HIGH_STAT) { str_max++; points--; } break; case 2: if (dex_max < HIGH_STAT) { dex_max++; points--; } break; case 3: if (int_max < HIGH_STAT) { int_max++; points--; } break; case 4: if (per_max < HIGH_STAT) { per_max++; points--; } break; } break; case 6: case 7: case 8: case 9: rn = random_skill(); Skill *aSkill = Skill::skill(rn); int level = skillLevel(aSkill); if (level < points) { points -= level + 1; skillLevel(aSkill).level(level + 2); } break; } } } break; case PLTYPE_TEMPLATE: { std::ifstream fin; std::stringstream filename; filename << "data/" << tempname << ".template"; fin.open(filename.str().c_str()); if (!fin.is_open()) { debugmsg("Couldn't open %s!", filename.str().c_str()); return false; } std::string(data); getline(fin, data); load_info(g, data); points = 0; } break; } tab = 3; } else points = OPTIONS[OPT_INITIAL_POINTS]; do { werase(w); wrefresh(w); switch (tab) { case 0: tab += set_stats (w, g, this, points); break; case 1: tab += set_traits (w, g, this, points); break; case 2: tab += set_profession (w, g, this, points); break; case 3: tab += set_skills (w, g, this, points); break; case 4: tab += set_description(w, g, this, points); break; } } while (tab >= 0 && tab < 5); delwin(w); if (tab < 0) return false; // Character is finalized. Now just set up HP, &c for (int i = 0; i < num_hp_parts; i++) { hp_max[i] = calc_HP(str_max, has_trait(PF_TOUGH)); hp_cur[i] = hp_max[i]; } if (has_trait(PF_GLASSJAW)) { hp_max[hp_head] = int(hp_max[hp_head] * .85); hp_cur[hp_head] = hp_max[hp_head]; } if (has_trait(PF_SMELLY)) scent = 800; if (has_trait(PF_ANDROID)) { std::map<std::string,bionic_data*>::iterator random_bionic = bionics.begin(); std::advance(random_bionic,rng(0,bionics.size()-1)); add_bionic(random_bionic->first);// Other if (bionics[my_bionics[0].id]->power_cost > 0) { add_bionic(bionic_id(power_source_bionics[rng(0,power_source_bionics.size())])); // Power Source max_power_level = 10; power_level = 10; } else { bionic_id tmpbio; do tmpbio = bionic_id(unpowered_bionics[rng(0, unpowered_bionics.size())]); while (bionics[tmpbio]->power_cost > 0); add_bionic(tmpbio); max_power_level = 0; power_level = 0; } /* CHEATER'S STUFF add_bionic(bionic_id(rng(0, "bio_ethanol"))); // Power Source for (int i = 0; i < 5; i++) add_bionic(bionic_id(rng("bio_memory", max_"bio_start" - 1)));// Other max_power_level = 80; power_level = 80; End of cheatery */ } if (has_trait(PF_MARTIAL_ARTS)) { itype_id ma_type; do { int choice = menu("Pick your style:", "Karate", "Judo", "Aikido", "Tai Chi", "Taekwondo", NULL); if (choice == 1) ma_type = "style_karate"; if (choice == 2) ma_type = "style_judo"; if (choice == 3) ma_type = "style_aikido"; if (choice == 4) ma_type = "style_tai_chi"; if (choice == 5) ma_type = "style_taekwondo"; item tmpitem = item(g->itypes[ma_type], 0); full_screen_popup(tmpitem.info(true).c_str()); } while (!query_yn("Use this style?")); styles.push_back(ma_type); } ret_null = item(g->itypes["null"], 0); if (!styles.empty()) weapon = item(g->itypes[ styles[0] ], 0, ':'); else weapon = item(g->itypes["null"], 0); item tmp; //gets used several times std::vector<std::string> prof_items = g->u.prof->items(); for (std::vector<std::string>::const_iterator iter = prof_items.begin(); iter != prof_items.end(); ++iter) { item tmp = item(g->itypes.at(*iter), 0, 'a' + worn.size()); if (tmp.is_armor()) { if (tmp.has_flag(IF_VARSIZE)) tmp.item_flags |= mfb(IF_FIT); worn.push_back(tmp); } else { inv.push_back(tmp); } // if we start with drugs, need to start strongly addicted, too if (tmp.is_food()) { it_comest *comest = dynamic_cast<it_comest*>(tmp.type); if (comest->add != ADD_NULL) { addiction add(comest->add, 10); g->u.addictions.push_back(add); } } } // The near-sighted get to start with glasses. if (has_trait(PF_MYOPIC)) { tmp = item(g->itypes["glasses_eye"], 0, 'a' + worn.size()); worn.push_back(tmp); } // And the far-sighted get to start with reading glasses. if (has_trait(PF_HYPEROPIC)) { tmp = item(g->itypes["glasses_reading"], 0, 'a' + worn.size()); worn.push_back(tmp); } // Likewise, the asthmatic start with their medication. if (has_trait(PF_ASTHMA)) { tmp = item(g->itypes["inhaler"], 0, 'a' + worn.size()); inv.push_back(tmp); } // Basic starter gear, added independently of profession. tmp = item(g->itypes["pockknife"], 0,'a' + worn.size()); inv.push_back(tmp); tmp = item(g->itypes["matches"], 0,'a' + worn.size()); inv.push_back(tmp); // make sure we have no mutations for (int i = 0; i < PF_MAX2; i++) my_mutations[i] = false; return true; }
bool player::create(game *g, character_type type, std::string tempname) { weapon = item(g->itypes["null"], 0); g->u.prof = profession::generic(); WINDOW* w = newwin(FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, (TERMY > FULL_SCREEN_HEIGHT) ? (TERMY-FULL_SCREEN_HEIGHT)/2 : 0, (TERMX > FULL_SCREEN_WIDTH) ? (TERMX-FULL_SCREEN_WIDTH)/2 : 0); int tab = 0, points = 38, max_trait_points = 12; if (type != PLTYPE_CUSTOM) { switch (type) { case PLTYPE_RANDOM: { str_max = rng(6, 12); dex_max = rng(6, 12); int_max = rng(6, 12); per_max = rng(6, 12); points = points - str_max - dex_max - int_max - per_max; if (str_max > HIGH_STAT) points -= (str_max - HIGH_STAT); if (dex_max > HIGH_STAT) points -= (dex_max - HIGH_STAT); if (int_max > HIGH_STAT) points -= (int_max - HIGH_STAT); if (per_max > HIGH_STAT) points -= (per_max - HIGH_STAT); int num_gtraits = 0, num_btraits = 0, rn, tries; while (points < 0 || rng(-3, 20) > points) { if (num_btraits < max_trait_points && one_in(3)) { tries = 0; do { rn = random_bad_trait(); tries++; } while ((has_trait(rn) || num_btraits - traits[rn].points > max_trait_points) && tries < 5); if (tries < 5) { toggle_trait(rn); points -= traits[rn].points; num_btraits -= traits[rn].points; } } else { switch (rng(1, 4)) { case 1: if (str_max > 5) { str_max--; points++; } break; case 2: if (dex_max > 5) { dex_max--; points++; } break; case 3: if (int_max > 5) { int_max--; points++; } break; case 4: if (per_max > 5) { per_max--; points++; } break; } } } while (points > 0) { switch (rng((num_gtraits < max_trait_points ? 1 : 5), 9)) { case 1: case 2: case 3: case 4: rn = random_good_trait(); if (!has_trait(rn) && points >= traits[rn].points && num_gtraits + traits[rn].points <= max_trait_points) { toggle_trait(rn); points -= traits[rn].points; num_gtraits += traits[rn].points; } break; case 5: switch (rng(1, 4)) { case 1: if (str_max < HIGH_STAT) { str_max++; points--; } break; case 2: if (dex_max < HIGH_STAT) { dex_max++; points--; } break; case 3: if (int_max < HIGH_STAT) { int_max++; points--; } break; case 4: if (per_max < HIGH_STAT) { per_max++; points--; } break; } break; case 6: case 7: case 8: case 9: rn = random_skill(); Skill *aSkill = Skill::skill(rn); int level = skillLevel(aSkill); if (level < points) { points -= level + 1; skillLevel(aSkill).level(level + 2); } break; } } } break; case PLTYPE_TEMPLATE: { std::ifstream fin; std::stringstream filename; filename << "data/" << tempname << ".template"; fin.open(filename.str().c_str()); if (!fin.is_open()) { debugmsg("Couldn't open %s!", filename.str().c_str()); return false; } std::string(data); getline(fin, data); load_info(g, data); points = 0; } break; } tab = NEWCHAR_TAB_MAX; } else points = OPTIONS[OPT_INITIAL_POINTS]; max_trait_points = OPTIONS[OPT_MAX_TRAIT_POINTS]; do { werase(w); wrefresh(w); switch (tab) { case 0: tab += set_stats (w, g, this, points); break; case 1: tab += set_traits (w, g, this, points, max_trait_points); break; case 2: tab += set_profession (w, g, this, points); break; case 3: tab += set_skills (w, g, this, points); break; case 4: tab += set_description(w, g, this, points); break; } } while (tab >= 0 && tab <= NEWCHAR_TAB_MAX); delwin(w); if (tab < 0) return false; // Character is finalized. Now just set up HP, &c for (int i = 0; i < num_hp_parts; i++) { hp_max[i] = calc_HP(str_max, has_trait(PF_TOUGH)); hp_cur[i] = hp_max[i]; } if (has_trait(PF_HARDCORE)) { for (int i = 0; i < num_hp_parts; i++) { hp_max[i] = int(hp_max[i] * .25); hp_cur[i] = hp_max[i]; } } if (has_trait(PF_GLASSJAW)) { hp_max[hp_head] = int(hp_max[hp_head] * .80); hp_cur[hp_head] = hp_max[hp_head]; } if (has_trait(PF_SMELLY)) scent = 800; if (has_trait(PF_ANDROID)) { bionic_id first_bio; do { first_bio = g->random_good_bionic(); } while (bionics[first_bio]->power_cost > 10); add_bionic(first_bio); add_bionic(bionic_id(power_source_bionics[rng(0,power_source_bionics.size()-1)])); // Power Source max_power_level = 10; power_level = 10; } if (has_trait(PF_MARTIAL_ARTS)) { itype_id ma_type; do { int choice = menu(false, _("Pick your style:"), _("Karate"), _("Judo"), _("Aikido"), _("Tai Chi"), _("Taekwondo"), NULL); if (choice == 1) ma_type = "style_karate"; if (choice == 2) ma_type = "style_judo"; if (choice == 3) ma_type = "style_aikido"; if (choice == 4) ma_type = "style_tai_chi"; if (choice == 5) ma_type = "style_taekwondo"; item tmpitem = item(g->itypes[ma_type], 0); full_screen_popup(tmpitem.info(true).c_str()); } while (!query_yn(_("Use this style?"))); styles.push_back(ma_type); style_selected=ma_type; } if (has_trait(PF_MARTIAL_ARTS2)) { itype_id ma_type; do { int choice = menu(false, _("Pick your style:"), _("Capoeira"), _("Krav Maga"), _("Muay Thai"), _("Ninjutsu"), _("Zui Quan"), NULL); if (choice == 1) ma_type = "style_capoeira"; if (choice == 2) ma_type = "style_krav_maga"; if (choice == 3) ma_type = "style_muay_thai"; if (choice == 4) ma_type = "style_ninjutsu"; if (choice == 5) ma_type = "style_zui_quan"; item tmpitem = item(g->itypes[ma_type], 0); full_screen_popup(tmpitem.info(true).c_str()); } while (!query_yn(_("Use this style?"))); styles.push_back(ma_type); style_selected=ma_type; } if (has_trait(PF_MARTIAL_ARTS3)) { itype_id ma_type; do { int choice = menu(false, _("Pick your style:"), _("Tiger"), _("Crane"), _("Leopard"), _("Snake"), _("Dragon"), NULL); if (choice == 1) ma_type = "style_tiger"; if (choice == 2) ma_type = "style_crane"; if (choice == 3) ma_type = "style_leopard"; if (choice == 4) ma_type = "style_snake"; if (choice == 5) ma_type = "style_dragon"; item tmpitem = item(g->itypes[ma_type], 0); full_screen_popup(tmpitem.info(true).c_str()); } while (!query_yn(_("Use this style?"))); styles.push_back(ma_type); style_selected=ma_type; } if (has_trait(PF_MARTIAL_ARTS4)) { itype_id ma_type; do { int choice = menu(false, _("Pick your style:"), _("Centipede"), _("Viper"), _("Scorpion"), _("Lizard"), _("Toad"), NULL); if (choice == 1) ma_type = "style_centipede"; if (choice == 2) ma_type = "style_venom_snake"; if (choice == 3) ma_type = "style_scorpion"; if (choice == 4) ma_type = "style_lizard"; if (choice == 5) ma_type = "style_toad"; item tmpitem = item(g->itypes[ma_type], 0); full_screen_popup(tmpitem.info(true).c_str()); } while (!query_yn(_("Use this style?"))); styles.push_back(ma_type); style_selected=ma_type; } ret_null = item(g->itypes["null"], 0); weapon = get_combat_style(); item tmp; //gets used several times std::vector<std::string> prof_items = g->u.prof->items(); for (std::vector<std::string>::const_iterator iter = prof_items.begin(); iter != prof_items.end(); ++iter) { tmp = item(item_controller->find_template(*iter), 0); inv.push_back(tmp); } std::vector<addiction> prof_addictions = g->u.prof->addictions(); for (std::vector<addiction>::const_iterator iter = prof_addictions.begin(); iter != prof_addictions.end(); ++iter) { g->u.addictions.push_back(*iter); } // Grab the skills from the profession, if there are any profession::StartingSkillList prof_skills = g->u.prof->skills(); for (profession::StartingSkillList::const_iterator iter = prof_skills.begin(); iter != prof_skills.end(); ++iter) { assert(Skill::skill(iter->first)); if (Skill::skill(iter->first)) { g->u.boost_skill_level(iter->first, iter->second); } } // Those who are both near-sighted and far-sighted start with bifocal glasses. if (has_trait(PF_HYPEROPIC) && has_trait(PF_MYOPIC)) { tmp = item(g->itypes["glasses_bifocal"], 0); inv.push_back(tmp); } // The near-sighted start with eyeglasses. else if (has_trait(PF_MYOPIC)) { tmp = item(g->itypes["glasses_eye"], 0); inv.push_back(tmp); } // The far-sighted start with reading glasses. else if (has_trait(PF_HYPEROPIC)) { tmp = item(g->itypes["glasses_reading"], 0); inv.push_back(tmp); } // Likewise, the asthmatic start with their medication. if (has_trait(PF_ASTHMA)) { tmp = item(g->itypes["inhaler"], 0); inv.push_back(tmp); } // Basic starter gear, added independently of profession. tmp = item(g->itypes["pockknife"], 0); inv.push_back(tmp); tmp = item(g->itypes["matches"], 0); inv.push_back(tmp); // make sure we have no mutations for (int i = 0; i < PF_MAX2; i++) if (!has_base_trait(i)) my_mutations[i] = false; // Equip any armor from our inventory. If we are unable to wear some of it due to encumberance, it will silently fail. std::vector<item*> tmp_inv; inv.dump(tmp_inv); for(std::vector<item*>::iterator i = tmp_inv.begin(); i != tmp_inv.end(); ++i) { if( (*i)->is_armor()) { if( (*i)->has_flag("VARSIZE")) { (*i)->item_tags.insert("FIT"); } // It might be more elegant to use player::wear_item, but then we have to implement our own inventory removal. wear(g, (*i)->invlet, false); } } // Ensure that persistent morale effects (e.g. Optimist) are present at the start. apply_persistent_morale(); return true; }
void game::craft() { if (u.morale_level() < MIN_MORALE_CRAFT) { // See morale.h add_msg("Your morale is too low to craft..."); return; } WINDOW *w_head = newwin( 3, 80, 0, 0); WINDOW *w_data = newwin(22, 80, 3, 0); craft_cat tab = CC_WEAPON; std::vector<recipe*> current; std::vector<bool> available; item tmp; int line = 0, xpos, ypos; bool redraw = true; bool done = false; char ch; do { if (redraw) { // When we switch tabs, redraw the header redraw = false; line = 0; draw_recipe_tabs(w_head, tab); current.clear(); available.clear(); // Set current to all recipes in the current tab; available are possible to make pick_recipes(current, available, tab); } // Clear the screen of recipe data, and draw it anew werase(w_data); mvwprintz(w_data, 20, 0, c_white, "\ Press ? to describe object. Press <ENTER> to attempt to craft object."); wrefresh(w_data); for (int i = 0; i < current.size() && i < 23; i++) { if (i == line) mvwprintz(w_data, i, 0, (available[i] ? h_white : h_dkgray), itypes[current[i]->result]->name.c_str()); else mvwprintz(w_data, i, 0, (available[i] ? c_white : c_dkgray), itypes[current[i]->result]->name.c_str()); } if (current.size() > 0) { nc_color col = (available[line] ? c_white : c_dkgray); mvwprintz(w_data, 0, 30, col, "Primary skill: %s", (current[line]->sk_primary == sk_null ? "N/A" : skill_name(current[line]->sk_primary).c_str())); mvwprintz(w_data, 1, 30, col, "Secondary skill: %s", (current[line]->sk_secondary == sk_null ? "N/A" : skill_name(current[line]->sk_secondary).c_str())); mvwprintz(w_data, 2, 30, col, "Difficulty: %d", current[line]->difficulty); if (current[line]->sk_primary == sk_null) mvwprintz(w_data, 3, 30, col, "Your skill level: N/A"); else mvwprintz(w_data, 3, 30, col, "Your skill level: %d", u.sklevel[current[line]->sk_primary]); if (current[line]->time >= 1000) mvwprintz(w_data, 4, 30, col, "Time to complete: %d minutes", int(current[line]->time / 1000)); else mvwprintz(w_data, 4, 30, col, "Time to complete: %d turns", int(current[line]->time / 100)); mvwprintz(w_data, 5, 30, col, "Tools required:"); if (current[line]->tools[0].size() == 0) { mvwputch(w_data, 6, 30, col, '>'); mvwprintz(w_data, 6, 32, c_green, "NONE"); ypos = 6; } else { ypos = 5; // Loop to print the required tools for (int i = 0; i < 5; i++) { if (current[line]->tools[i].size() > 0) { ypos++; mvwputch(w_data, ypos, 30, col, '>'); } xpos = 32; for (int j = 0; j < current[line]->tools[i].size(); j++) { itype_id type = current[line]->tools[i][j].type; int charges = current[line]->tools[i][j].count; nc_color toolcol = c_red; if (charges < 0 && u.has_amount(type, 1)) toolcol = c_green; else if (charges > 0 && u.has_charges(type, charges)) toolcol = c_green; if (u.has_bionic(bio_tools)) toolcol = c_green; std::stringstream toolinfo; toolinfo << itypes[type]->name + " "; if (charges > 0) toolinfo << "(" << charges << " charges) "; std::string toolname = toolinfo.str(); if (xpos + toolname.length() >= 80) { xpos = 32; ypos++; } mvwprintz(w_data, ypos, xpos, toolcol, toolname.c_str()); xpos += toolname.length(); if (j < current[line]->tools[i].size() - 1) { if (xpos >= 77) { xpos = 32; ypos++; } mvwprintz(w_data, ypos, xpos, c_white, "OR "); xpos += 3; } } } } // Loop to print the required components ypos++; mvwprintz(w_data, ypos, 30, col, "Components required:"); for (int i = 0; i < 5; i++) { if (current[line]->components[i].size() > 0) { ypos++; mvwputch(w_data, ypos, 30, col, '>'); } xpos = 32; for (int j = 0; j < current[line]->components[i].size(); j++) { int count = current[line]->components[i][j].count; itype_id type = current[line]->components[i][j].type; nc_color compcol = (u.has_amount(type, count) ? c_green : c_red); std::stringstream dump; dump << count << "x " << itypes[type]->name << " "; std::string compname = dump.str(); if (xpos + compname.length() >= 80) { ypos++; xpos = 32; } mvwprintz(w_data, ypos, xpos, compcol, compname.c_str()); xpos += compname.length(); if (j < current[line]->components[i].size() - 1) { if (xpos >= 77) { ypos++; xpos = 32; } mvwprintz(w_data, ypos, xpos, c_white, "OR "); xpos += 3; } } } } wrefresh(w_data); ch = input(); switch (ch) { case '<': if (tab == CC_WEAPON) tab = CC_MISC; else tab = craft_cat(int(tab) - 1); redraw = true; break; case '>': if (tab == CC_MISC) tab = CC_WEAPON; else tab = craft_cat(int(tab) + 1); redraw = true; break; case 'j': line++; break; case 'k': line--; break; case '\n': if (!available[line]) popup("You can't do that!"); else { make_craft(current[line]); done = true; } break; case '?': tmp = item(itypes[current[line]->result], 0); full_screen_popup(tmp.info(true).c_str()); redraw = true; break; } if (line < 0) line = current.size() - 1; else if (line >= current.size()) line = 0; } while (ch != KEY_ESCAPE && ch != 'q' && ch != 'Q' && !done); werase(w_head); werase(w_data); delwin(w_head); delwin(w_data); refresh_all(); }
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; }
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; }