SkillLevel const& Character::get_skill_level(const skill_id &ident) const { if( !ident ) { static const SkillLevel none{}; return none; } return get_skill_level( &ident.obj() ); }
int SkillLevelMap::exceeds_recipe_requirements( const recipe &rec ) const { int over = rec.skill_used ? get_skill_level( rec.skill_used ) - rec.difficulty : 0; for( const auto &elem : compare_skill_requirements( rec.required_skills ) ) { over = std::min( over, elem.second ); } return over; }
bool SkillLevelMap::meets_skill_requirements( const std::map<skill_id, int> &req, const item &context ) const { return std::all_of( req.begin(), req.end(), [this, &context]( const std::pair<skill_id, int> &pr ) { return get_skill_level( pr.first, context ) >= pr.second; } ); }
std::map<skill_id, int> SkillLevelMap::compare_skill_requirements( const std::map<skill_id, int> &req, const item &context ) const { std::map<skill_id, int> res; for( const auto &elem : req ) { const int diff = get_skill_level( elem.first, context ) - elem.second; if( diff != 0 ) { res[elem.first] = diff; } } return res; }
int Character::skill_dispersion( const item& gun, bool random ) const { static skill_id skill_gun( "gun" ); int dispersion = 0; // Measured in Minutes of Arc. const int lvl = get_skill_level( gun.gun_skill() ); if( lvl < 10 ) { // Up to 0.75 degrees for each skill point < 10. ///\EFFECT_PISTOL <10 randomly increases dispersion for pistols ///\EFFECT_SMG <10 randomly increases dispersion for smgs ///\EFFECT_RIFLE <10 randomly increases dispersion for rifles ///\EFFECT_LAUNCHER <10 randomly increases dispersion for launchers dispersion += 45 * ( 10 - lvl ); } if( get_skill_level( skill_gun ) < 10 ) { // Up to 0.25 deg per each skill point < 10. ///\EFFECT_GUN <10 randomly increased dispersion of all gunfire dispersion += 15 * ( 10 - lvl ); } return random ? rng(0, dispersion) : dispersion; }
bool player::can_arm_block() const { const martialart &ma = style_selected.obj(); ///\EFFECT_UNARMED increases ability to perform arm block int unarmed_skill = has_active_bionic( bionic_id( "bio_cqb" ) ) ? 5 : (int)get_skill_level(skill_id("unarmed")); // Success conditions. if (hp_cur[hp_arm_l] > 0 || hp_cur[hp_arm_r] > 0) { if( unarmed_skill >= ma.arm_block ) { return true; } else if( ma.arm_block_with_bio_armor_arms && has_bionic( bionic_id( "bio_armor_arms" ) ) ) { return true; } } // if not above, can't block. return false; }
SkillLevel const& Character::get_skill_level(const Skill &_skill) const { return get_skill_level(&_skill); }
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 ); }
void player::complete_disassemble( int item_pos, const tripoint &loc, bool from_ground, const recipe &dis ) { // Get the proper recipe - the one for disassembly, not assembly const auto dis_requirements = dis.disassembly_requirements(); item &org_item = get_item_for_uncraft( *this, item_pos, loc, from_ground ); bool filthy = org_item.is_filthy(); if( org_item.is_null() ) { add_msg( _( "The item has vanished." ) ); activity.set_to_null(); return; } if( org_item.typeId() != dis.result ) { add_msg( _( "The item might be gone, at least it is not at the expected position anymore." ) ); activity.set_to_null(); return; } // Make a copy to keep its data (damage/components) even after it // has been removed. item dis_item = org_item; float component_success_chance = std::min( std::pow( 0.8, dis_item.damage() ), 1.0 ); add_msg( _( "You disassemble the %s into its components." ), dis_item.tname().c_str() ); // Remove any batteries, ammo and mods first remove_ammo( &dis_item, *this ); remove_radio_mod( dis_item, *this ); if( dis_item.count_by_charges() ) { // remove the charges that one would get from crafting it org_item.charges -= dis.create_result().charges; } // remove the item, except when it's counted by charges and still has some if( !org_item.count_by_charges() || org_item.charges <= 0 ) { if( from_ground ) { g->m.i_rem( loc, item_pos ); } else { i_rem( item_pos ); } } // Consume tool charges for( const auto &it : dis_requirements.get_tools() ) { consume_tools( it ); } // add the components to the map // Player skills should determine how many components are returned int skill_dice = 2 + get_skill_level( dis.skill_used ) * 3; skill_dice += get_skill_level( dis.skill_used ); // Sides on dice is 16 plus your current intelligence ///\EFFECT_INT increases success rate for disassembling items int skill_sides = 16 + int_cur; int diff_dice = dis.difficulty; int diff_sides = 24; // 16 + 8 (default intelligence) // disassembly only nets a bit of practice if( dis.skill_used ) { practice( dis.skill_used, ( dis.difficulty ) * 2, dis.difficulty ); } for( const auto &altercomps : dis_requirements.get_components() ) { const item_comp comp = find_component( altercomps, dis_item ); int compcount = comp.count; item newit( comp.type, calendar::turn ); // Counted-by-charge items that can be disassembled individually // have their component count multiplied by the number of charges. if( dis_item.count_by_charges() && dis.has_flag( "UNCRAFT_SINGLE_CHARGE" ) ) { compcount *= std::min( dis_item.charges, dis.create_result().charges ); } // Compress liquids and counted-by-charges items into one item, // they are added together on the map anyway and handle_liquid // should only be called once to put it all into a container at once. if( newit.count_by_charges() || newit.made_of( LIQUID ) ) { newit.charges = compcount; compcount = 1; } else if( !newit.craft_has_charges() && newit.charges > 0 ) { // tools that can be unloaded should be created unloaded, // tools that can't be unloaded will keep their default charges. newit.charges = 0; } for( ; compcount > 0; compcount-- ) { const bool comp_success = ( dice( skill_dice, skill_sides ) > dice( diff_dice, diff_sides ) ); if( dis.difficulty != 0 && !comp_success ) { add_msg( m_bad, _( "You fail to recover %s." ), newit.tname().c_str() ); continue; } const bool dmg_success = component_success_chance > rng_float( 0, 1 ); if( !dmg_success ) { // Show reason for failure (damaged item, tname contains the damage adjective) //~ %1s - material, %2$s - disassembled item add_msg( m_bad, _( "You fail to recover %1$s from the %2$s." ), newit.tname().c_str(), dis_item.tname().c_str() ); continue; } // Use item from components list, or (if not contained) // use newit, the default constructed. item act_item = newit; if( filthy ) { act_item.item_tags.insert( "FILTHY" ); } for( item::t_item_vector::iterator a = dis_item.components.begin(); a != dis_item.components.end(); ++a ) { if( a->type == newit.type ) { act_item = *a; dis_item.components.erase( a ); break; } } int veh_part = -1; vehicle *veh = g->m.veh_at( pos(), veh_part ); if( veh != nullptr ) { veh_part = veh->part_with_feature( veh_part, "CARGO" ); } if( act_item.made_of( LIQUID ) ) { g->handle_all_liquid( act_item, PICKUP_RANGE ); } else if( veh_part != -1 && veh->add_item( veh_part, act_item ) ) { // add_item did put the items in the vehicle, nothing further to be done } else { // TODO: For items counted by charges, add as much as we can to the vehicle, and // the rest on the ground (see dropping code and @vehicle::add_charges) g->m.add_item_or_charges( pos(), act_item ); } } } if( !dis.learn_by_disassembly.empty() && !knows_recipe( &dis ) ) { if( can_decomp_learn( dis ) ) { // @todo: make this depend on intelligence if( one_in( 4 ) ) { learn_recipe( &recipe_dict[ dis.ident() ] ); add_msg( m_good, _( "You learned a recipe from disassembling it!" ) ); } else { add_msg( m_info, _( "You might be able to learn a recipe if you disassemble another." ) ); } } else { add_msg( m_info, _( "If you had better skills, you might learn a recipe next time." ) ); } } }
SkillLevel const& Character::get_skill_level(const std::string &ident) const { const Skill* sk = Skill::skill(ident); return get_skill_level(sk); }
/* * Variables common to player and npc */ void player::json_save_common_variables(JsonOut &json) const { // assumes already in player object // positional data json.member( "posx", posx ); json.member( "posy", posy ); // attributes, current / max levels json.member( "str_cur", str_cur ); json.member( "str_max", str_max ); json.member( "dex_cur", dex_cur ); json.member( "dex_max", dex_max ); json.member( "int_cur", int_cur ); json.member( "int_max", int_max ); json.member( "per_cur", per_cur ); json.member( "per_max", per_max ); // om-noms or lack thereof json.member( "hunger", hunger ); json.member( "thirst", thirst ); // energy json.member( "fatigue", fatigue ); json.member( "stim", stim ); // pain json.member( "pain", pain ); json.member( "pkill", pkill ); // misc levels json.member( "radiation", radiation ); json.member( "scent", int(scent) ); // initiative type stuff json.member( "moves", moves ); json.member( "dodges_left", num_dodges); // breathing json.member( "underwater", underwater ); json.member( "oxygen", oxygen ); // gender json.member( "male", male ); json.member( "cash", cash ); json.member( "recoil", int(recoil) ); // potential incompatibility with future expansion // todo: consider ["parts"]["head"]["hp_cur"] instead of ["hp_cur"][head_enum_value] json.member( "hp_cur", std::vector<int>( hp_cur, hp_cur + num_hp_parts ) ); json.member( "hp_max", std::vector<int>( hp_max, hp_max + num_hp_parts ) ); // npc; unimplemented json.member( "power_level", power_level ); json.member( "max_power_level", max_power_level ); // traits: permanent 'mutations' more or less json.member( "traits", my_traits ); // skills json.member( "skills" ); json.start_object(); for (std::vector<Skill*>::iterator aSkill = Skill::skills.begin(); aSkill != Skill::skills.end(); ++aSkill) { SkillLevel sk = get_skill_level(*aSkill); json.member((*aSkill)->ident(), sk); } json.end_object(); // martial arts /*for (int i = 0; i < ma_styles.size(); i++) { ptmpvect.push_back( pv( ma_styles[i] ) ); }*/ json.member( "ma_styles", ma_styles ); // disease json.member( "illness", illness ); json.member( "effects", effects ); // "Looks like I picked the wrong week to quit smoking." - Steve McCroskey json.member( "addictions", addictions ); // "Fracking Toasters" - Saul Tigh, toaster json.member( "my_bionics", my_bionics ); }
int SkillLevelMap::get_skill_level( const skill_id &ident, const item &context ) const { const auto id = context.is_null() ? ident : context.contextualize_skill( ident ); return get_skill_level( id ); }