void game::complete_construction() { inventory map_inv; map_inv.form_from_map(this, point(u.posx, u.posy), PICKUP_RANGE); int stage_num = u.activity.values[0]; constructable *built = constructions[u.activity.index]; construction_stage stage = built->stages[stage_num]; std::vector<component> player_use; std::vector<component> map_use; u.practice(sk_carpentry, built->difficulty * 10); if (built->difficulty < 1) u.practice(sk_carpentry, 10); for (int i = 0; i < 3; i++) { if (!stage.components[i].empty()) consume_items(this, stage.components[i]); } // Make the terrain change int terx = u.activity.placement.x, tery = u.activity.placement.y; m.ter(terx, tery) = stage.terrain; construct effects; (effects.*(built->done))(this, point(terx, tery)); // Strip off the first stage in our list... u.activity.values.erase(u.activity.values.begin()); // ...and start the next one, if it exists if (u.activity.values.size() > 0) { construction_stage next = built->stages[u.activity.values[0]]; u.activity.moves_left = next.time * 1000; } else // We're finished! u.activity.type = ACT_NULL; }
void game::complete_construction() { int stage_num = u.activity.values[0]; constructable *built = constructions[u.activity.index]; construction_stage stage = built->stages[stage_num]; u.practice(turn, "carpentry", std::max(built->difficulty, 1) * 10); for (int i = 0; i < 10; i++) { if (!stage.components[i].empty()) { consume_items(&u, stage.components[i]); } } // Make the terrain change int terx = u.activity.placement.x, tery = u.activity.placement.y; if (stage.terrain != t_null) { m.ter_set(terx, tery, stage.terrain); } if (stage.furniture != f_null) { m.furn_set(terx, tery, stage.furniture); } // Strip off the first stage in our list... u.activity.values.erase(u.activity.values.begin()); // ...and start the next one, if it exists if (u.activity.values.size() > 0) { construction_stage next = built->stages[u.activity.values[0]]; u.activity.moves_left = next.time * 1000; } else { // We're finished! u.activity.type = ACT_NULL; } // This comes after clearing the activity, in case the function interrupts // activities construct effects; (effects.*(built->done))(this, point(terx, tery)); }
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 ); }
/* This call is in-efficient when doing it for multiple items with the same map inventory. In that case, consider using select_item_component with 1 pre-created map inventory, and then passing the results to consume_items */ std::list<item> player::consume_items( const std::vector<item_comp> &components, int batch ) { inventory map_inv; map_inv.form_from_map( pos(), PICKUP_RANGE ); return consume_items( select_item_component( components, batch, map_inv ), batch ); }
void game::complete_craft() { recipe* making = recipes[u.activity.index]; // Which recipe is it? // # of dice is 75% primary skill, 25% secondary (unless secondary is null) int skill_dice = u.sklevel[making->sk_primary] * 3; if (making->sk_secondary == sk_null) skill_dice += u.sklevel[making->sk_primary]; else skill_dice += u.sklevel[making->sk_secondary]; // Sides on dice is 16 plus your current intelligence int skill_sides = 16 + u.int_cur; int diff_dice = making->difficulty * 4; // Since skill level is * 4 also 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->sk_primary != sk_null) u.practice(making->sk_primary, making->difficulty * 5 + 20); if (making->sk_secondary != sk_null) u.practice(making->sk_secondary, 5); // Messed up badly; waste some components. if (making->difficulty != 0 && diff_roll > skill_roll * (1 + 0.1 * rng(1, 5))) { add_msg("You fail to make the %s, and waste some materials.", itypes[making->result]->name.c_str()); for (int i = 0; i < 5; i++) { if (making->components[i].size() > 0) { std::vector<component> copy = making->components[i]; for (int j = 0; j < copy.size(); j++) copy[j].count = rng(0, copy[j].count); consume_items(this, copy); } if (making->tools[i].size() > 0) consume_tools(this, making->tools[i]); } u.activity.type = ACT_NULL; return; // Messed up slightly; no components wasted. } else if (diff_roll > skill_roll) { add_msg("You fail to make the %s, but don't waste any materials.", itypes[making->result]->name.c_str()); u.activity.type = ACT_NULL; return; } // If we're here, the craft was a success! // Use up the components and tools for (int i = 0; i < 5; i++) { if (making->components[i].size() > 0) consume_items(this, making->components[i]); if (making->tools[i].size() > 0) consume_tools(this, making->tools[i]); } // Set up the new item, and pick an inventory letter int iter = 0; item newit(itypes[making->result], turn, nextinv); if (!newit.craft_has_charges()) newit.charges = 0; do { newit.invlet = nextinv; advance_nextinv(); iter++; } while (u.has_item(newit.invlet) && iter < 52); //newit = newit.in_its_container(&itypes); if (newit.made_of(LIQUID)) handle_liquid(newit, false, false); else { // We might not have space for the item if (iter == 52 || u.volume_carried()+newit.volume() > u.volume_capacity()) { add_msg("There's no room in your inventory for the %s, so you drop it.", newit.tname().c_str()); m.add_item(u.posx, u.posy, newit); } else if (u.weight_carried() + newit.volume() > u.weight_capacity()) { add_msg("The %s is too heavy to carry, so you drop it.", newit.tname().c_str()); m.add_item(u.posx, u.posy, newit); } else { u.i_add(newit); add_msg("%c - %s", newit.invlet, newit.tname().c_str()); } } }