Example #1
0
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 );
}
Example #2
0
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;
}
Example #3
0
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);
}
Example #4
0
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);
}
Example #6
0
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();
}
Example #7
0
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 );
}