bool tutorial_game::init() { // TODO: clean up old tutorial calendar::turn = HOURS( 12 ); // Start at noon for( auto &elem : tutorials_seen ) { elem = false; } g->scent.reset(); g->temperature = 65; // We use a Z-factor of 10 so that we don't plop down tutorial rooms in the // middle of the "real" game world g->u.normalize(); g->u.str_cur = g->u.str_max; g->u.per_cur = g->u.per_max; g->u.int_cur = g->u.int_max; g->u.dex_cur = g->u.dex_max; for( int i = 0; i < num_hp_parts; i++ ) { g->u.hp_cur[i] = g->u.hp_max[i]; } const oter_id rock( "rock" ); //~ default name for the tutorial g->u.name = _( "John Smith" ); g->u.prof = profession::generic(); // overmap terrain coordinates const int lx = 50; const int ly = 50; auto &starting_om = overmap_buffer.get( 0, 0 ); for( int i = 0; i < OMAPX; i++ ) { for( int j = 0; j < OMAPY; j++ ) { starting_om.ter( i, j, -1 ) = rock; // Start with the overmap revealed starting_om.seen( i, j, 0 ) = true; } } starting_om.ter( lx, ly, 0 ) = oter_id( "tutorial" ); starting_om.ter( lx, ly, -1 ) = oter_id( "tutorial" ); starting_om.clear_mon_groups(); g->u.toggle_trait( trait_id( "QUICK" ) ); item lighter( "lighter", 0 ); lighter.invlet = 'e'; g->u.inv.add_item( lighter, true, false ); g->u.set_skill_level( skill_id( "gun" ), 5 ); g->u.set_skill_level( skill_id( "melee" ), 5 ); g->load_map( omt_to_sm_copy( tripoint( lx, ly, 0 ) ) ); g->u.setx( 2 ); g->u.sety( 4 ); // This shifts the view to center the players pos g->update_map( g->u ); return true; }
void fault::load_fault( JsonObject &jo ) { fault f; f.id_ = fault_id( jo.get_string( "id" ) ); f.name_ = _( jo.get_string( "name" ) ); f.description_ = _( jo.get_string( "description" ) ); f.time_ = jo.get_int( "time" ); auto sk = jo.get_array( "skills" ); while( sk.has_more() ) { auto cur = sk.next_array(); f.skills_.emplace( skill_id( cur.get_string( 0 ) ), cur.size() >= 2 ? cur.get_int( 1 ) : 1 ); } if( jo.has_string( "requirements" ) ) { f.requirements_ = requirement_id( jo.get_string( "requirements" ) ); } else { auto req = jo.get_object( "requirements" ); const requirement_id req_id( std::string( "inline_fault_" ) + f.id_.str() ); requirement_data::load_requirement( req, req_id ); f.requirements_ = req_id; } if( faults_all.find( f.id_ ) != faults_all.end() ) { jo.throw_error( "parsed fault overwrites existing definition", "id" ); } else { faults_all[ f.id_ ] = f; } }
void npc_class::load( JsonObject &jo, const std::string & ) { mandatory( jo, was_loaded, "name", name, translated_string_reader ); mandatory( jo, was_loaded, "job_description", job_description, translated_string_reader ); optional( jo, was_loaded, "common", common, true ); bonus_str = load_distribution( jo, "bonus_str" ); bonus_dex = load_distribution( jo, "bonus_dex" ); bonus_int = load_distribution( jo, "bonus_int" ); bonus_per = load_distribution( jo, "bonus_per" ); optional( jo, was_loaded, "shopkeeper_item_group", shopkeeper_item_group, "EMPTY_GROUP" ); optional( jo, was_loaded, "worn_override", worn_override ); optional( jo, was_loaded, "carry_override", carry_override ); optional( jo, was_loaded, "weapon_override", weapon_override ); if( jo.has_array( "traits" ) ) { JsonArray jarr = jo.get_array( "traits" ); while( jarr.has_more() ) { JsonArray jarr_in = jarr.next_array(); traits[ trait_id( jarr_in.get_string( 0 ) ) ] = jarr_in.get_int( 1 ); } } if( jo.has_array( "skills" ) ) { JsonArray jarr = jo.get_array( "skills" ); while( jarr.has_more() ) { JsonObject skill_obj = jarr.next_object(); auto skill_ids = skill_obj.get_tags( "skill" ); if( skill_obj.has_object( "level" ) ) { distribution dis = load_distribution( skill_obj, "level" ); for( const auto &sid : skill_ids ) { skills[ skill_id( sid ) ] = dis; } } else { distribution dis = load_distribution( skill_obj, "bonus" ); for( const auto &sid : skill_ids ) { bonus_skills[ skill_id( sid ) ] = dis; } } } } }
void fungal_effects::fungalize( const tripoint &sporep, Creature *origin, double spore_chance ) { int mondex = gm.mon_at( sporep ); if( mondex != -1 ) { // Spores hit a monster if( gm.u.sees( sporep ) && !gm.zombie( mondex ).type->in_species( FUNGUS ) ) { add_msg( _( "The %s is covered in tiny spores!" ), gm.zombie( mondex ).name().c_str() ); } monster &critter = gm.zombie( mondex ); if( !critter.make_fungus() ) { // Don't insta-kill non-fungables. Jabberwocks, for example critter.add_effect( effect_stunned, rng( 1, 3 ) ); critter.apply_damage( origin, bp_torso, rng( 25, 50 ) ); } } else if( gm.u.pos() == sporep ) { player &pl = gm.u; // TODO: Make this accept NPCs when they understand fungals ///\EFFECT_DEX increases chance of knocking fungal spores away with your TAIL_CATTLE ///\EFFECT_MELEE increases chance of knocking fungal sports away with your TAIL_CATTLE if( pl.has_trait( trait_id( "TAIL_CATTLE" ) ) && one_in( 20 - pl.dex_cur - pl.get_skill_level( skill_id( "melee" ) ) ) ) { pl.add_msg_if_player( _( "The spores land on you, but you quickly swat them off with your tail!" ) ); return; } // Spores hit the player--is there any hope? bool hit = false; hit |= one_in( 4 ) && pl.add_env_effect( effect_spores, bp_head, 3, 90, bp_head ); hit |= one_in( 2 ) && pl.add_env_effect( effect_spores, bp_torso, 3, 90, bp_torso ); hit |= one_in( 4 ) && pl.add_env_effect( effect_spores, bp_arm_l, 3, 90, bp_arm_l ); hit |= one_in( 4 ) && pl.add_env_effect( effect_spores, bp_arm_r, 3, 90, bp_arm_r ); hit |= one_in( 4 ) && pl.add_env_effect( effect_spores, bp_leg_l, 3, 90, bp_leg_l ); hit |= one_in( 4 ) && pl.add_env_effect( effect_spores, bp_leg_r, 3, 90, bp_leg_r ); if( hit ) { add_msg( m_warning, _( "You're covered in tiny spores!" ) ); } } else if( gm.num_zombies() < 250 && x_in_y( spore_chance, 1.0 ) ) { // Spawn a spore if( gm.summon_mon( mon_spore, sporep ) ) { monster *spore = gm.monster_at( sporep ); monster *origin_mon = dynamic_cast<monster *>( origin ); if( origin_mon != nullptr ) { spore->make_ally( origin_mon ); } else if( origin != nullptr && origin->is_player() && gm.u.has_trait( trait_id( "THRESH_MYCUS" ) ) ) { spore->friendly = 1000; } } } else { spread_fungus( sporep ); } }
void Skill::load_skill(JsonObject &jsobj) { skill_id ident = skill_id( jsobj.get_string("ident") ); skills.erase(std::remove_if(begin(skills), end(skills), [&](Skill const &s) { return s._ident == ident; }), end(skills)); std::string name = _(jsobj.get_string("name").c_str()); std::string description = _(jsobj.get_string("description").c_str()); std::set<std::string> tags = jsobj.get_tags("tags"); DebugLog( D_INFO, DC_ALL ) << "Loaded skill: " << name; skills.emplace_back(std::move(ident), std::move(name), std::move(description), std::move(tags)); }
void Skill::load_skill( JsonObject &jsobj ) { skill_id ident = skill_id( jsobj.get_string( "ident" ) ); skills.erase( std::remove_if( begin( skills ), end( skills ), [&]( const Skill & s ) { return s._ident == ident; } ), end( skills ) ); const Skill sk( ident, _( jsobj.get_string( "name" ) ), _( jsobj.get_string( "description" ) ), jsobj.get_tags( "tags" ) ); if( sk.is_contextual_skill() ) { contextual_skills[sk.ident()] = sk; } else { skills.push_back( sk ); } }
void gun_actor::load( JsonObject &obj ) { // Mandatory gun_type = obj.get_string( "gun_type" ); ammo_type = obj.get_string( "ammo_type" ); JsonArray jarr = obj.get_array( "fake_skills" ); while( jarr.has_more() ) { JsonArray cur = jarr.next_array(); fake_skills[skill_id( cur.get_string( 0 ) )] = cur.get_int( 1 ); } range = obj.get_float( "range" ); description = obj.get_string( "description" ); move_cost = obj.get_int( "move_cost" ); targeting_cost = obj.get_int( "targeting_cost" ); // Optional: max_ammo = obj.get_int( "max_ammo", INT_MAX ); fake_str = obj.get_int( "fake_str", 8 ); fake_dex = obj.get_int( "fake_dex", 8 ); fake_int = obj.get_int( "fake_int", 8 ); fake_per = obj.get_int( "fake_per", 8 ); require_targeting_player = obj.get_bool( "require_targeting_player", true ); require_targeting_npc = obj.get_bool( "require_targeting_npc", false ); require_targeting_monster = obj.get_bool( "require_targeting_monster", false ); targeting_timeout = obj.get_int( "targeting_timeout", 8 ); targeting_timeout_extend = obj.get_int( "targeting_timeout_extend", 3 ); burst_limit = obj.get_int( "burst_limit", INT_MAX ); laser_lock = obj.get_bool( "laser_lock", false ); range_no_burst = obj.get_float( "range_no_burst", range + 1 ); if( obj.has_member( "targeting_sound" ) || obj.has_member( "targeting_volume" ) ) { // Both or neither, but not just one targeting_sound = obj.get_string( "targeting_sound" ); targeting_volume = obj.get_int( "targeting_volume" ); } // Sound of no ammo no_ammo_sound = obj.get_string( "no_ammo_sound", "" ); }
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("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("bio_armor_arms") ) { return true; } } // if not above, can't block. return false; }
static void parse_vp_reqs( JsonObject &obj, const std::string &id, const std::string &key, std::vector<std::pair<requirement_id, int>> &reqs, std::map<skill_id, int> &skills, int &moves ) { if( !obj.has_object( key ) ) { return; } auto src = obj.get_object( key ); auto sk = src.get_array( "skills" ); if( !sk.empty() ) { skills.clear(); } while( sk.has_more() ) { auto cur = sk.next_array(); skills.emplace( skill_id( cur.get_string( 0 ) ), cur.size() >= 2 ? cur.get_int( 1 ) : 1 ); } assign( src, "time", moves ); if( src.has_string( "using" ) ) { reqs = { { requirement_id( src.get_string( "using" ) ), 1 } }; } else if( src.has_array( "using" ) ) { auto arr = src.get_array( "using" ); while( arr.has_more() ) { auto cur = arr.next_array(); reqs.emplace_back( requirement_id( cur.get_string( 0 ) ), cur.get_int( 1 ) ); } } else { const requirement_id req_id( string_format( "inline_%s_%s", key.c_str(), id.c_str() ) ); requirement_data::load_requirement( src, req_id ); reqs = { { req_id, 1 } }; } }
void fault::load_fault( JsonObject &jo ) { fault f; f.id_ = fault_id( jo.get_string( "id" ) ); f.name_ = _( jo.get_string( "name" ).c_str() ); f.description_ = _( jo.get_string( "description" ).c_str() ); auto sk = jo.get_array( "skills" ); while( sk.has_more() ) { auto cur = sk.next_array(); f.skills_.emplace( skill_id( cur.get_string( 0 ) ) , cur.size() >= 2 ? cur.get_int( 1 ) : 1 ); } auto req = jo.get_object( "requirements" ); f.requirements_.load( req ); if( faults_all.find( f.id_ ) != faults_all.end() ) { jo.throw_error( "parsed fault overwrites existing definition", "id" ); } else { faults_all[ f.id_ ] = f; DebugLog( D_INFO, DC_ALL ) << "Loaded fault: " << f.name_; } }
/** * Reads in a vehicle part from a JsonObject. */ void vpart_info::load( JsonObject &jo, const std::string &src ) { vpart_info def; if( jo.has_string( "copy-from" ) ) { auto const base = vehicle_part_types.find( vpart_str_id( jo.get_string( "copy-from" ) ) ); auto const ab = abstract_parts.find( vpart_str_id( jo.get_string( "copy-from" ) ) ); if( base != vehicle_part_types.end() ) { def = base->second; } else if( ab != abstract_parts.end() ) { def = ab->second; } else { deferred.emplace_back( jo.str(), src ); } } if( jo.has_string( "abstract" ) ) { def.id = vpart_str_id( jo.get_string( "abstract" ) ); } else { def.id = vpart_str_id( jo.get_string( "id" ) ); } assign( jo, "name", def.name_ ); assign( jo, "item", def.item ); assign( jo, "location", def.location ); assign( jo, "durability", def.durability ); assign( jo, "damage_modifier", def.dmg_mod ); assign( jo, "power", def.power ); assign( jo, "epower", def.epower ); assign( jo, "fuel_type", def.fuel_type ); assign( jo, "folded_volume", def.folded_volume ); assign( jo, "size", def.size ); assign( jo, "difficulty", def.difficulty ); assign( jo, "bonus", def.bonus ); assign( jo, "flags", def.flags ); auto reqs = jo.get_object( "requirements" ); if( reqs.has_object( "install" ) ) { auto ins = reqs.get_object( "install" ); auto sk = ins.get_array( "skills" ); if( !sk.empty() ) { def.install_skills.clear(); } while( sk.has_more() ) { auto cur = sk.next_array(); def.install_skills.emplace( skill_id( cur.get_string( 0 ) ) , cur.size() >= 2 ? cur.get_int( 1 ) : 1 ); } assign( ins, "time", def.install_moves ); if( ins.has_string( "using" ) ) { def.install_reqs = { { requirement_id( ins.get_string( "using" ) ), 1 } }; } else if( ins.has_array( "using" ) ) { auto arr = ins.get_array( "using" ); while( arr.has_more() ) { auto cur = arr.next_array(); def.install_reqs.emplace_back( requirement_id( cur.get_string( 0 ) ), cur.get_int( 1 ) ); } } else { auto req_id = std::string( "inline_vehins_" ) += def.id.str(); requirement_data::load_requirement( ins, req_id ); def.install_reqs = { { requirement_id( req_id ), 1 } }; } def.legacy = false; } if( reqs.has_object( "removal" ) ) { auto rem = reqs.get_object( "removal" ); auto sk = rem.get_array( "skills" ); if( !sk.empty() ) { def.removal_skills.clear(); } while( sk.has_more() ) { auto cur = sk.next_array(); def.removal_skills.emplace( skill_id( cur.get_string( 0 ) ) , cur.size() >= 2 ? cur.get_int( 1 ) : 1 ); } assign( rem, "time", def.removal_moves ); if( rem.has_string( "using" ) ) { def.removal_reqs = { { requirement_id( rem.get_string( "using" ) ), 1 } }; } else if( rem.has_array( "using" ) ) { auto arr = rem.get_array( "using" ); while( arr.has_more() ) { auto cur = arr.next_array(); def.removal_reqs.emplace_back( requirement_id( cur.get_string( 0 ) ), cur.get_int( 1 ) ); } } else { auto req_id = std::string( "inline_vehins_" ) += def.id.str(); requirement_data::load_requirement( rem, req_id ); def.removal_reqs = { { requirement_id( req_id ), 1 } }; } def.legacy = false; } if( jo.has_member( "symbol" ) ) { def.sym = jo.get_string( "symbol" )[ 0 ]; } if( jo.has_member( "broken_symbol" ) ) { def.sym_broken = jo.get_string( "broken_symbol" )[ 0 ]; } if( jo.has_member( "color" ) ) { def.color = color_from_string( jo.get_string( "color" ) ); } if( jo.has_member( "broken_color" ) ) { def.color_broken = color_from_string( jo.get_string( "broken_color" ) ); } if( jo.has_member( "breaks_into" ) ) { JsonIn& stream = *jo.get_raw( "breaks_into" ); def.breaks_into_group = item_group::load_item_group( stream, "collection" ); } auto qual = jo.get_array( "qualities" ); if( !qual.empty() ) { def.qualities.clear(); while( qual.has_more() ) { auto pair = qual.next_array(); def.qualities[ quality_id( pair.get_string( 0 ) ) ] = pair.get_int( 1 ); } } if( jo.has_member( "damage_reduction" ) ) { JsonObject dred = jo.get_object( "damage_reduction" ); def.damage_reduction = load_damage_array( dred ); } else { def.damage_reduction.fill( 0.0f ); } if( jo.has_string( "abstract" ) ) { abstract_parts[ def.id ] = def; return; } auto const iter = vehicle_part_types.find( def.id ); if( iter != vehicle_part_types.end() ) { // Entry in the map already exists, so the pointer in the vector is already correct // and does not need to be changed, only the int-id needs to be taken from the old entry. def.loadid = iter->second.loadid; iter->second = def; } else { // The entry is new, "generate" a new int-id and link the new entry from the vector. def.loadid = vpart_id( vehicle_part_int_types.size() ); vpart_info &new_entry = vehicle_part_types[ def.id ]; new_entry = def; vehicle_part_int_types.push_back( &new_entry ); } }
int vehicle::automatic_fire_turret( vehicle_part &pt ) { auto gun = turret_query( pt ); if( gun.query() != turret_data::status::ready ) { return 0; } tripoint pos = global_part_pos3( pt ); npc tmp; tmp.set_fake( true ); tmp.name = rmp_format( _( "<veh_player>The %s" ), pt.name().c_str() ); tmp.set_skill_level( gun.base()->gun_skill(), 8 ); tmp.set_skill_level( skill_id( "gun" ), 4 ); tmp.recoil = abs( velocity ) / 100 / 4; tmp.setpos( pos ); tmp.str_cur = 16; tmp.dex_cur = 8; tmp.per_cur = 12; // Assume vehicle turrets are friendly to the player. tmp.attitude = NPCATT_FOLLOW; int area = aoe_size( gun.ammo_effects() ); if( area > 0 ) { area += area == 1 ? 1 : 2; // Pad a bit for less friendly fire } tripoint targ = pos; auto &target = pt.target; if( target.first == target.second ) { // Manual target not set, find one automatically const bool u_see = g->u.sees( pos ); int boo_hoo; // @todo calculate chance to hit and cap range based upon this int range = std::min( gun.range(), 12 ); Creature *auto_target = tmp.auto_find_hostile_target( range, boo_hoo, area ); if( auto_target == nullptr ) { if( u_see && boo_hoo ) { add_msg( m_warning, ngettext( "%s points in your direction and emits an IFF warning beep.", "%s points in your direction and emits %d annoyed sounding beeps.", boo_hoo ), tmp.name.c_str(), boo_hoo ); } return 0; } targ = auto_target->pos(); } else if( target.first != target.second ) { // Target set manually // Make sure we didn't move between aiming and firing (it's a bug if we did) if( targ != target.first ) { target.second = target.first; return 0; } targ = target.second; // Remove the target target.second = target.first; } else { // Shouldn't happen target.first = target.second; return 0; } auto shots = gun.fire( tmp, targ ); if( g->u.sees( pos ) && shots ) { add_msg( _( "The %1$s fires its %2$s!" ), name.c_str(), pt.name().c_str() ); } return shots; }
void recipe::load( JsonObject &jo, const std::string &src ) { bool strict = src == "dda"; abstract = jo.has_string( "abstract" ); if( abstract ) { ident_ = recipe_id( jo.get_string( "abstract" ) ); } else { result_ = jo.get_string( "result" ); ident_ = recipe_id( result_ ); } assign( jo, "time", time, strict, 0 ); assign( jo, "difficulty", difficulty, strict, 0, MAX_SKILL ); assign( jo, "flags", flags ); // automatically set contained if we specify as container assign( jo, "contained", contained, strict ); contained |= assign( jo, "container", container, strict ); if( jo.has_array( "batch_time_factors" ) ) { auto batch = jo.get_array( "batch_time_factors" ); batch_rscale = batch.get_int( 0 ) / 100.0; batch_rsize = batch.get_int( 1 ); } assign( jo, "charges", charges ); assign( jo, "result_mult", result_mult ); assign( jo, "skill_used", skill_used, strict ); if( jo.has_member( "skills_required" ) ) { auto sk = jo.get_array( "skills_required" ); required_skills.clear(); if( sk.empty() ) { // clear all requirements } else if( sk.has_array( 0 ) ) { // multiple requirements while( sk.has_more() ) { auto arr = sk.next_array(); required_skills[skill_id( arr.get_string( 0 ) )] = arr.get_int( 1 ); } } else { // single requirement required_skills[skill_id( sk.get_string( 0 ) )] = sk.get_int( 1 ); } } // simplified autolearn sets requirements equal to required skills at finalization if( jo.has_bool( "autolearn" ) ) { assign( jo, "autolearn", autolearn ); } else if( jo.has_array( "autolearn" ) ) { autolearn = true; auto sk = jo.get_array( "autolearn" ); while( sk.has_more() ) { auto arr = sk.next_array(); autolearn_requirements[skill_id( arr.get_string( 0 ) )] = arr.get_int( 1 ); } } if( jo.has_member( "decomp_learn" ) ) { learn_by_disassembly.clear(); if( jo.has_int( "decomp_learn" ) ) { if( !skill_used ) { jo.throw_error( "decomp_learn specified with no skill_used" ); } assign( jo, "decomp_learn", learn_by_disassembly[skill_used] ); } else if( jo.has_array( "decomp_learn" ) ) { auto sk = jo.get_array( "decomp_learn" ); while( sk.has_more() ) { auto arr = sk.next_array(); learn_by_disassembly[skill_id( arr.get_string( 0 ) )] = arr.get_int( 1 ); } } } if( jo.has_member( "book_learn" ) ) { auto bk = jo.get_array( "book_learn" ); booksets.clear(); while( bk.has_more() ) { auto arr = bk.next_array(); booksets.emplace( arr.get_string( 0 ), arr.size() > 1 ? arr.get_int( 1 ) : -1 ); } } // recipes not specifying any external requirements inherit from their parent recipe (if any) if( jo.has_string( "using" ) ) { reqs_external = { { requirement_id( jo.get_string( "using" ) ), 1 } }; } else if( jo.has_array( "using" ) ) { auto arr = jo.get_array( "using" ); reqs_external.clear(); while( arr.has_more() ) { auto cur = arr.next_array(); reqs_external.emplace_back( requirement_id( cur.get_string( 0 ) ), cur.get_int( 1 ) ); } } const std::string type = jo.get_string( "type" ); if( type == "recipe" ) { if( jo.has_string( "id_suffix" ) ) { if( abstract ) { jo.throw_error( "abstract recipe cannot specify id_suffix", "id_suffix" ); } ident_ = recipe_id( ident_.str() + "_" + jo.get_string( "id_suffix" ) ); } assign( jo, "category", category, strict ); assign( jo, "subcategory", subcategory, strict ); assign( jo, "reversible", reversible, strict ); if( jo.has_member( "byproducts" ) ) { auto bp = jo.get_array( "byproducts" ); byproducts.clear(); while( bp.has_more() ) { auto arr = bp.next_array(); byproducts[ arr.get_string( 0 ) ] += arr.size() == 2 ? arr.get_int( 1 ) : 1; } } } else if( type == "uncraft" ) { reversible = true; } else { jo.throw_error( "unknown recipe type", "type" ); } // inline requirements are always replaced (cannot be inherited) const auto req_id = string_format( "inline_%s_%s", type.c_str(), ident_.c_str() ); requirement_data::load_requirement( jo, req_id ); reqs_internal = { { requirement_id( req_id ), 1 } }; }
void npc_class::load( JsonObject &jo, const std::string & ) { mandatory( jo, was_loaded, "name", name, translated_string_reader ); mandatory( jo, was_loaded, "job_description", job_description, translated_string_reader ); optional( jo, was_loaded, "common", common, true ); bonus_str = load_distribution( jo, "bonus_str" ); bonus_dex = load_distribution( jo, "bonus_dex" ); bonus_int = load_distribution( jo, "bonus_int" ); bonus_per = load_distribution( jo, "bonus_per" ); optional( jo, was_loaded, "shopkeeper_item_group", shopkeeper_item_group, "EMPTY_GROUP" ); optional( jo, was_loaded, "worn_override", worn_override ); optional( jo, was_loaded, "carry_override", carry_override ); optional( jo, was_loaded, "weapon_override", weapon_override ); if( jo.has_array( "traits" ) ) { traits = trait_group::load_trait_group( *jo.get_raw( "traits" ), "collection" ); } /* Mutation rounds can be specified as follows: * "mutation_rounds": { * "ANY" : { "constant": 1 }, * "INSECT" : { "rng": [1, 3] } * } */ if( jo.has_object( "mutation_rounds" ) ) { const std::map<std::string, mutation_category_trait> &mutation_categories = mutation_category_trait::get_all(); auto jo2 = jo.get_object( "mutation_rounds" ); for( auto &mutation : jo2.get_member_names() ) { auto category_match = [&mutation]( std::pair<const std::string, mutation_category_trait> p ) { return p.second.id == mutation; }; if( std::find_if( mutation_categories.begin(), mutation_categories.end(), category_match ) == mutation_categories.end() ) { debugmsg( "Unrecognized mutation category %s", mutation ); continue; } auto distrib = jo2.get_object( mutation ); mutation_rounds[mutation] = load_distribution( distrib ); } } if( jo.has_array( "skills" ) ) { JsonArray jarr = jo.get_array( "skills" ); while( jarr.has_more() ) { JsonObject skill_obj = jarr.next_object(); auto skill_ids = skill_obj.get_tags( "skill" ); if( skill_obj.has_object( "level" ) ) { distribution dis = load_distribution( skill_obj, "level" ); for( const auto &sid : skill_ids ) { skills[ skill_id( sid ) ] = dis; } } else { distribution dis = load_distribution( skill_obj, "bonus" ); for( const auto &sid : skill_ids ) { bonus_skills[ skill_id( sid ) ] = dis; } } } } }
void check_test_overmap_data( const overmap &test_map ) { // Spot-check a bunch of terrain values. // Bottom level, "L 0" in the save REQUIRE(test_map.get_ter(0, 0, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(47, 3, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(48, 3, -10).id() == "rock"); REQUIRE(test_map.get_ter(49, 3, -10).id() == "rock"); REQUIRE(test_map.get_ter(50, 3, -10).id() == "rock"); REQUIRE(test_map.get_ter(51, 3, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(45, 4, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(46, 4, -10).id() == "rock"); REQUIRE(test_map.get_ter(47, 4, -10).id() == "rock"); REQUIRE(test_map.get_ter(48, 4, -10).id() == "slimepit"); REQUIRE(test_map.get_ter(49, 4, -10).id() == "slimepit"); REQUIRE(test_map.get_ter(50, 4, -10).id() == "slimepit"); REQUIRE(test_map.get_ter(51, 4, -10).id() == "rock"); REQUIRE(test_map.get_ter(52, 4, -10).id() == "empty_rock"); REQUIRE(test_map.get_ter(179, 179, -10).id() == "empty_rock"); // Level -9, "L 1" in the save REQUIRE(test_map.get_ter(0, 0, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(44, 1, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(45, 1, -9).id() == "rock"); REQUIRE(test_map.get_ter(46, 1, -9).id() == "rock"); REQUIRE(test_map.get_ter(47, 1, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(48, 1, -9).id() == "rock"); REQUIRE(test_map.get_ter(49, 1, -9).id() == "rock"); REQUIRE(test_map.get_ter(50, 1, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(43, 2, -9).id() == "empty_rock"); REQUIRE(test_map.get_ter(44, 2, -9).id() == "rock"); REQUIRE(test_map.get_ter(45, 2, -9).id() == "slimepit"); REQUIRE(test_map.get_ter(46, 2, -9).id() == "slimepit"); REQUIRE(test_map.get_ter(47, 2, -9).id() == "rock"); REQUIRE(test_map.get_ter(48, 2, -9).id() == "slimepit"); REQUIRE(test_map.get_ter(49, 2, -9).id() == "slimepit"); REQUIRE(test_map.get_ter(50, 2, -9).id() == "rock"); REQUIRE(test_map.get_ter(51, 2, -9).id() == "empty_rock"); // Level -3, "L 7" in save REQUIRE(test_map.get_ter(0, 0, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(156, 0, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(157, 0, -3).id() == "temple_stairs"); REQUIRE(test_map.get_ter(158, 0, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(45, 5, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(46, 5, -3).id() == "rock"); REQUIRE(test_map.get_ter(47, 5, -3).id() == "rock"); REQUIRE(test_map.get_ter(48, 5, -3).id() == "rock"); REQUIRE(test_map.get_ter(49, 5, -3).id() == "rock"); REQUIRE(test_map.get_ter(50, 5, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(133, 5, -3).id() == "empty_rock"); REQUIRE(test_map.get_ter(134, 5, -3).id() == "mine"); REQUIRE(test_map.get_ter(135, 5, -3).id() == "empty_rock"); // Ground level REQUIRE(test_map.get_ter(0, 0, 0).id() == "field"); REQUIRE(test_map.get_ter(23, 0, 0).id() == "field"); REQUIRE(test_map.get_ter(24, 0, 0).id() == "forest_thick"); REQUIRE(test_map.get_ter(25, 0, 0).id() == "forest_thick"); REQUIRE(test_map.get_ter(26, 0, 0).id() == "forest_thick"); REQUIRE(test_map.get_ter(27, 0, 0).id() == "forest"); REQUIRE(test_map.get_ter(28, 0, 0).id() == "forest"); REQUIRE(test_map.get_ter(29, 0, 0).id() == "forest"); REQUIRE(test_map.get_ter(30, 0, 0).id() == "forest"); // Sky REQUIRE(test_map.get_ter(0, 0, 1).id() == "open_air"); REQUIRE(test_map.get_ter(179, 179, 1).id() == "open_air"); REQUIRE(test_map.get_ter(0, 0, 2).id() == "open_air"); REQUIRE(test_map.get_ter(179, 179, 2).id() == "open_air"); REQUIRE(test_map.get_ter(0, 0, 10).id() == "open_air"); REQUIRE(test_map.get_ter(179, 179, 10).id() == "open_air"); // Spot-check a few of the monster groups. std::vector<mongroup> expected_groups{ {"GROUP_ANT", {0, 0, -1}, 1, 3, {0, 0, 0}, 0, false, false, false}, {"GROUP_TRIFFID", {0, 132, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {0, 189, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_FUNGI", {0, 288, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {1, 0, -1}, 1, 3, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {1, 0, 0}, 1, 2, {0, 0, 0}, 0, false, false, false}, {"GROUP_TRIFFID", {1, 137, 0}, 1, 2, {0, 0, 0}, 0, false, false, false}, {"GROUP_WORM", {2, 67, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_FUNGI_FLOWERS", {2, 150, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_FUNGI_FLOWERS", {5, 150, 0}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {6, 365, -1}, 1, 1, {0, 0, 0}, 0, false, false, false}, {"GROUP_ANT", {1, 8, -2}, 1, 2, {100, 50, 0}, 0, true, false, false}, {"GROUP_GOO", {2, 9, -1}, 1, 4, {25, 75, 0}, 10, false, true, false}, {"GROUP_BEE", {3, 10, 0}, 1, 6, {92, 64, 0}, 20, false, false, true}, {"GROUP_CHUD", {4, 11, -2}, 1, 8, {88, 55, 0}, 30, true, true, false}, {"GROUP_SPIRAL", {5, 12, -1}, 1, 10, {62, 47, 0}, 40, false, true, true}, {"GROUP_RIVER", {6, 13, 0}, 1, 12, {94, 72, 0}, 50, true, false, true}, {"GROUP_SWAMP", {7, 14, -2}, 1, 14, {37, 85, 0}, 60, true, true, true} }; for( auto group : expected_groups ) { REQUIRE(test_map.mongroup_check(group)); } // Only a few cities, so check them all. std::vector<city> expected_cities {{145, 53, 9},{24,60,7},{90,114,2},{108,129,9},{83,26,10}, {140,89,2},{71,33,2},{67,111,2},{97,144,9},{96,166,2}}; REQUIRE(test_map.cities.size() == expected_cities.size()); for( const auto &candidate_city : test_map.cities ) { REQUIRE(std::find(expected_cities.begin(), expected_cities.end(), candidate_city) != expected_cities.end() ); } // Check all the roads too. // Roads are getting size set to 0, but I expect -1. std::vector<city> expected_roads = {{179,126, -1},{136,179, -1}}; REQUIRE(test_map.roads_out.size() == expected_roads.size()); for( const auto &candidate_road : test_map.roads_out ) { REQUIRE(std::find(expected_roads.begin(), expected_roads.end(), candidate_road) != expected_roads.end() ); } // Check the radio towers. std::vector<radio_tower> expected_towers{ {2,42,122,"This is FEMA camp 121. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 121. A designated long-term emergency shelter.",MESSAGE_BROADCAST}, {36,300,193,"This is FEMA camp 18150. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 18150. A designated long-term emergency shelter.",MESSAGE_BROADCAST}, {56,194,92,"This is automated emergency shelter beacon 2897. Supplies, amenities and shelter are stocked.",MESSAGE_BROADCAST}, {62,208,176,"This is FEMA camp 31104. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 31104. A designated long-term emergency shelter.",MESSAGE_BROADCAST}, {64,42,190,"",WEATHER_RADIO},{92,146,100,"",WEATHER_RADIO}, {126,194,112,"This is FEMA camp 6397. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 6397. A designated long-term emergency shelter.", MESSAGE_BROADCAST}, {142,128,114,"This is FEMA camp 7164. Supplies are limited, please bring supplemental food, water, and bedding. This is FEMA camp 7164. A designated long-term emergency shelter.", MESSAGE_BROADCAST}, {236,168,115,"",WEATHER_RADIO}, {240,352,95,"This is automated emergency shelter beacon 120176. Supplies, amenities and shelter are stocked.", MESSAGE_BROADCAST}, {244,162,150,"This is emergency broadcast station 12281. Please proceed quickly and calmly to your designated evacuation point.", MESSAGE_BROADCAST}, {282,48,190,"This is emergency broadcast station 14124. Please proceed quickly and calmly to your designated evacuation point.", MESSAGE_BROADCAST}, {306,66,90,"This is emergency broadcast station 15333. Please proceed quickly and calmly to your designated evacuation point.", MESSAGE_BROADCAST}}; REQUIRE(test_map.radios.size() == expected_towers.size()); for( const auto &candidate_tower : test_map.radios ) { REQUIRE(std::find(expected_towers.begin(), expected_towers.end(), candidate_tower) != expected_towers.end() ); } // Spot-check some monsters. std::vector<std::pair<tripoint, monster>> expected_monsters{ {{251, 86, 0},{ mtype_id("mon_zombie"), {140, 23, 0}}}, {{253, 87, 0},{ mtype_id("mon_zombie"), {136, 25, 0}}}, {{259, 95, 0},{ mtype_id("mon_zombie"), {143, 122, 0}}}, {{259, 94, 0},{ mtype_id("mon_zombie"), {139, 109, 0}}}, {{259, 91, 0},{ mtype_id("mon_dog"), {139, 82, 0}}}, {{194, 87, -3},{ mtype_id("mon_irradiated_wanderer_4"), {119, 73, -3}}}, {{194, 87, -3},{ mtype_id("mon_charred_nightmare"), {117, 83, -3}}}, {{142, 96, 0},{ mtype_id("mon_deer"), {16, 109, 0}}}, {{196, 66, -1},{ mtype_id("mon_turret"), {17, 65, -1}}}, {{196, 63, -1},{ mtype_id("mon_broken_cyborg"), {19, 26, -1}}} }; for( auto candidate_monster : expected_monsters ) { REQUIRE(test_map.monster_check(candidate_monster)); } // Check NPCs. They're complicated enough that I'm just going to spot-check some stats. for( const npc *test_npc : test_map.npcs ) { if( test_npc->disp_name() == "Felix Brandon" ) { REQUIRE(test_npc->get_str() == 7); REQUIRE(test_npc->get_dex() == 8); REQUIRE(test_npc->get_int() == 7); REQUIRE(test_npc->get_per() == 10); REQUIRE(test_npc->get_skill_level(skill_id("barter")) == 4); REQUIRE(test_npc->get_skill_level(skill_id("driving")) == 2); REQUIRE(test_npc->get_skill_level(skill_id("firstaid")) == 7); REQUIRE(test_npc->get_skill_level(skill_id("mechanics")) == 5); REQUIRE(test_npc->get_skill_level(skill_id("dodge")) == 3); REQUIRE(test_npc->get_skill_level(skill_id("launcher")) == 3); REQUIRE(test_npc->pos() == tripoint(168, 66, 0)); } else if( test_npc->disp_name() == "Mariann Araujo" ) { REQUIRE(test_npc->get_str() == 11); REQUIRE(test_npc->get_dex() == 9); REQUIRE(test_npc->get_int() == 10); REQUIRE(test_npc->get_per() == 10); REQUIRE(test_npc->get_skill_level(skill_id("barter")) == 4); REQUIRE(test_npc->get_skill_level(skill_id("driving")) == 0); REQUIRE(test_npc->get_skill_level(skill_id("firstaid")) == 5); REQUIRE(test_npc->get_skill_level(skill_id("bashing")) == 5); REQUIRE(test_npc->get_skill_level(skill_id("dodge")) == 4); REQUIRE(test_npc->pos() == tripoint(72, 54, 0)); } else { // Unrecognized NPC, fail. REQUIRE(false); } } }
bool game::dump_stats( const std::string &what, dump_mode mode, const std::vector<std::string> &opts ) { try { loading_ui ui( false ); load_core_data( ui ); load_packs( _( "Loading content packs" ), { mod_id( "dda" ) }, ui ); DynamicDataLoader::get_instance().finalize_loaded_data( ui ); } catch( const std::exception &err ) { std::cerr << "Error loading data from json: " << err.what() << std::endl; return false; } std::vector<std::string> header; std::vector<std::vector<std::string>> rows; int scol = 0; // sorting column std::map<std::string, standard_npc> test_npcs; test_npcs[ "S1" ] = standard_npc( "S1", { "gloves_survivor", "mask_lsurvivor" }, 4, 8, 10, 8, 10 /* DEX 10, PER 10 */ ); test_npcs[ "S2" ] = standard_npc( "S2", { "gloves_fingerless", "sunglasses" }, 4, 8, 8, 8, 10 /* PER 10 */ ); test_npcs[ "S3" ] = standard_npc( "S3", { "gloves_plate", "helmet_plate" }, 4, 10, 8, 8, 8 /* STAT 10 */ ); test_npcs[ "S4" ] = standard_npc( "S4", {}, 0, 8, 10, 8, 10 /* DEX 10, PER 10 */ ); test_npcs[ "S5" ] = standard_npc( "S5", {}, 4, 8, 10, 8, 10 /* DEX 10, PER 10 */ ); test_npcs[ "S6" ] = standard_npc( "S6", { "gloves_hsurvivor", "mask_hsurvivor" }, 4, 8, 10, 8, 10 /* DEX 10, PER 10 */ ); std::map<std::string, item> test_items; test_items[ "G1" ] = item( "glock_19" ).ammo_set( "9mm" ); test_items[ "G2" ] = item( "hk_mp5" ).ammo_set( "9mm" ); test_items[ "G3" ] = item( "ar15" ).ammo_set( "223" ); test_items[ "G4" ] = item( "remington_700" ).ammo_set( "270" ); test_items[ "G4" ].emplace_back( "rifle_scope" ); if( what == "AMMO" ) { header = { "Name", "Ammo", "Volume", "Weight", "Stack", "Range", "Dispersion", "Recoil", "Damage", "Pierce" }; auto dump = [&rows]( const item & obj ) { // a common task is comparing ammo by type so ammo has multiple repeat the entry for( const auto &e : obj.type->ammo->type ) { std::vector<std::string> r; r.push_back( obj.tname( 1, false ) ); r.push_back( e.str() ); r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.type->stack_size ) ); r.push_back( to_string( obj.type->ammo->range ) ); r.push_back( to_string( obj.type->ammo->dispersion ) ); r.push_back( to_string( obj.type->ammo->recoil ) ); damage_instance damage = obj.type->ammo->damage; r.push_back( to_string( damage.total_damage() ) ); r.push_back( to_string( damage.empty() ? 0 : ( *damage.begin() ).res_pen ) ); rows.push_back( r ); } }; for( const itype *e : item_controller->all() ) { if( e->ammo ) { dump( item( e, calendar::turn, item::solitary_tag {} ) ); } } } else if( what == "ARMOR" ) { header = { "Name", "Encumber (fit)", "Warmth", "Weight", "Storage", "Coverage", "Bash", "Cut", "Acid", "Fire" }; auto dump = [&rows]( const item & obj ) { std::vector<std::string> r; r.push_back( obj.tname( 1, false ) ); r.push_back( to_string( obj.get_encumber() ) ); r.push_back( to_string( obj.get_warmth() ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.get_storage() / units::legacy_volume_factor ) ); r.push_back( to_string( obj.get_coverage() ) ); r.push_back( to_string( obj.bash_resist() ) ); r.push_back( to_string( obj.cut_resist() ) ); r.push_back( to_string( obj.acid_resist() ) ); r.push_back( to_string( obj.fire_resist() ) ); rows.push_back( r ); }; body_part bp = opts.empty() ? num_bp : get_body_part_token( opts.front() ); for( const itype *e : item_controller->all() ) { if( e->armor ) { item obj( e ); if( bp == num_bp || obj.covers( bp ) ) { if( obj.has_flag( "VARSIZE" ) ) { obj.item_tags.insert( "FIT" ); } dump( obj ); } } } } else if( what == "EDIBLE" ) { header = { "Name", "Volume", "Weight", "Stack", "Calories", "Quench", "Healthy" }; for( const auto &v : vitamin::all() ) { header.push_back( v.second.name() ); } auto dump = [&rows]( const item & obj ) { std::vector<std::string> r; r.push_back( obj.tname( false ) ); r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.type->stack_size ) ); r.push_back( to_string( obj.type->comestible->get_calories() ) ); r.push_back( to_string( obj.type->comestible->quench ) ); r.push_back( to_string( obj.type->comestible->healthy ) ); auto vits = g->u.vitamins_from( obj ); for( const auto &v : vitamin::all() ) { r.push_back( to_string( vits[ v.first ] ) ); } rows.push_back( r ); }; for( const itype *e : item_controller->all() ) { item food( e, calendar::turn, item::solitary_tag {} ); if( food.is_food() && g->u.can_eat( food ).success() ) { dump( food ); } } } else if( what == "GUN" ) { header = { "Name", "Ammo", "Volume", "Weight", "Capacity", "Range", "Dispersion", "Effective recoil", "Damage", "Pierce", "Aim time", "Effective range", "Snapshot range", "Max range" }; std::set<std::string> locations; for( const itype *e : item_controller->all() ) { if( e->gun ) { std::transform( e->gun->valid_mod_locations.begin(), e->gun->valid_mod_locations.end(), std::inserter( locations, locations.begin() ), []( const std::pair<gunmod_location, int> &q ) { return q.first.name(); } ); } } for( const auto &e : locations ) { header.push_back( e ); } auto dump = [&rows, &locations]( const standard_npc & who, const item & obj ) { std::vector<std::string> r; r.push_back( obj.tname( 1, false ) ); r.push_back( obj.ammo_type() ? obj.ammo_type().str() : "" ); r.push_back( to_string( obj.volume() / units::legacy_volume_factor ) ); r.push_back( to_string( to_gram( obj.weight() ) ) ); r.push_back( to_string( obj.ammo_capacity() ) ); r.push_back( to_string( obj.gun_range() ) ); r.push_back( to_string( obj.gun_dispersion() ) ); r.push_back( to_string( obj.gun_recoil( who ) ) ); damage_instance damage = obj.gun_damage(); r.push_back( to_string( damage.total_damage() ) ); r.push_back( to_string( damage.empty() ? 0 : ( *damage.begin() ).res_pen ) ); r.push_back( to_string( who.gun_engagement_moves( obj ) ) ); for( const auto &e : locations ) { const auto &vml = obj.type->gun->valid_mod_locations; const auto iter = vml.find( e ); r.push_back( to_string( iter != vml.end() ? iter->second : 0 ) ); } rows.push_back( r ); }; for( const itype *e : item_controller->all() ) { if( e->gun ) { item gun( e ); if( !gun.magazine_integral() ) { gun.emplace_back( gun.magazine_default() ); } gun.ammo_set( gun.ammo_type()->default_ammotype(), gun.ammo_capacity() ); dump( test_npcs[ "S1" ], gun ); if( gun.type->gun->barrel_length > 0 ) { gun.emplace_back( "barrel_small" ); dump( test_npcs[ "S1" ], gun ); } } } } else if( what == "RECIPE" ) { // optionally filter recipes to include only those using specified skills recipe_subset dict; for( const auto &r : recipe_dict ) { if( opts.empty() || std::any_of( opts.begin(), opts.end(), [&r]( const std::string & s ) { if( r.second.skill_used == skill_id( s ) && r.second.difficulty > 0 ) { return true; } auto iter = r.second.required_skills.find( skill_id( s ) ); return iter != r.second.required_skills.end() && iter->second > 0; } ) ) { dict.include( &r.second ); } } // only consider skills that are required by at least one recipe std::vector<Skill> sk; std::copy_if( Skill::skills.begin(), Skill::skills.end(), std::back_inserter( sk ), [&dict]( const Skill & s ) { return std::any_of( dict.begin(), dict.end(), [&s]( const recipe * r ) { return r->skill_used == s.ident() || r->required_skills.find( s.ident() ) != r->required_skills.end(); } ); } ); header = { "Result" }; for( const auto &e : sk ) { header.push_back( e.ident().str() ); } for( const recipe *e : dict ) { std::vector<std::string> r; r.push_back( e->result_name() ); for( const auto &s : sk ) { if( e->skill_used == s.ident() ) { r.push_back( to_string( e->difficulty ) ); } else { auto iter = e->required_skills.find( s.ident() ); r.push_back( to_string( iter != e->required_skills.end() ? iter->second : 0 ) ); } } rows.push_back( r ); } } else if( what == "VEHICLE" ) { header = { "Name", "Weight (empty)", "Weight (fueled)", "Max velocity (mph)", "Safe velocity (mph)", "Acceleration (mph/turn)", "Mass coeff %", "Aerodynamics coeff %", "Friction coeff %", "Traction coeff % (grass)" }; auto dump = [&rows]( const vproto_id & obj ) { auto veh_empty = vehicle( obj, 0, 0 ); auto veh_fueled = vehicle( obj, 100, 0 ); std::vector<std::string> r; r.push_back( veh_empty.name ); r.push_back( to_string( to_kilogram( veh_empty.total_mass() ) ) ); r.push_back( to_string( to_kilogram( veh_fueled.total_mass() ) ) ); r.push_back( to_string( veh_fueled.max_velocity() / 100 ) ); r.push_back( to_string( veh_fueled.safe_velocity() / 100 ) ); r.push_back( to_string( veh_fueled.acceleration() / 100 ) ); r.push_back( to_string( ( int )( 100 * veh_fueled.k_mass() ) ) ); r.push_back( to_string( ( int )( 100 * veh_fueled.k_aerodynamics() ) ) ); r.push_back( to_string( ( int )( 100 * veh_fueled.k_friction() ) ) ); r.push_back( to_string( ( int )( 100 * veh_fueled.k_traction( veh_fueled.wheel_area( false ) / 2.0f ) ) ) ); rows.push_back( r ); }; for( auto &e : vehicle_prototype::get_all() ) { dump( e ); } } else if( what == "VPART" ) { header = { "Name", "Location", "Weight", "Size" }; auto dump = [&rows]( const vpart_info & obj ) { std::vector<std::string> r; r.push_back( obj.name() ); r.push_back( obj.location ); r.push_back( to_string( int( ceil( to_gram( item( obj.item ).weight() ) / 1000.0 ) ) ) ); r.push_back( to_string( obj.size / units::legacy_volume_factor ) ); rows.push_back( r ); }; for( const auto &e : vpart_info::all() ) { dump( e.second ); } } else { std::cerr << "unknown argument: " << what << std::endl; return false; } rows.erase( std::remove_if( rows.begin(), rows.end(), []( const std::vector<std::string> &e ) { return e.empty(); } ), rows.end() ); if( scol >= 0 ) { std::sort( rows.begin(), rows.end(), [&scol]( const std::vector<std::string> &lhs, const std::vector<std::string> &rhs ) { return lhs[ scol ] < rhs[ scol ]; } ); } rows.erase( std::unique( rows.begin(), rows.end() ), rows.end() ); switch( mode ) { case dump_mode::TSV: rows.insert( rows.begin(), header ); for( const auto &r : rows ) { std::copy( r.begin(), r.end() - 1, std::ostream_iterator<std::string>( std::cout, "\t" ) ); std::cout << r.back() << "\n"; } break; case dump_mode::HTML: std::cout << "<table>"; std::cout << "<thead>"; std::cout << "<tr>"; for( const auto &col : header ) { std::cout << "<th>" << col << "</th>"; } std::cout << "</tr>"; std::cout << "</thead>"; std::cout << "<tdata>"; for( const auto &r : rows ) { std::cout << "<tr>"; for( const auto &col : r ) { std::cout << "<td>" << col << "</td>"; } std::cout << "</tr>"; } std::cout << "</tdata>"; std::cout << "</table>"; break; } return true; }
void gun_actor::load_internal( JsonObject &obj, const std::string & ) { gun_type = obj.get_string( "gun_type" ); obj.read( "ammo_type", ammo_type ); if( obj.has_array( "fake_skills" ) ) { JsonArray jarr = obj.get_array( "fake_skills" ); while( jarr.has_more() ) { JsonArray cur = jarr.next_array(); fake_skills[skill_id( cur.get_string( 0 ) )] = cur.get_int( 1 ); } } obj.read( "fake_str", fake_str ); obj.read( "fake_dex", fake_dex ); obj.read( "fake_int", fake_int ); obj.read( "fake_per", fake_per ); auto arr = obj.get_array( "ranges" ); while( arr.has_more() ) { auto mode = arr.next_array(); if( mode.size() < 2 || mode.get_int( 0 ) > mode.get_int( 1 ) ) { obj.throw_error( "incomplete or invalid range specified", "ranges" ); } ranges.emplace( std::make_pair<int, int>( mode.get_int( 0 ), mode.get_int( 1 ) ), gun_mode_id( mode.size() > 2 ? mode.get_string( 2 ) : "" ) ); } obj.read( "max_ammo", max_ammo ); obj.read( "move_cost", move_cost ); if( obj.read( "description", description ) ) { description = _( description ); } if( obj.read( "failure_msg", failure_msg ) ) { failure_msg = _( failure_msg ); } if( obj.read( "no_ammo_sound", no_ammo_sound ) ) { no_ammo_sound = _( no_ammo_sound ); } else { no_ammo_sound = _( "Click." ); } obj.read( "targeting_cost", targeting_cost ); obj.read( "require_targeting_player", require_targeting_player ); obj.read( "require_targeting_npc", require_targeting_npc ); obj.read( "require_targeting_monster", require_targeting_monster ); obj.read( "targeting_timeout", targeting_timeout ); obj.read( "targeting_timeout_extend", targeting_timeout_extend ); if( obj.read( "targeting_sound", targeting_sound ) ) { targeting_sound = _( targeting_sound ); } else { targeting_sound = _( "Beep." ); } obj.read( "targeting_volume", targeting_volume ); obj.get_bool( "laser_lock", laser_lock ); obj.read( "require_sunlight", require_sunlight ); }
void erase_next( JsonIn &jin, C &container ) const { const skill_id id = skill_id( jin.get_string() ); reader_detail::handler<C>().erase_if( container, [&id]( const std::pair<skill_id, int> &e ) { return e.first == id; } ); }
void recipe_dictionary::load( JsonObject &jo, const std::string &src, bool uncraft ) { bool strict = src == "core"; recipe r; // defer entries dependent upon as-yet unparsed definitions if( jo.has_string( "copy-from" ) ) { auto base = jo.get_string( "copy-from" ); if( uncraft ) { if( !recipe_dict.uncraft.count( base ) ) { deferred.emplace_back( jo.str(), src ); return; } r = recipe_dict.uncraft[ base ]; } else { if( !recipe_dict.recipes.count( base ) ) { deferred.emplace_back( jo.str(), src ); return; } r = recipe_dict.recipes[ base ]; } } if( jo.has_string( "abstract" ) ) { r.ident_ = jo.get_string( "abstract" ); r.abstract = true; } else { r.ident_ = r.result = jo.get_string( "result" ); r.abstract = false; } if( !uncraft ) { if( jo.has_string( "id_suffix" ) ) { if( r.abstract ) { jo.throw_error( "abstract recipe cannot specify id_suffix", "id_suffix" ); } r.ident_ += "_" + jo.get_string( "id_suffix" ); } assign( jo, "category", r.category, strict ); assign( jo, "subcategory", r.subcategory, strict ); assign( jo, "reversible", r.reversible, strict ); } else { r.reversible = true; } assign( jo, "time", r.time, strict, 0 ); assign( jo, "difficulty", r.difficulty, strict, 0, MAX_SKILL ); assign( jo, "flags", r.flags ); // automatically set contained if we specify as container assign( jo, "contained", r.contained, strict ); r.contained |= assign( jo, "container", r.container, strict ); if( jo.has_array( "batch_time_factors" ) ) { auto batch = jo.get_array( "batch_time_factors" ); r.batch_rscale = batch.get_int( 0 ) / 100.0; r.batch_rsize = batch.get_int( 1 ); } assign( jo, "charges", r.charges ); assign( jo, "result_mult", r.result_mult ); assign( jo, "skill_used", r.skill_used, strict ); if( jo.has_member( "skills_required" ) ) { auto sk = jo.get_array( "skills_required" ); r.required_skills.clear(); if( sk.empty() ) { // clear all requirements } else if( sk.has_array( 0 ) ) { // multiple requirements while( sk.has_more() ) { auto arr = sk.next_array(); r.required_skills[skill_id( arr.get_string( 0 ) )] = arr.get_int( 1 ); } } else { // single requirement r.required_skills[skill_id( sk.get_string( 0 ) )] = sk.get_int( 1 ); } } // simplified autolearn sets requirements equal to required skills at finalization if( jo.has_bool( "autolearn" ) ) { assign( jo, "autolearn", r.autolearn ); } else if( jo.has_array( "autolearn" ) ) { r.autolearn = true; auto sk = jo.get_array( "autolearn" ); while( sk.has_more() ) { auto arr = sk.next_array(); r.autolearn_requirements[skill_id( arr.get_string( 0 ) )] = arr.get_int( 1 ); } } if( jo.has_member( "decomp_learn" ) ) { r.learn_by_disassembly.clear(); if( jo.has_int( "decomp_learn" ) ) { if( !r.skill_used ) { jo.throw_error( "decomp_learn specified with no skill_used" ); } assign( jo, "decomp_learn", r.learn_by_disassembly[r.skill_used] ); } else if( jo.has_array( "decomp_learn" ) ) { auto sk = jo.get_array( "decomp_learn" ); while( sk.has_more() ) { auto arr = sk.next_array(); r.learn_by_disassembly[skill_id( arr.get_string( 0 ) )] = arr.get_int( 1 ); } } } if( !uncraft && jo.has_member( "byproducts" ) ) { auto bp = jo.get_array( "byproducts" ); r.byproducts.clear(); while( bp.has_more() ) { auto arr = bp.next_array(); r.byproducts[ arr.get_string( 0 ) ] += arr.size() == 2 ? arr.get_int( 1 ) : 1; } } if( jo.has_member( "book_learn" ) ) { auto bk = jo.get_array( "book_learn" ); r.booksets.clear(); while( bk.has_more() ) { auto arr = bk.next_array(); r.booksets.emplace( arr.get_string( 0 ), arr.get_int( 1 ) ); } } // recipes not specifying any external requirements inherit from their parent recipe (if any) if( jo.has_string( "using" ) ) { r.reqs_external = { { requirement_id( jo.get_string( "using" ) ), 1 } }; } else if( jo.has_array( "using" ) ) { auto arr = jo.get_array( "using" ); r.reqs_external.clear(); while( arr.has_more() ) { auto cur = arr.next_array(); r.reqs_external.emplace_back( requirement_id( cur.get_string( 0 ) ), cur.get_int( 1 ) ); } } // inline requirements are always replaced (cannot be inherited) auto req_id = string_format( "inline_%s_%s", uncraft ? "uncraft" : "recipe", r.ident_.c_str() ); requirement_data::load_requirement( jo, req_id ); r.reqs_internal = { { requirement_id( req_id ), 1 } }; if( uncraft ) { recipe_dict.uncraft[ r.ident_ ] = r; } else { recipe_dict.recipes[ r.ident_ ] = r; } }
void load_construction(JsonObject &jo) { construction con; con.id = constructions.size(); con.description = _(jo.get_string("description").c_str()); con.skill = skill_id( jo.get_string( "skill", skill_carpentry.str() ) ); con.difficulty = jo.get_int("difficulty"); con.category = jo.get_string("category", "OTHER"); // constructions use different time units in json, this makes it compatible // with recipes/requirements, TODO: should be changed in json con.time = jo.get_int("time") * 1000; if( jo.has_string( "using" ) ) { con.requirements = requirement_id( jo.get_string( "using" ) ); } else { // Warning: the IDs may change! std::string req_id = string_format( "inline_construction_%i", con.id ); requirement_data::load_requirement( jo, req_id ); con.requirements = requirement_id( req_id ); } con.pre_terrain = jo.get_string("pre_terrain", ""); if (con.pre_terrain.size() > 1 && con.pre_terrain[0] == 'f' && con.pre_terrain[1] == '_') { con.pre_is_furniture = true; } else { con.pre_is_furniture = false; } con.post_terrain = jo.get_string("post_terrain", ""); if (con.post_terrain.size() > 1 && con.post_terrain[0] == 'f' && con.post_terrain[1] == '_') { con.post_is_furniture = true; } else { con.post_is_furniture = false; } con.pre_flags = jo.get_tags("pre_flags"); std::string prefunc = jo.get_string("pre_special", ""); if (prefunc == "check_empty") { con.pre_special = &construct::check_empty; } else if (prefunc == "check_support") { con.pre_special = &construct::check_support; } else if (prefunc == "check_deconstruct") { con.pre_special = &construct::check_deconstruct; } else if (prefunc == "check_up_OK") { con.pre_special = &construct::check_up_OK; } else if (prefunc == "check_down_OK") { con.pre_special = &construct::check_down_OK; } else { if (prefunc != "") { debugmsg("Unknown pre_special function: %s", prefunc.c_str()); } con.pre_special = &construct::check_nothing; } std::string postfunc = jo.get_string("post_special", ""); if (postfunc == "done_tree") { con.post_special = &construct::done_tree; } else if (postfunc == "done_trunk_log") { con.post_special = &construct::done_trunk_log; } else if (postfunc == "done_trunk_plank") { con.post_special = &construct::done_trunk_plank; } else if (postfunc == "done_vehicle") { con.post_special = &construct::done_vehicle; } else if (postfunc == "done_deconstruct") { con.post_special = &construct::done_deconstruct; } else if (postfunc == "done_dig_stair") { con.post_special = &construct::done_dig_stair; } else if (postfunc == "done_mine_downstair") { con.post_special = &construct::done_mine_downstair; } else if (postfunc == "done_mine_upstair") { con.post_special = &construct::done_mine_upstair; } else if (postfunc == "done_window_curtains") { con.post_special = &construct::done_window_curtains; } else { if (postfunc != "") { debugmsg("Unknown post_special function: %s", postfunc.c_str()); } con.post_special = &construct::done_nothing; } constructions.push_back(con); }
void player::activate_mutation( const std::string &mut ) { const auto &mdata = mutation_branch::get( mut ); auto &tdata = my_mutations[mut]; int cost = mdata.cost; // You can take yourself halfway to Near Death levels of hunger/thirst. // Fatigue can go to Exhausted. if ((mdata.hunger && get_hunger() >= 700) || (mdata.thirst && get_thirst() >= 260) || (mdata.fatigue && get_fatigue() >= EXHAUSTED)) { // Insufficient Foo to *maintain* operation is handled in player::suffer add_msg_if_player(m_warning, _("You feel like using your %s would kill you!"), mdata.name.c_str()); return; } if (tdata.powered && tdata.charge > 0) { // Already-on units just lose a bit of charge tdata.charge--; } else { // Not-on units, or those with zero charge, have to pay the power cost if (mdata.cooldown > 0) { tdata.charge = mdata.cooldown - 1; } if (mdata.hunger){ mod_hunger(cost); } if (mdata.thirst){ mod_thirst(cost); } if (mdata.fatigue){ mod_fatigue(cost); } tdata.powered = true; // Handle stat changes from activation apply_mods(mut, true); recalc_sight_limits(); } if( mut == "WEB_WEAVER" ) { g->m.add_field(pos(), fd_web, 1, 0); add_msg_if_player(_("You start spinning web with your spinnerets!")); } else if (mut == "BURROW"){ if( is_underwater() ) { add_msg_if_player(m_info, _("You can't do that while underwater.")); tdata.powered = false; return; } tripoint dirp; if (!choose_adjacent(_("Burrow where?"), dirp)) { tdata.powered = false; return; } if( dirp == pos() ) { add_msg_if_player(_("You've got places to go and critters to beat.")); add_msg_if_player(_("Let the lesser folks eat their hearts out.")); tdata.powered = false; return; } int turns; if (g->m.is_bashable(dirp) && g->m.has_flag("SUPPORTS_ROOF", dirp) && g->m.ter(dirp) != t_tree) { // Takes about 100 minutes (not quite two hours) base time. // Being better-adapted to the task means that skillful Survivors can do it almost twice as fast. ///\EFFECT_CARPENTRY speeds up burrowing turns = (100000 - 5000 * skillLevel( skill_id( "carpentry" ) )); } else if (g->m.move_cost(dirp) == 2 && g->get_levz() == 0 && g->m.ter(dirp) != t_dirt && g->m.ter(dirp) != t_grass) { turns = 18000; } else { add_msg_if_player(m_info, _("You can't burrow there.")); tdata.powered = false; return; } assign_activity(ACT_BURROW, turns, -1, 0); activity.placement = dirp; add_msg_if_player(_("You tear into the %s with your teeth and claws."), g->m.tername(dirp).c_str()); tdata.powered = false; return; // handled when the activity finishes } else if (mut == "SLIMESPAWNER") { std::vector<tripoint> valid; for (int x = posx() - 1; x <= posx() + 1; x++) { for (int y = posy() - 1; y <= posy() + 1; y++) { tripoint dest(x, y, posz()); if (g->is_empty(dest)) { valid.push_back( dest ); } } } // Oops, no room to divide! if (valid.size() == 0) { add_msg_if_player(m_bad, _("You focus, but are too hemmed in to birth a new slimespring!")); tdata.powered = false; return; } add_msg_if_player(m_good, _("You focus, and with a pleasant splitting feeling, birth a new slimespring!")); int numslime = 1; for (int i = 0; i < numslime && !valid.empty(); i++) { const tripoint target = random_entry_removed( valid ); if (g->summon_mon(mtype_id( "mon_player_blob" ), target)) { monster *slime = g->monster_at( target ); slime->friendly = -1; } } if (one_in(3)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("wow! you look just like me! we should look out for each other!")); } else if (one_in(2)) { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("come on, big me, let's go!")); } else { //~ Usual enthusiastic slimespring small voices! :D add_msg_if_player(m_good, _("we're a team, we've got this!")); } tdata.powered = false; return; } else if ((mut == "NAUSEA") || (mut == "VOMITOUS") ){ vomit(); tdata.powered = false; return; } else if (mut == "M_FERTILE"){ spores(); tdata.powered = false; return; } else if (mut == "M_BLOOM"){ blossoms(); tdata.powered = false; return; } else if (mut == "VINES3"){ item newit( "vine_30", calendar::turn ); if (!can_pickVolume(newit.volume())) { //Accounts for result_mult add_msg_if_player(_("You detach a vine but don't have room to carry it, so you drop it.")); g->m.add_item_or_charges(pos(), newit); } else if (!can_pickWeight(newit.weight(), !OPTIONS["DANGEROUS_PICKUPS"])) { add_msg_if_player(_("Your freshly-detached vine is too heavy to carry, so you drop it.")); g->m.add_item_or_charges(pos(), newit); } else { inv.assign_empty_invlet(newit); newit = i_add(newit); add_msg_if_player(m_info, "%c - %s", newit.invlet == 0 ? ' ' : newit.invlet, newit.tname().c_str()); } tdata.powered = false; return; } else if( mut == "SELFAWARE" ) { print_health(); tdata.powered = false; return; } }
std::pair<skill_id, int> get_next( JsonIn &jin ) const { JsonObject jo = jin.get_object(); return std::pair<skill_id, int>( skill_id( jo.get_string( "name" ) ), jo.get_int( "level" ) ); }
skill_id Skill::from_legacy_int( const int legacy_id ) { static const std::array<skill_id, 28> legacy_skills = { { skill_id::NULL_ID(), skill_id( "dodge" ), skill_id( "melee" ), skill_id( "unarmed" ), skill_id( "bashing" ), skill_id( "cutting" ), skill_id( "stabbing" ), skill_id( "throw" ), skill_id( "gun" ), skill_id( "pistol" ), skill_id( "shotgun" ), skill_id( "smg" ), skill_id( "rifle" ), skill_id( "archery" ), skill_id( "launcher" ), skill_id( "mechanics" ), skill_id( "electronics" ), skill_id( "cooking" ), skill_id( "tailor" ), skill_id::NULL_ID(), skill_id( "firstaid" ), skill_id( "speech" ), skill_id( "barter" ), skill_id( "computer" ), skill_id( "survival" ), skill_id( "traps" ), skill_id( "swimming" ), skill_id( "driving" ), } }; if( static_cast<size_t>( legacy_id ) < legacy_skills.size() ) { return legacy_skills[legacy_id]; } debugmsg( "legacy skill id %d is invalid", legacy_id ); return skills.front().ident(); // return a non-null id because callers might not expect a null-id }
void player_activity::do_turn( player *p ) { switch( type ) { case ACT_WAIT: case ACT_WAIT_NPC: case ACT_WAIT_WEATHER: // Based on time, not speed moves_left -= 100; p->rooted(); p->pause(); break; case ACT_PICKAXE: // Based on speed, not time if( p->moves <= moves_left ) { moves_left -= p->moves; p->moves = 0; } else { p->moves -= moves_left; moves_left = 0; } activity_handlers::pickaxe_do_turn( this, p ); break; case ACT_BURROW: // Based on speed, not time if( p->moves <= moves_left ) { moves_left -= p->moves; p->moves = 0; } else { p->moves -= moves_left; moves_left = 0; } activity_handlers::burrow_do_turn( this, p ); break; case ACT_AIM: if( index == 0 ) { if( !p->weapon.is_gun() ) { // We lost our gun somehow, bail out. type = ACT_NULL; break; } g->m.build_map_cache( g->get_levz() ); g->plfire(); } break; case ACT_GAME: // Takes care of u.activity.moves_left activity_handlers::game_do_turn( this, p ); break; case ACT_VIBE: // Takes care of u.activity.moves_left activity_handlers::vibe_do_turn( this, p ); break; case ACT_REFILL_VEHICLE: type = ACT_NULL; // activity is not used anymore. break; case ACT_PULP: // does not really use u.activity.moves_left, stops itself when finished activity_handlers::pulp_do_turn( this, p ); break; case ACT_FISH: // Based on time, not speed--or it should be // (Being faster doesn't make the fish bite quicker) moves_left -= 100; p->rooted(); p->pause(); break; case ACT_DROP: activity_on_turn_drop(); break; case ACT_STASH: activity_on_turn_stash(); break; case ACT_PICKUP: activity_on_turn_pickup(); break; case ACT_MOVE_ITEMS: activity_on_turn_move_items(); break; case ACT_ADV_INVENTORY: p->cancel_activity(); advanced_inv(); break; case ACT_ARMOR_LAYERS: p->cancel_activity(); p->sort_armor(); break; case ACT_START_FIRE: moves_left -= 100; // based on time if( p->i_at( position ).has_flag( "LENS" ) ) { // if using a lens, handle potential changes in weather activity_handlers::start_fire_lens_do_turn( this, p ); } p->rooted(); p->pause(); break; case ACT_OPEN_GATE: // Based on speed, not time if( p->moves <= moves_left ) { moves_left -= p->moves; p->moves = 0; } else { p->moves -= moves_left; moves_left = 0; } break; case ACT_FILL_LIQUID: activity_handlers::fill_liquid_do_turn( this, p ); break; case ACT_ATM: iexamine::atm( *p, p->pos() ); break; case ACT_START_ENGINES: moves_left -= 100; p->rooted(); p->pause(); break; case ACT_OXYTORCH: if( p->moves <= moves_left ) { moves_left -= p->moves; p->moves = 0; } else { p->moves -= moves_left; moves_left = 0; } if( values[0] > 0 ) { activity_handlers::oxytorch_do_turn( this, p ); } break; case ACT_CRACKING: if( !( p->has_amount( "stethoscope", 1 ) || p->has_bionic( "bio_ears" ) ) ) { // We lost our cracking tool somehow, bail out. type = ACT_NULL; break; } // Based on speed, not time if( p->moves <= moves_left ) { moves_left -= p->moves; p->moves = 0; } else { p->moves -= moves_left; moves_left = 0; } p->practice( skill_id( "mechanics" ), 1 ); break; case ACT_REPAIR_ITEM: { // Based on speed * detail vision const int effective_moves = p->moves / p->fine_detail_vision_mod(); if( effective_moves <= moves_left ) { moves_left -= effective_moves; p->moves = 0; } else { p->moves -= moves_left * p->fine_detail_vision_mod(); moves_left = 0; } } break; case ACT_BUTCHER: // Drain some stamina p->mod_stat( "stamina", -20.0f * p->stamina / p->get_stamina_max() ); // Based on speed, not time if( p->moves <= moves_left ) { moves_left -= p->moves; p->moves = 0; } else { p->moves -= moves_left; moves_left = 0; } break; default: // Based on speed, not time if( p->moves <= moves_left ) { moves_left -= p->moves; p->moves = 0; } else { p->moves -= moves_left; moves_left = 0; } } if( is_complete() ) { finish( p ); } }
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 ); }