bool player::can_make( const recipe *r, int batch_size ) { const inventory &crafting_inv = crafting_inventory(); if( has_recipe( r, crafting_inv, get_crafting_helpers() ) < 0 ) { return false; } return r->requirements().can_make_with_inventory( crafting_inv, batch_size ); }
bool player::disassemble( item &obj, int pos, bool ground, bool interactive ) { // check sufficient tools for disassembly std::string err; if( !can_disassemble( obj, crafting_inventory(), &err ) ) { if( interactive ) { add_msg_if_player( m_info, "%s", err.c_str() ); } return false; } const auto &r = recipe_dictionary::get_uncraft( obj.typeId() ); // last chance to back out if( interactive && get_option<bool>( "QUERY_DISASSEMBLE" ) ) { const auto components( r.disassembly_requirements().get_components() ); std::ostringstream list; for( const auto &elem : components ) { list << "- " << elem.front().to_string() << std::endl; } if( !query_yn( _( "Disassembling the %s may yield:\n%s\nReally disassemble?" ), obj.tname().c_str(), list.str().c_str() ) ) { return false; } } if( activity.id() != activity_id( "ACT_DISASSEMBLE" ) ) { assign_activity( activity_id( "ACT_DISASSEMBLE" ), r.time ); } else if( activity.moves_left <= 0 ) { activity.moves_left = r.time; } activity.values.push_back( pos ); activity.coords.push_back( ground ? this->pos() : tripoint_min ); activity.str_values.push_back( r.result ); return true; }
void game::place_construction(constructable *con) { refresh_all(); inventory total_inv = crafting_inventory(); std::vector<point> valid; for (int x = u.posx - 1; x <= u.posx + 1; x++) { for (int y = u.posy - 1; y <= u.posy + 1; y++) { if (x == u.posx && y == u.posy) y++; construct test; bool place_okay = (test.*(con->able))(this, point(x, y)); for (int i = 0; i < con->stages.size() && !place_okay; i++) { if (m.ter(x, y) == con->stages[i].terrain) place_okay = true; } if (place_okay) { // Make sure we're not trying to continue a construction that we can't finish int starting_stage = 0, max_stage = -1; for (int i = 0; i < con->stages.size(); i++) { if (m.ter(x, y) == con->stages[i].terrain) starting_stage = i + 1; } for(int i = starting_stage; i < con->stages.size(); i++) { if (player_can_build(u, total_inv, con, i, true, true)) max_stage = i; else break; } if (max_stage >= starting_stage) { valid.push_back(point(x, y)); m.drawsq(w_terrain, u, x, y, true, false); wrefresh(w_terrain); } } } } mvprintz(0, 0, c_red, "Pick a direction in which to construct:"); int dirx, diry; get_direction(this, dirx, diry, input()); if (dirx == -2) { add_msg("Invalid direction."); return; } dirx += u.posx; diry += u.posy; bool point_is_okay = false; for (int i = 0; i < valid.size() && !point_is_okay; i++) { if (valid[i].x == dirx && valid[i].y == diry) point_is_okay = true; } if (!point_is_okay) { construct test; if (con->name == "Move Furniture" && !(test.*(con->able))(this, point(dirx, diry))) { add_msg("You're not strong enough!"); } else { add_msg("You cannot build there!"); } return; } // Figure out what stage to start at, and what stage is the maximum int starting_stage = 0, max_stage = 0; for (int i = 0; i < con->stages.size(); i++) { if (m.ter(dirx, diry) == con->stages[i].terrain) starting_stage = i + 1; if (player_can_build(u, total_inv, con, i, true)) max_stage = i; } u.assign_activity(this, ACT_BUILD, con->stages[starting_stage].time * 1000, con->id); u.moves = 0; std::vector<int> stages; for (int i = starting_stage; i <= max_stage; i++) stages.push_back(i); u.activity.values = stages; u.activity.placement = point(dirx, diry); }
void game::construction_menu() { int iMaxY = TERMY; if (constructions.size()+2 < iMaxY) iMaxY = constructions.size()+2; if (iMaxY < 25) iMaxY = 25; WINDOW *w_con = newwin(iMaxY, 80, (TERMY > iMaxY) ? (TERMY-iMaxY)/2 : 0, (TERMX > 80) ? (TERMX-80)/2 : 0); wborder(w_con, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); mvwprintz(w_con, 0, 8, c_ltred, " Construction "); mvwputch(w_con, 0, 30, c_ltgray, LINE_OXXX); mvwputch(w_con, iMaxY-1, 30, c_ltgray, LINE_XXOX); for (int i = 1; i < iMaxY-1; i++) mvwputch(w_con, i, 30, c_ltgray, LINE_XOXO); mvwprintz(w_con, 1, 31, c_white, "Difficulty:"); wrefresh(w_con); bool update_info = true; int select = 0; int chosen = 0; long ch; bool exit = false; inventory total_inv = crafting_inventory(); do { // Erase existing list of constructions for (int i = 1; i < iMaxY-1; i++) { for (int j = 1; j < 30; j++) mvwputch(w_con, i, j, c_black, ' '); } // Determine where in the master list to start printing //int offset = select - 11; int offset = 0; if (select >= iMaxY-2) offset = select - iMaxY + 3; // Print the constructions between offset and max (or how many will fit) for (int i = 0; i < iMaxY-2 && (i + offset) < constructions.size(); i++) { int current = i + offset; nc_color col = (player_can_build(u, total_inv, constructions[current]) ? c_white : c_dkgray); // Map menu items to hotkey letters, skipping j, k, l, and q. unsigned char hotkey = 97 + current; if (hotkey > 122) hotkey = hotkey - 58; if (current == select) col = hilite(col); mvwprintz(w_con, 1 + i, 1, col, "%c %s", hotkey, constructions[current]->name.c_str()); } if (update_info) { update_info = false; constructable* current_con = constructions[select]; // Print difficulty int pskill = u.skillLevel("carpentry"); int diff = current_con->difficulty > 0 ? current_con->difficulty : 0; mvwprintz(w_con, 1, 43, (pskill >= diff ? c_white : c_red), "%d ", diff); // Clear out lines for tools & materials for (int i = 2; i < iMaxY-1; i++) { for (int j = 31; j < 79; j++) mvwputch(w_con, i, j, c_black, ' '); } // Print stages and their requirements int posx = 33, posy = 2; for (int n = 0; n < current_con->stages.size(); n++) { nc_color color_stage = (player_can_build(u, total_inv, current_con, n, false, true) ? c_white : c_dkgray); mvwprintz(w_con, posy, 31, color_stage, "Stage %d: %s", n + 1, current_con->stages[n].terrain == t_null? "" : terlist[current_con->stages[n].terrain].name.c_str()); posy++; // Print tools construction_stage stage = current_con->stages[n]; bool has_tool[10] = {stage.tools[0].empty(), stage.tools[1].empty(), stage.tools[2].empty(), stage.tools[3].empty(), stage.tools[4].empty(), stage.tools[5].empty(), stage.tools[6].empty(), stage.tools[7].empty(), stage.tools[8].empty(), stage.tools[9].empty()}; posy++; posx = 33; for (int i = 0; i < 9 && !has_tool[i]; i++) { mvwprintz(w_con, posy, posx-2, c_white, ">"); for (int j = 0; j < stage.tools[i].size(); j++) { itype_id tool = stage.tools[i][j].type; nc_color col = c_red; if (total_inv.has_amount(tool, 1)) { has_tool[i] = true; col = c_green; } int length = item_controller->find_template(tool)->name.length(); if (posx + length > 79) { posy++; posx = 33; } mvwprintz(w_con, posy, posx, col, item_controller->find_template(tool)->name.c_str()); posx += length + 1; // + 1 for an empty space if (j < stage.tools[i].size() - 1) { // "OR" if there's more if (posx > 77) { posy++; posx = 33; } mvwprintz(w_con, posy, posx, c_white, "OR"); posx += 3; } } posy += 2; posx = 33; } // Print components posx = 33; bool has_component[10] = {stage.components[0].empty(), stage.components[1].empty(), stage.components[2].empty(), stage.components[3].empty(), stage.components[4].empty(), stage.components[5].empty(), stage.components[6].empty(), stage.components[7].empty(), stage.components[8].empty(), stage.components[9].empty()}; for (int i = 0; i < 10; i++) { if (has_component[i]) continue; mvwprintz(w_con, posy, posx-2, c_white, ">"); for (int j = 0; j < stage.components[i].size() && i < 10; j++) { nc_color col = c_red; component comp = stage.components[i][j]; if (( item_controller->find_template(comp.type)->is_ammo() && total_inv.has_charges(comp.type, comp.count)) || (!item_controller->find_template(comp.type)->is_ammo() && total_inv.has_amount(comp.type, comp.count))) { has_component[i] = true; col = c_green; } int length = item_controller->find_template(comp.type)->name.length(); if (posx + length > 79) { posy++; posx = 33; } mvwprintz(w_con, posy, posx, col, "%s x%d", item_controller->find_template(comp.type)->name.c_str(), comp.count); posx += length + 3; // + 2 for " x", + 1 for an empty space // Add more space for the length of the count if (comp.count < 10) posx++; else if (comp.count < 100) posx += 2; else posx += 3; if (j < stage.components[i].size() - 1) { // "OR" if there's more if (posx > 77) { posy++; posx = 33; } mvwprintz(w_con, posy, posx, c_white, "OR"); posx += 3; } } posx = 33; posy += 2; } } wrefresh(w_con); } // Finished updating ch = getch(); switch (ch) { case KEY_DOWN: update_info = true; if (select < constructions.size() - 1) select++; else select = 0; break; case KEY_UP: update_info = true; if (select > 0) select--; else select = constructions.size() - 1; break; case ' ': case KEY_ESCAPE: case 'q': case 'Q': exit = true; break; case '\n': default: if (ch > 64 && ch < 91) //A-Z chosen = ch - 65 + 26; else if (ch > 96 && ch < 123) //a-z chosen = ch - 97; else if (ch == '\n') chosen = select; if (chosen < constructions.size()) { if (player_can_build(u, total_inv, constructions[chosen])) { place_construction(constructions[chosen]); exit = true; } else { popup("You can't build that!"); select = chosen; for (int i = 1; i < iMaxY-1; i++) mvwputch(w_con, i, 30, c_ltgray, LINE_XOXO); update_info = true; } } break; } } while (!exit); for (int i = iMaxY-25; i < iMaxY+1; i++) { for (int j = TERRAIN_WINDOW_WIDTH; j < 81; j++) mvwputch(w_con, i, j, c_black, ' '); } wrefresh(w_con); refresh_all(); }
void game::place_construction(constructable *con) { refresh_all(); inventory total_inv = crafting_inventory(&u); std::vector<point> valid; for (int x = u.posx - 1; x <= u.posx + 1; x++) { for (int y = u.posy - 1; y <= u.posy + 1; y++) { if (x == u.posx && y == u.posy) y++; construct test; bool place_okay = (test.*(con->able))(this, point(x, y)); for (int i = 0; i < con->stages.size() && !place_okay; i++) { ter_id t = con->stages[i].terrain; furn_id f = con->stages[i].furniture; if ((t != t_null || f != f_null) && (m.ter(x, y) == t || t == t_null) && (m.furn(x, y) == f || f == f_null)) place_okay = true; } if (place_okay) { // Make sure we're not trying to continue a construction that we can't finish int starting_stage = 0, max_stage = -1; for (int i = 0; i < con->stages.size(); i++) { ter_id t = con->stages[i].terrain; furn_id f = con->stages[i].furniture; if ((t != t_null || f != f_null) && (m.ter(x, y) == t || t == t_null) && (m.furn(x, y) == f || f == f_null)) starting_stage = i + 1; } if (starting_stage == con->stages.size() && con->loopstages) starting_stage = 0; // Looping stages for(int i = starting_stage; i < con->stages.size(); i++) { if (player_can_build(u, total_inv, con, i, true, true)) max_stage = i; else break; } if (max_stage >= starting_stage) { valid.push_back(point(x, y)); m.drawsq(w_terrain, u, x, y, true, false); wrefresh(w_terrain); } } } } // snip snip if (con->name == _("Move Furniture") ) { grab(); return; } // int dirx, diry; if (!choose_adjacent(_("Contruct where?"), dirx, diry)) return; bool point_is_okay = false; for (int i = 0; i < valid.size() && !point_is_okay; i++) { if (valid[i].x == dirx && valid[i].y == diry) point_is_okay = true; } if (!point_is_okay) { add_msg(_("You cannot build there!")); return; } // Figure out what stage to start at, and what stage is the maximum int starting_stage = 0, max_stage = 0; for (int i = 0; i < con->stages.size(); i++) { ter_id t = con->stages[i].terrain; furn_id f = con->stages[i].furniture; if ((t != t_null || f != f_null) && (m.ter(dirx, diry) == t || t == t_null) && (m.furn(dirx, diry) == f || f == f_null)) starting_stage = i + 1; if (player_can_build(u, total_inv, con, i, true)) max_stage = i; } if (starting_stage == con->stages.size() && con->loopstages) starting_stage = 0; // Looping stages u.assign_activity(this, ACT_BUILD, con->stages[starting_stage].time * 1000, con->id); u.moves = 0; std::vector<int> stages; for (int i = starting_stage; i <= max_stage; i++) stages.push_back(i); u.activity.values = stages; u.activity.placement = point(dirx, diry); }
void game::construction_menu() { WINDOW *w_con = newwin(25, 80, 0, 0); wborder(w_con, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); mvwprintz(w_con, 0, 1, c_red, "Construction"); mvwputch(w_con, 0, 30, c_white, LINE_OXXX); mvwputch(w_con, 24, 30, c_white, LINE_XXOX); for (int i = 1; i < 24; i++) mvwputch(w_con, i, 30, c_white, LINE_XOXO); mvwprintz(w_con, 1, 31, c_white, "Difficulty:"); wrefresh(w_con); bool update_info = true; unsigned int select = 0; char ch; inventory total_inv = crafting_inventory(); do { // Erase existing list of constructions for (int i = 1; i < 24; i++) { for (int j = 1; j < 29; j++) mvwputch(w_con, i, j, c_black, 'x'); } // Determine where in the master list to start printing //int offset = select - 11; int offset = 0; if (select >= 22) offset = select - 22; // Print the constructions between offset and max (or how many will fit) for (int i = 0; i <= 22 && (i + offset) < constructions.size(); i++) { int current = i + offset; nc_color col = (player_can_build(u, total_inv, constructions[current]) ? c_white : c_dkgray); // Map menu items to hotkey letters, skipping j, k, l, and q. char hotkey = current + ((current < 9) ? 97 : ((current < 13) ? 100 : 101)); if (current == select) col = hilite(col); mvwprintz(w_con, 1 + i, 1, col, "%c %s", hotkey, constructions[current]->name.c_str()); } if (update_info) { update_info = false; constructable* current_con = constructions[select]; // Print difficulty int pskill = u.skillLevel("carpentry").level(); int diff = current_con->difficulty > 0 ? current_con->difficulty : 0; mvwprintz(w_con, 1, 43, (pskill >= diff ? c_white : c_red), "%d ", diff); // Clear out lines for tools & materials for (int i = 2; i < 24; i++) { for (int j = 31; j < 79; j++) mvwputch(w_con, i, j, c_black, 'x'); } // Print stages and their requirements int posx = 33, posy = 2; for (int n = 0; n < current_con->stages.size(); n++) { nc_color color_stage = (player_can_build(u, total_inv, current_con, n, false, true) ? c_white : c_dkgray); mvwprintz(w_con, posy, 31, color_stage, "Stage %d: %s", n + 1, current_con->stages[n].terrain == t_null? "" : terlist[current_con->stages[n].terrain].name.c_str()); posy++; // Print tools construction_stage stage = current_con->stages[n]; bool has_tool[3] = {stage.tools[0].empty(), stage.tools[1].empty(), stage.tools[2].empty()}; for (int i = 0; i < 3 && !has_tool[i]; i++) { posy++; posx = 33; for (int j = 0; j < stage.tools[i].size(); j++) { itype_id tool = stage.tools[i][j]; nc_color col = c_red; if (total_inv.has_amount(tool, 1)) { has_tool[i] = true; col = c_green; } int length = itypes[tool]->name.length(); if (posx + length > 79) { posy++; posx = 33; } mvwprintz(w_con, posy, posx, col, itypes[tool]->name.c_str()); posx += length + 1; // + 1 for an empty space if (j < stage.tools[i].size() - 1) { // "OR" if there's more if (posx > 77) { posy++; posx = 33; } mvwprintz(w_con, posy, posx, c_white, "OR"); posx += 3; } } } // Print components posy++; posx = 33; bool has_component[3] = {stage.components[0].empty(), stage.components[1].empty(), stage.components[2].empty()}; for (int i = 0; i < 3; i++) { posx = 33; while (has_component[i]) i++; for (int j = 0; j < stage.components[i].size() && i < 3; j++) { nc_color col = c_red; component comp = stage.components[i][j]; if (( itypes[comp.type]->is_ammo() && total_inv.has_charges(comp.type, comp.count)) || (!itypes[comp.type]->is_ammo() && total_inv.has_amount(comp.type, comp.count))) { has_component[i] = true; col = c_green; } int length = itypes[comp.type]->name.length(); if (posx + length > 79) { posy++; posx = 33; } mvwprintz(w_con, posy, posx, col, "%s x%d", itypes[comp.type]->name.c_str(), comp.count); posx += length + 3; // + 2 for " x", + 1 for an empty space // Add more space for the length of the count if (comp.count < 10) posx++; else if (comp.count < 100) posx += 2; else posx += 3; if (j < stage.components[i].size() - 1) { // "OR" if there's more if (posx > 77) { posy++; posx = 33; } mvwprintz(w_con, posy, posx, c_white, "OR"); posx += 3; } } posy++; } } wrefresh(w_con); } // Finished updating ch = input(); switch (ch) { case 'j': update_info = true; if (select < constructions.size() - 1) select++; else select = 0; break; case 'k': update_info = true; if (select > 0) select--; else select = constructions.size() - 1; break; case '\n': case 'l': if (player_can_build(u, total_inv, constructions[select])) { place_construction(constructions[select]); ch = 'q'; } else { popup("You can't build that!"); for (int i = 1; i < 24; i++) mvwputch(w_con, i, 30, c_white, LINE_XOXO); update_info = true; } break; case 'q': case 'Q': case KEY_ESCAPE: break; default: if (ch < 96 || unsigned(ch) > constructions.size() + 101) break; // Map menu items to hotkey letters, skipping j, k, l, and q. char hotkey = ch - ((ch < 106) ? 97 : ((ch < 112) ? 100 : 101)); if (player_can_build(u, total_inv, constructions[hotkey])) { place_construction(constructions[hotkey]); ch = 'q'; } else { popup("You can't build that!"); for (int i = 1; i < 24; i++) mvwputch(w_con, i, 30, c_white, LINE_XOXO); update_info = true; } break; } } while (ch != 'q' && ch != 'Q' && ch != KEY_ESCAPE); refresh_all(); }
void player::complete_craft() { const recipe *making = &recipe_dict[ activity.name ]; // Which recipe is it? int batch_size = activity.values.front(); if( making == nullptr ) { debugmsg( "no recipe with id %s found", activity.name.c_str() ); activity.set_to_null(); return; } int secondary_dice = 0; int secondary_difficulty = 0; for( const auto &pr : making->required_skills ) { secondary_dice += get_skill_level( pr.first ); secondary_difficulty += pr.second; } // # of dice is 75% primary skill, 25% secondary (unless secondary is null) int skill_dice; if( secondary_difficulty > 0 ) { skill_dice = get_skill_level( making->skill_used ) * 3 + secondary_dice; } else { skill_dice = get_skill_level( making->skill_used ) * 4; } auto helpers = g->u.get_crafting_helpers(); for( const npc *np : helpers ) { if( np->get_skill_level( making->skill_used ) >= get_skill_level( making->skill_used ) ) { // NPC assistance is worth half a skill level skill_dice += 2; add_msg( m_info, _( "%s helps with crafting..." ), np->name.c_str() ); break; } } // farsightedness can impose a penalty on electronics and tailoring success // it's equivalent to a 2-rank electronics penalty, 1-rank tailoring if( has_trait( trait_id( "HYPEROPIC" ) ) && !is_wearing( "glasses_reading" ) && !is_wearing( "glasses_bifocal" ) && !has_effect( effect_contacts ) ) { int main_rank_penalty = 0; if( making->skill_used == skill_id( "electronics" ) ) { main_rank_penalty = 2; } else if( making->skill_used == skill_id( "tailor" ) ) { main_rank_penalty = 1; } skill_dice -= main_rank_penalty * 4; } // It's tough to craft with paws. Fortunately it's just a matter of grip and fine-motor, // not inability to see what you're doing if( has_trait( trait_PAWS ) || has_trait( trait_PAWS_LARGE ) ) { int paws_rank_penalty = 0; if( has_trait( trait_PAWS_LARGE ) ) { paws_rank_penalty += 1; } if( making->skill_used == skill_id( "electronics" ) || making->skill_used == skill_id( "tailor" ) || making->skill_used == skill_id( "mechanics" ) ) { paws_rank_penalty += 1; } skill_dice -= paws_rank_penalty * 4; } // Sides on dice is 16 plus your current intelligence ///\EFFECT_INT increases crafting success chance int skill_sides = 16 + int_cur; int diff_dice; if( secondary_difficulty > 0 ) { diff_dice = making->difficulty * 3 + secondary_difficulty; } else { // Since skill level is * 4 also diff_dice = making->difficulty * 4; } int diff_sides = 24; // 16 + 8 (default intelligence) int skill_roll = dice( skill_dice, skill_sides ); int diff_roll = dice( diff_dice, diff_sides ); if( making->skill_used ) { const double batch_mult = 1 + time_to_craft( *making, batch_size ) / 30000.0; //normalize experience gain to crafting time, giving a bonus for longer crafting practice( making->skill_used, ( int )( ( making->difficulty * 15 + 10 ) * batch_mult ), ( int )making->difficulty * 1.25 ); //NPCs assisting or watching should gain experience... for( auto &elem : helpers ) { //If the NPC can understand what you are doing, they gain more exp if( elem->get_skill_level( making->skill_used ) >= making->difficulty ) { elem->practice( making->skill_used, ( int )( ( making->difficulty * 15 + 10 ) * batch_mult * .50 ), ( int )making->difficulty * 1.25 ); if( batch_size > 1 ) { add_msg( m_info, _( "%s assists with crafting..." ), elem->name.c_str() ); } if( batch_size == 1 ) { add_msg( m_info, _( "%s could assist you with a batch..." ), elem->name.c_str() ); } //NPCs around you understand the skill used better } else { elem->practice( making->skill_used, ( int )( ( making->difficulty * 15 + 10 ) * batch_mult * .15 ), ( int )making->difficulty * 1.25 ); add_msg( m_info, _( "%s watches you craft..." ), elem->name.c_str() ); } } } // Messed up badly; waste some components. if( making->difficulty != 0 && diff_roll > skill_roll * ( 1 + 0.1 * rng( 1, 5 ) ) ) { add_msg( m_bad, _( "You fail to make the %s, and waste some materials." ), item::nname( making->result ).c_str() ); if( last_craft->has_cached_selections() ) { last_craft->consume_components(); } else { // @todo Guarantee that selections are cached const auto &req = making->requirements(); for( const auto &it : req.get_components() ) { consume_items( it, batch_size ); } for( const auto &it : req.get_tools() ) { consume_tools( it, batch_size ); } } activity.set_to_null(); return; // Messed up slightly; no components wasted. } else if( diff_roll > skill_roll ) { add_msg( m_neutral, _( "You fail to make the %s, but don't waste any materials." ), item::nname( making->result ).c_str() ); //this method would only have been called from a place that nulls activity.type, //so it appears that it's safe to NOT null that variable here. //rationale: this allows certain contexts (e.g. ACT_LONGCRAFT) to distinguish major and minor failures return; } // If we're here, the craft was a success! // Use up the components and tools std::list<item> used; if( !last_craft->has_cached_selections() ) { // This should fail and return, but currently crafting_command isn't saved // Meaning there are still cases where has_cached_selections will be false // @todo Allow saving last_craft and debugmsg+fail craft if selection isn't cached if( !has_trait( trait_id( "DEBUG_HS" ) ) ) { const auto &req = making->requirements(); for( const auto &it : req.get_components() ) { std::list<item> tmp = consume_items( it, batch_size ); used.splice( used.end(), tmp ); } for( const auto &it : req.get_tools() ) { consume_tools( it, batch_size ); } } } else if( !has_trait( trait_id( "DEBUG_HS" ) ) ) { used = last_craft->consume_components(); if( used.empty() ) { return; } } // Set up the new item, and assign an inventory letter if available std::vector<item> newits = making->create_results( batch_size ); bool first = true; float used_age_tally = 0; int used_age_count = 0; size_t newit_counter = 0; for( item &newit : newits ) { // messages, learning of recipe, food spoilage calc only once if( first ) { first = false; if( knows_recipe( making ) ) { add_msg( _( "You craft %s from memory." ), newit.type_name( 1 ).c_str() ); } else { add_msg( _( "You craft %s using a book as a reference." ), newit.type_name( 1 ).c_str() ); // If we made it, but we don't know it, // we're making it from a book and have a chance to learn it. // Base expected time to learn is 1000*(difficulty^4)/skill/int moves. // This means time to learn is greatly decreased with higher skill level, // but also keeps going up as difficulty goes up. // Worst case is lvl 10, which will typically take // 10^4/10 (1,000) minutes, or about 16 hours of crafting it to learn. int difficulty = has_recipe( making, crafting_inventory(), helpers ); ///\EFFECT_INT increases chance to learn recipe when crafting from a book if( x_in_y( making->time, ( 1000 * 8 * ( difficulty * difficulty * difficulty * difficulty ) ) / ( std::max( get_skill_level( making->skill_used ).level(), 1 ) * std::max( get_int(), 1 ) ) ) ) { learn_recipe( ( recipe * )making ); add_msg( m_good, _( "You memorized the recipe for %s!" ), newit.type_name( 1 ).c_str() ); } } for( auto &elem : used ) { if( elem.goes_bad() ) { used_age_tally += elem.get_relative_rot(); ++used_age_count; } } } // Don't store components for things made by charges, // don't store components for things that can't be uncrafted. if( recipe_dictionary::get_uncraft( making->result ) && !newit.count_by_charges() ) { // Setting this for items counted by charges gives only problems: // those items are automatically merged everywhere (map/vehicle/inventory), // which would either loose this information or merge it somehow. set_components( newit.components, used, batch_size, newit_counter ); newit_counter++; } finalize_crafted_item( newit, used_age_tally, used_age_count ); set_item_inventory( newit ); } if( making->has_byproducts() ) { std::vector<item> bps = making->create_byproducts( batch_size ); for( auto &bp : bps ) { finalize_crafted_item( bp, used_age_tally, used_age_count ); set_item_inventory( bp ); } } inv.restack( this ); }