void clear_player() { player &dummy = g->u; // Remove first worn item until there are none left. std::list<item> temp; while( dummy.takeoff( dummy.i_at( -2 ), &temp ) ); dummy.inv.clear(); dummy.remove_weapon(); for( const trait_id &tr : dummy.get_mutations() ) { dummy.unset_mutation( tr ); } // Prevent spilling, but don't cause encumbrance if( !dummy.has_trait( trait_id( "DEBUG_STORAGE" ) ) ) { dummy.set_mutation( trait_id( "DEBUG_STORAGE" ) ); } dummy.clear_morale(); dummy.clear_bionics(); // Make stats nominal. dummy.str_cur = 8; dummy.dex_cur = 8; dummy.int_cur = 8; dummy.per_cur = 8; const tripoint spot( 60, 60, 0 ); dummy.setpos( spot ); }
bool trap::detect_trap( const tripoint &pos, const player &p ) const { // Some decisions are based around: // * Starting, and thus average perception, is 8. // * Buried landmines, the silent killer, has a visibility of 10. // * There will always be a distance malus of 1 unless you're on top of the trap. // * ...and an average character should at least have a minor chance of // noticing a buried landmine if standing right next to it. // Effective Perception... ///\EFFECT_PER increases chance of detecting a trap return ( p.per_cur - ( p.encumb( bp_eyes ) / 10 ) ) + // ...small bonus from stimulants... ( p.stim > 10 ? rng( 1, 2 ) : 0 ) + // ...bonus from trap skill... ///\EFFECT_TRAPS increases chance of detecting a trap ( p.get_skill_level( skill_traps ) * 2 ) + // ...luck, might be good, might be bad... rng( -4, 4 ) - // ...malus if we are tired... ( p.has_effect( effect_lack_sleep ) ? rng( 1, 5 ) : 0 ) - // ...malus farther we are from trap... rl_dist( p.pos(), pos ) + // Police are trained to notice Something Wrong. ( p.has_trait( trait_id( "PROF_POLICE" ) ) ? 1 : 0 ) + ( p.has_trait( trait_id( "PROF_PD_DET" ) ) ? 2 : 0 ) > // ...must all be greater than the trap visibility. visibility; }
void prepare_test() { player &dummy = g->u; // Remove first worn item until there are none left. std::list<item> temp; while( dummy.takeoff( dummy.i_at( -2 ), &temp ) ); for( trait_id tr : dummy.get_mutations() ) { dummy.unset_mutation( tr ); } // Prevent spilling, but don't cause encumbrance if( !dummy.has_trait( trait_id( "DEBUG_STORAGE" ) ) ) { dummy.set_mutation( trait_id( "DEBUG_STORAGE" ) ); } // Make stats nominal. dummy.str_cur = 8; dummy.dex_cur = 8; dummy.int_cur = 8; dummy.per_cur = 8; const tripoint spot( 60, 60, 0 ); dummy.setpos( spot ); g->m.ter_set( spot, ter_id( "t_dirt" ) ); g->m.furn_set( spot, furn_id( "f_null" ) ); g->m.i_clear( spot ); }
ret_val<edible_rating> player::can_eat( const item &food ) const { // @todo This condition occurs way too often. Unify it. if( is_underwater() ) { return ret_val<edible_rating>::make_failure( _( "You can't do that while underwater." ) ); } const auto &comest = food.type->comestible; if( !comest ) { return ret_val<edible_rating>::make_failure( _( "That doesn't look edible." ) ); } const bool eat_verb = food.has_flag( "USE_EAT_VERB" ); const bool edible = eat_verb || comest->comesttype == "FOOD"; const bool drinkable = !eat_verb && comest->comesttype == "DRINK"; if( edible || drinkable ) { for( const auto &elem : food.type->materials ) { if( !elem->edible() ) { return ret_val<edible_rating>::make_failure( _( "That doesn't look edible in its current form." ) ); } } } if( comest->tool != "null" ) { const bool has = item::count_by_charges( comest->tool ) ? has_charges( comest->tool, 1 ) : has_amount( comest->tool, 1 ); if( !has ) { return ret_val<edible_rating>::make_failure( NO_TOOL, string_format( _( "You need a %s to consume that!" ), item::nname( comest->tool ).c_str() ) ); } } // For all those folks who loved eating marloss berries. D:< mwuhahaha if( has_trait( trait_id( "M_DEPENDENT" ) ) && food.typeId() != "mycus_fruit" ) { return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "We can't eat that. It's not right for us." ) ); } // Here's why PROBOSCIS is such a negative trait. if( has_trait( trait_id( "PROBOSCIS" ) ) && !drinkable ) { return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "Ugh, you can't drink that!" ) ); } if( has_trait( trait_id( "CARNIVORE" ) ) && nutrition_for( food ) > 0 && food.has_any_flag( carnivore_blacklist ) && !food.has_flag( "CARNIVORE_OK" ) ) { return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "Eww. Inedible plant stuff!" ) ); } if( ( has_trait( trait_id( "HERBIVORE" ) ) || has_trait( trait_id( "RUMINANT" ) ) ) && food.has_any_flag( herbivore_blacklist ) ) { // Like non-cannibal, but more strict! return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "The thought of eating that makes you feel sick." ) ); } return ret_val<edible_rating>::make_success(); }
void mutation_branch::load_trait_group( JsonObject &jsobj, const trait_group::Trait_group_tag &gid, const std::string &subtype ) { if( subtype != "distribution" && subtype != "collection" && subtype != "old" ) { jsobj.throw_error( "unknown trait group type", "subtype" ); } Trait_group &tg = make_group_or_throw( gid, ( subtype == "collection" || subtype == "old" ) ); // TODO: (sm) Looks like this makes the new code backwards-compatible with the old format. Great if so! if( subtype == "old" ) { JsonArray traits = jsobj.get_array( "traits" ); while( traits.has_more() ) { JsonArray pair = traits.next_array(); tg.add_trait_entry( trait_id( pair.get_string( 0 ) ), pair.get_int( 1 ) ); } return; } // TODO: (sm) Taken from item_factory.cpp almost verbatim. Ensure that these work! if( jsobj.has_member( "entries" ) ) { JsonArray traits = jsobj.get_array( "entries" ); while( traits.has_more() ) { JsonObject subobj = traits.next_object(); add_entry( tg, subobj ); } } if( jsobj.has_member( "traits" ) ) { JsonArray traits = jsobj.get_array( "traits" ); while( traits.has_more() ) { if( traits.test_string() ) { tg.add_trait_entry( trait_id( traits.next_string() ), 100 ); } else if( traits.test_array() ) { JsonArray subtrait = traits.next_array(); tg.add_trait_entry( trait_id( subtrait.get_string( 0 ) ), subtrait.get_int( 1 ) ); } else { JsonObject subobj = traits.next_object(); add_entry( tg, subobj ); } } } if( jsobj.has_member( "groups" ) ) { JsonArray traits = jsobj.get_array( "groups" ); while( traits.has_more() ) { if( traits.test_string() ) { tg.add_group_entry( trait_group::Trait_group_tag( traits.next_string() ), 100 ); } else if( traits.test_array() ) { JsonArray subtrait = traits.next_array(); tg.add_group_entry( trait_group::Trait_group_tag( traits.get_string( 0 ) ), subtrait.get_int( 1 ) ); } else { JsonObject subobj = traits.next_object(); add_entry( tg, subobj ); } } } }
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 ); } }
int player::stomach_capacity() const { if( has_trait( trait_id( "GIZZARD" ) ) ) { return 0; } if( has_active_mutation( trait_id( "HIBERNATE" ) ) ) { return -620; } if( has_trait( trait_id( "GOURMAND" ) ) || has_trait( trait_id( "HIBERNATE" ) ) ) { return -60; } return -20; }
std::list<item> craft_command::consume_components() { std::list<item> used; if( crafter->has_trait( trait_id( "DEBUG_HS" ) ) ) { return used; } if( empty() ) { debugmsg( "Warning: attempted to consume items from an empty craft_command" ); return used; } inventory map_inv; map_inv.form_from_map( crafter->pos(), PICKUP_RANGE ); if( !check_item_components_missing( map_inv ).empty() ) { debugmsg( "Aborting crafting: couldn't find cached components" ); return used; } for( const auto &it : item_selections ) { std::list<item> tmp = crafter->consume_items( it, batch_size ); used.splice( used.end(), tmp ); } for( const auto &it : tool_selections ) { crafter->consume_tools( it, batch_size ); } return used; }
void mutation_branch::load_trait_blacklist( JsonObject &jsobj ) { JsonArray jarr = jsobj.get_array( "traits" ); while( jarr.has_more() ) { trait_blacklist.insert( trait_id( jarr.next_string() ) ); } }
units::volume stomach_contents::capacity() const { float max_mod = 1; if( g->u.has_trait( trait_id( "GIZZARD" ) ) ) { max_mod *= 0.9; } if( g->u.has_active_mutation( trait_id( "HIBERNATE" ) ) ) { max_mod *= 3; } if( g->u.has_active_mutation( trait_id( "GOURMAND" ) ) ) { max_mod *= 2; } if( g->u.has_trait( trait_id( "SLIMESPAWNER" ) ) ) { max_mod *= 3; } return max_volume * max_mod; }
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 json_item_substitution::do_load( JsonObject &jo ) { const bool item_mode = jo.has_string( "item" ); const std::string title = jo.get_string( item_mode ? "item" : "trait" ); auto check_duplicate_item = [&]( const itype_id & it ) { return substitutions.find( it ) != substitutions.end() || std::find_if( bonuses.begin(), bonuses.end(), [&it]( const std::pair<itype_id, trait_requirements> &p ) { return p.first == it; } ) != bonuses.end(); }; if( item_mode && check_duplicate_item( title ) ) { jo.throw_error( "Duplicate definition of item" ); } if( jo.has_array( "bonus" ) ) { if( !item_mode ) { jo.throw_error( "Bonuses can only be used in item mode" ); } JsonArray arr = jo.get_array( "bonus" ); bonuses.emplace_back( title, trait_requirements::load( arr ) ); } else if( !jo.has_array( "sub" ) ) { jo.throw_error( "Missing sub array" ); } JsonArray sub = jo.get_array( "sub" ); while( sub.has_more() ) { JsonArray line = sub.next_array(); substitution s; const itype_id old_it = item_mode ? title : line.next_string(); if( item_mode ) { s.trait_reqs = trait_requirements::load( line ); } else { if( check_duplicate_item( old_it ) ) { line.throw_error( "Duplicate definition of item" ); } s.trait_reqs.present.push_back( trait_id( title ) ); } // Error if the array doesn't have at least one new_item do { s.infos.push_back( substitution::info::load( line ) ); } while( line.has_more() ); substitutions[old_it].push_back( s ); } }
npc create_model() { npc model_npc; model_npc.normalize(); model_npc.randomize( NC_NONE ); for( trait_id tr : model_npc.get_mutations() ) { model_npc.unset_mutation( tr ); } model_npc.set_hunger( 0 ); model_npc.set_thirst( 0 ); model_npc.set_fatigue( 0 ); model_npc.remove_effect( efftype_id( "sleep" ) ); // An ugly hack to prevent NPC falling asleep during testing due to massive fatigue model_npc.set_mutation( trait_id( "WEB_WEAVER" ) ); return model_npc; }
void bite_actor::on_damage( monster &z, Creature &target, dealt_damage_instance &dealt ) const { melee_actor::on_damage( z, target, dealt ); if( target.has_effect( effect_grabbed ) && one_in( no_infection_chance - dealt.total_damage() ) ) { const body_part hit = dealt.bp_hit; if( target.has_effect( effect_bite, hit ) ) { target.add_effect( effect_bite, 40_minutes, hit, true ); } else if( target.has_effect( effect_infected, hit ) ) { target.add_effect( effect_infected, 25_minutes, hit, true ); } else { target.add_effect( effect_bite, 1_turns, hit, true ); } } if( target.has_trait( trait_id( "TOXICFLESH" ) ) ) { z.add_effect( effect_poison, 5_minutes ); z.add_effect( effect_badpoison, 5_minutes ); } }
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 mutation_category_trait::load( JsonObject &jsobj ) { mutation_category_trait new_category; new_category.id = jsobj.get_string( "id" ); new_category.raw_name = jsobj.get_string( "name" ); new_category.threshold_mut = trait_id( jsobj.get_string( "threshold_mut" ) ); new_category.raw_mutagen_message = jsobj.get_string( "mutagen_message" ); new_category.mutagen_hunger = jsobj.get_int( "mutagen_hunger", 10 ); new_category.mutagen_thirst = jsobj.get_int( "mutagen_thirst", 10 ); new_category.mutagen_pain = jsobj.get_int( "mutagen_pain", 2 ); new_category.mutagen_fatigue = jsobj.get_int( "mutagen_fatigue", 5 ); new_category.mutagen_morale = jsobj.get_int( "mutagen_morale", 0 ); new_category.raw_iv_message = jsobj.get_string( "iv_message" ); new_category.iv_min_mutations = jsobj.get_int( "iv_min_mutations", 1 ); new_category.iv_additional_mutations = jsobj.get_int( "iv_additional_mutations", 2 ); new_category.iv_additional_mutations_chance = jsobj.get_int( "iv_additional_mutations_chance", 3 ); new_category.iv_hunger = jsobj.get_int( "iv_hunger", 10 ); new_category.iv_thirst = jsobj.get_int( "iv_thirst", 10 ); new_category.iv_pain = jsobj.get_int( "iv_pain", 2 ); new_category.iv_fatigue = jsobj.get_int( "iv_fatigue", 5 ); new_category.iv_morale = jsobj.get_int( "iv_morale", 0 ); new_category.iv_morale_max = jsobj.get_int( "iv_morale_max", 0 ); new_category.iv_sound = jsobj.get_bool( "iv_sound", false ); new_category.raw_iv_sound_message = jsobj.get_string( "iv_sound_message", translate_marker( "You inject yoursel-arRGH!" ) ); new_category.raw_iv_sound_id = jsobj.get_string( "iv_sound_id", "shout" ); new_category.raw_iv_sound_variant = jsobj.get_string( "iv_sound_variant", "default" ); new_category.iv_noise = jsobj.get_int( "iv_noise", 0 ); new_category.iv_sleep = jsobj.get_bool( "iv_sleep", false ); new_category.raw_iv_sleep_message = jsobj.get_string( "iv_sleep_message", translate_marker( "You fall asleep." ) ); new_category.iv_sleep_dur = jsobj.get_int( "iv_sleep_dur", 0 ); static_cast<void>( translate_marker_context( "memorial_male", "Crossed a threshold" ) ); static_cast<void>( translate_marker_context( "memorial_female", "Crossed a threshold" ) ); new_category.raw_memorial_message = jsobj.get_string( "memorial_message", "Crossed a threshold" ); new_category.raw_junkie_message = jsobj.get_string( "junkie_message", translate_marker( "Oh, yeah! That's the stuff!" ) ); mutation_category_traits[new_category.id] = new_category; }
void draw_description( const catacurses::window &win, bionic const &bio ) { werase( win ); const int width = getmaxx( win ); const std::string poweronly_string = build_bionic_poweronly_string( bio ); int ypos = fold_and_print( win, 0, 0, width, c_white, bio.id->name ); if( !poweronly_string.empty() ) { ypos += fold_and_print( win, ypos, 0, width, c_light_gray, _( "Power usage: %s" ), poweronly_string.c_str() ); } ypos += 1 + fold_and_print( win, ypos, 0, width, c_light_blue, bio.id->description ); // @todo: Unhide when enforcing limits if( g->u.has_trait( trait_id( "DEBUG_CBM_SLOTS" ) ) ) { const bool each_bp_on_new_line = ypos + ( int )num_bp + 1 < getmaxy( win ); ypos += fold_and_print( win, ypos, 0, width, c_light_gray, list_occupied_bps( bio.id, _( "This bionic occupies the following body parts:" ), each_bp_on_new_line ) ); } wrefresh( win ); }
morale_type player::allergy_type( const item &food ) const { using allergy_tuple = std::tuple<trait_id, std::string, morale_type>; static const std::array<allergy_tuple, 8> allergy_tuples = {{ std::make_tuple( trait_id( "VEGETARIAN" ), "ALLERGEN_MEAT", MORALE_VEGETARIAN ), std::make_tuple( trait_id( "MEATARIAN" ), "ALLERGEN_VEGGY", MORALE_MEATARIAN ), std::make_tuple( trait_id( "LACTOSE" ), "ALLERGEN_MILK", MORALE_LACTOSE ), std::make_tuple( trait_id( "ANTIFRUIT" ), "ALLERGEN_FRUIT", MORALE_ANTIFRUIT ), std::make_tuple( trait_id( "ANTIJUNK" ), "ALLERGEN_JUNK", MORALE_ANTIJUNK ), std::make_tuple( trait_id( "ANTIWHEAT" ), "ALLERGEN_WHEAT", MORALE_ANTIWHEAT ) } }; for( const auto &tp : allergy_tuples ) { if( has_trait( std::get<0>( tp ) ) && food.has_flag( std::get<1>( tp ) ) ) { return std::get<2>( tp ); } } return MORALE_NULL; }
ret_val<edible_rating> player::will_eat( const item &food, bool interactive ) const { const auto ret = can_eat( food ); if( !ret.success() ) { if( interactive ) { add_msg_if_player( m_info, "%s", ret.c_str() ); } return ret; } std::vector<ret_val<edible_rating>> consequences; const auto add_consequence = [&consequences]( const std::string & msg, edible_rating code ) { consequences.emplace_back( ret_val<edible_rating>::make_failure( code, msg ) ); }; const bool saprophage = has_trait( trait_id( "SAPROPHAGE" ) ); const auto &comest = food.type->comestible; if( food.rotten() ) { const bool saprovore = has_trait( trait_id( "SAPROVORE" ) ); if( !saprophage && !saprovore ) { add_consequence( _( "This is rotten and smells awful!" ), ROTTEN ); } } const bool carnivore = has_trait( trait_id( "CARNIVORE" ) ); if( food.has_flag( "CANNIBALISM" ) && !has_trait_flag( "CANNIBAL" ) ) { add_consequence( _( "The thought of eating human flesh makes you feel sick." ), CANNIBALISM ); } const bool edible = comest->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); if( edible && has_effect( effect_nausea ) ) { add_consequence( _( "You still feel nauseous and will probably puke it all up again." ), NAUSEA ); } if( ( allergy_type( food ) != MORALE_NULL ) || ( carnivore && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) ) ) { add_consequence( _( "Your stomach won't be happy (allergy)." ), ALLERGY ); } if( saprophage && edible && food.rotten() && !food.has_flag( "FERTILIZER" ) ) { // Note: We're allowing all non-solid "food". This includes drugs // Hard-coding fertilizer for now - should be a separate flag later //~ No, we don't eat "rotten" food. We eat properly aged food, like a normal person. //~ Semantic difference, but greatly facilitates people being proud of their character. add_consequence( _( "Your stomach won't be happy (not rotten enough)." ), ALLERGY_WEAK ); } const int nutr = nutrition_for( food ); const int quench = comest->quench; const int temp_hunger = get_hunger() - nutr; const int temp_thirst = get_thirst() - quench; if( !has_active_mutation( trait_id( "EATHEALTH" ) ) && !has_active_mutation( trait_id( "HIBERNATE" ) ) && !has_trait( trait_id( "SLIMESPAWNER" ) ) ) { if( get_hunger() < 0 && nutr >= 5 && !has_active_mutation( trait_id( "GOURMAND" ) ) ) { add_consequence( _( "You're full already and will be forcing yourself to eat." ), TOO_FULL ); } else if( ( ( nutr > 0 && temp_hunger < stomach_capacity() ) || ( comest->quench > 0 && temp_thirst < stomach_capacity() ) ) && !food.has_infinite_charges() ) { add_consequence( _( "You will not be able to finish it all." ), TOO_FULL ); } } if( !consequences.empty() ) { if( !interactive ) { return consequences.front(); } std::ostringstream req; for( const auto &elem : consequences ) { req << elem.str() << std::endl; } const bool eat_verb = food.has_flag( "USE_EAT_VERB" ); if( eat_verb || comest->comesttype == "FOOD" ) { req << string_format( _( "Eat your %s anyway?" ), food.tname().c_str() ); } else if( !eat_verb && comest->comesttype == "DRINK" ) { req << string_format( _( "Drink your %s anyway?" ), food.tname().c_str() ); } else { req << string_format( _( "Consume your %s anyway?" ), food.tname().c_str() ); } if( !query_yn( req.str() ) ) { return consequences.front(); } } // All checks ended, it's edible (or we're pretending it is) return ret_val<edible_rating>::make_success(); }
struct less<failure> { bool operator()( const failure &lhs, const failure &rhs ) const { return lhs.prof < rhs.prof; } }; } // TODO: According to profiling (interrupt, backtrace, wait a few seconds, repeat) with a sample // size of 20, 70% of the time is due to the call to Character::set_mutation in try_set_traits. // When the mutation stuff isn't commented out, the test takes 110 minutes (not a typo)! TEST_CASE( "starting_items" ) { // Every starting trait that interferes with food/clothing const std::vector<trait_id> mutations = { trait_id( "ANTIFRUIT" ), trait_id( "ANTIJUNK" ), trait_id( "ANTIWHEAT" ), //trait_id( "ARM_TENTACLES" ), //trait_id( "BEAK" ), trait_id( "CANNIBAL" ), //trait_id( "CARNIVORE" ), //trait_id( "HERBIVORE" ), //trait_id( "HOOVES" ), trait_id( "LACTOSE" ), //trait_id( "LEG_TENTACLES" ), trait_id( "MEATARIAN" ), //trait_id( "RAP_TALONS" ), //trait_id( "TAIL_FLUFFY" ), //trait_id( "TAIL_LONG" ), trait_id( "VEGETARIAN" ),
void player::power_bionics() { std::vector <bionic *> passive = filtered_bionics( *my_bionics, TAB_PASSIVE ); std::vector <bionic *> active = filtered_bionics( *my_bionics, TAB_ACTIVE ); bionic *bio_last = NULL; bionic_tab_mode tab_mode = TAB_ACTIVE; //added title_tab_height for the tabbed bionic display int TITLE_HEIGHT = 2; int TITLE_TAB_HEIGHT = 3; // Main window /** Total required height is: * top frame line: + 1 * height of title window: + TITLE_HEIGHT * height of tabs: + TITLE_TAB_HEIGHT * height of the biggest list of active/passive bionics: + bionic_count * bottom frame line: + 1 * TOTAL: TITLE_HEIGHT + TITLE_TAB_HEIGHT + bionic_count + 2 */ const int HEIGHT = std::min( TERMY, std::max( FULL_SCREEN_HEIGHT, TITLE_HEIGHT + TITLE_TAB_HEIGHT + ( int )my_bionics->size() + 2 ) ); const int WIDTH = FULL_SCREEN_WIDTH + ( TERMX - FULL_SCREEN_WIDTH ) / 2; const int START_X = ( TERMX - WIDTH ) / 2; const int START_Y = ( TERMY - HEIGHT ) / 2; //wBio is the entire bionic window catacurses::window wBio = catacurses::newwin( HEIGHT, WIDTH, START_Y, START_X ); const int LIST_HEIGHT = HEIGHT - TITLE_HEIGHT - TITLE_TAB_HEIGHT - 2; const int DESCRIPTION_WIDTH = WIDTH - 2 - 40; const int DESCRIPTION_START_Y = START_Y + TITLE_HEIGHT + TITLE_TAB_HEIGHT + 1; const int DESCRIPTION_START_X = START_X + 1 + 40; //w_description is the description panel that is controlled with ! key catacurses::window w_description = catacurses::newwin( LIST_HEIGHT, DESCRIPTION_WIDTH, DESCRIPTION_START_Y, DESCRIPTION_START_X ); // Title window const int TITLE_START_Y = START_Y + 1; const int HEADER_LINE_Y = TITLE_HEIGHT + TITLE_TAB_HEIGHT + 1; catacurses::window w_title = catacurses::newwin( TITLE_HEIGHT, WIDTH - 2, TITLE_START_Y, START_X + 1 ); const int TAB_START_Y = TITLE_START_Y + 2; //w_tabs is the tab bar for passive and active bionic groups catacurses::window w_tabs = catacurses::newwin( TITLE_TAB_HEIGHT, WIDTH - 2, TAB_START_Y, START_X + 1 ); int scroll_position = 0; int cursor = 0; //generate the tab title string and a count of the bionics owned bionic_menu_mode menu_mode = ACTIVATING; // offset for display: bionic with index i is drawn at y=list_start_y+i // drawing the bionics starts with bionic[scroll_position] const int list_start_y = HEADER_LINE_Y;// - scroll_position; int half_list_view_location = LIST_HEIGHT / 2; int max_scroll_position = std::max( 0, ( int )active.size() ); input_context ctxt( "BIONICS" ); ctxt.register_updown(); ctxt.register_action( "ANY_INPUT" ); ctxt.register_action( "TOGGLE_EXAMINE" ); ctxt.register_action( "REASSIGN" ); ctxt.register_action( "REMOVE" ); ctxt.register_action( "NEXT_TAB" ); ctxt.register_action( "PREV_TAB" ); ctxt.register_action( "CONFIRM" ); ctxt.register_action( "HELP_KEYBINDINGS" ); bool recalc = false; bool redraw = true; for( ;; ) { if( recalc ) { passive = filtered_bionics( *my_bionics, TAB_PASSIVE ); active = filtered_bionics( *my_bionics, TAB_ACTIVE ); if( active.empty() && !passive.empty() ) { tab_mode = TAB_PASSIVE; } if( --cursor < 0 ) { cursor = 0; } if( scroll_position > max_scroll_position && cursor - scroll_position < LIST_HEIGHT - half_list_view_location ) { scroll_position--; } recalc = false; // bionics were modified, so it's necessary to redraw the screen redraw = true; } //track which list we are looking at std::vector<bionic *> *current_bionic_list = ( tab_mode == TAB_ACTIVE ? &active : &passive ); max_scroll_position = std::max( 0, ( int )current_bionic_list->size() - LIST_HEIGHT ); if( redraw ) { redraw = false; werase( wBio ); draw_border( wBio, BORDER_COLOR, _( " BIONICS " ) ); // Draw symbols to connect additional lines to border mvwputch( wBio, HEADER_LINE_Y - 1, 0, BORDER_COLOR, LINE_XXXO ); // |- mvwputch( wBio, HEADER_LINE_Y - 1, WIDTH - 1, BORDER_COLOR, LINE_XOXX ); // -| int max_width = 0; std::vector<std::string>bps; for( int i = 0; i < num_bp; ++i ) { const body_part bp = bp_aBodyPart[i]; const int total = get_total_bionics_slots( bp ); const std::string s = string_format( "%s: %d/%d", body_part_name_as_heading( bp, 1 ).c_str(), total - get_free_bionics_slots( bp ), total ); bps.push_back( s ); max_width = std::max( max_width, utf8_width( s ) ); } const int pos_x = WIDTH - 2 - max_width; if( g->u.has_trait( trait_id( "DEBUG_CBM_SLOTS" ) ) ) { for( int i = 0; i < num_bp; ++i ) { mvwprintz( wBio, i + list_start_y, pos_x, c_light_gray, bps[i] ); } } if( current_bionic_list->empty() ) { std::string msg; switch( tab_mode ) { case TAB_ACTIVE: msg = _( "No activatable bionics installed." ); break; case TAB_PASSIVE: msg = _( "No passive bionics installed." ); break; } fold_and_print( wBio, list_start_y, 2, pos_x - 1, c_light_gray, msg ); } else { for( size_t i = scroll_position; i < current_bionic_list->size(); i++ ) { if( list_start_y + static_cast<int>( i ) - scroll_position == HEIGHT - 1 ) { break; } const bool is_highlighted = cursor == static_cast<int>( i ); const nc_color col = get_bionic_text_color( *( *current_bionic_list )[i], is_highlighted ); const std::string desc = string_format( "%c %s", ( *current_bionic_list )[i]->invlet, build_bionic_powerdesc_string( *( *current_bionic_list )[i] ).c_str() ); trim_and_print( wBio, list_start_y + i - scroll_position, 2, WIDTH - 3, col, desc ); if( is_highlighted && menu_mode != EXAMINING && g->u.has_trait( trait_id( "DEBUG_CBM_SLOTS" ) ) ) { const bionic_id bio_id = ( *current_bionic_list )[i]->id; draw_connectors( wBio, list_start_y + i - scroll_position, utf8_width( desc ) + 3, pos_x - 2, bio_id ); // redraw highlighted (occupied) body parts for( auto &elem : bio_id->occupied_bodyparts ) { const int i = static_cast<int>( elem.first ); mvwprintz( wBio, i + list_start_y, pos_x, c_yellow, bps[i] ); } } } } draw_scrollbar( wBio, cursor, LIST_HEIGHT, current_bionic_list->size(), list_start_y ); } wrefresh( wBio ); draw_bionics_tabs( w_tabs, active.size(), passive.size(), tab_mode ); draw_bionics_titlebar( w_title, this, menu_mode ); if( menu_mode == EXAMINING && !current_bionic_list->empty() ) { draw_description( w_description, *( *current_bionic_list )[cursor] ); } const std::string action = ctxt.handle_input(); const long ch = ctxt.get_raw_input().get_first_input(); bionic *tmp = NULL; bool confirmCheck = false; if( action == "DOWN" ) { redraw = true; if( static_cast<size_t>( cursor ) < current_bionic_list->size() - 1 ) { cursor++; } else { cursor = 0; } if( scroll_position < max_scroll_position && cursor - scroll_position > LIST_HEIGHT - half_list_view_location ) { scroll_position++; } if( scroll_position > 0 && cursor - scroll_position < half_list_view_location ) { scroll_position = std::max( cursor - half_list_view_location, 0 ); } } else if( action == "UP" ) { redraw = true; if( cursor > 0 ) { cursor--; } else { cursor = current_bionic_list->size() - 1; } if( scroll_position > 0 && cursor - scroll_position < half_list_view_location ) { scroll_position--; } if( scroll_position < max_scroll_position && cursor - scroll_position > LIST_HEIGHT - half_list_view_location ) { scroll_position = std::max( std::min<int>( current_bionic_list->size() - LIST_HEIGHT, cursor - half_list_view_location ), 0 ); } } else if( menu_mode == REASSIGNING ) { menu_mode = ACTIVATING; if( action == "CONFIRM" && !current_bionic_list->empty() ) { auto &bio_list = tab_mode == TAB_ACTIVE ? active : passive; tmp = bio_list[cursor]; } else { tmp = bionic_by_invlet( ch ); } if( tmp == nullptr ) { // Selected an non-existing bionic (or Escape, or ...) continue; } redraw = true; const long newch = popup_getkey( _( "%s; enter new letter. Space to clear. Esc to cancel." ), tmp->id->name.c_str() ); wrefresh( wBio ); if( newch == ch || newch == KEY_ESCAPE ) { continue; } if( newch == ' ' ) { tmp->invlet = ' '; continue; } if( !bionic_chars.valid( newch ) ) { popup( _( "Invalid bionic letter. Only those characters are valid:\n\n%s" ), bionic_chars.get_allowed_chars().c_str() ); continue; } bionic *otmp = bionic_by_invlet( newch ); if( otmp != nullptr ) { std::swap( tmp->invlet, otmp->invlet ); } else { tmp->invlet = newch; } // TODO: show a message like when reassigning a key to an item? } else if( action == "NEXT_TAB" ) { redraw = true; scroll_position = 0; cursor = 0; if( tab_mode == TAB_ACTIVE ) { tab_mode = TAB_PASSIVE; } else { tab_mode = TAB_ACTIVE; } } else if( action == "PREV_TAB" ) { redraw = true; scroll_position = 0; cursor = 0; if( tab_mode == TAB_PASSIVE ) { tab_mode = TAB_ACTIVE; } else { tab_mode = TAB_PASSIVE; } } else if( action == "REASSIGN" ) { menu_mode = REASSIGNING; } else if( action == "TOGGLE_EXAMINE" ) { // switches between activation and examination menu_mode = menu_mode == ACTIVATING ? EXAMINING : ACTIVATING; redraw = true; } else if( action == "REMOVE" ) { menu_mode = REMOVING; redraw = true; } else if( action == "HELP_KEYBINDINGS" ) { redraw = true; } else if( action == "CONFIRM" ) { confirmCheck = true; } else { confirmCheck = true; } //confirmation either occurred by pressing enter where the bionic cursor is, or the hotkey was selected if( confirmCheck ) { auto &bio_list = tab_mode == TAB_ACTIVE ? active : passive; if( action == "CONFIRM" && !current_bionic_list->empty() ) { tmp = bio_list[cursor]; } else { tmp = bionic_by_invlet( ch ); if( tmp && tmp != bio_last ) { // new bionic selected, update cursor and scroll position int temp_cursor = 0; for( temp_cursor = 0; temp_cursor < ( int )bio_list.size(); temp_cursor++ ) { if( bio_list[temp_cursor] == tmp ) { break; } } // if bionic is not found in current list, ignore the attempt to view/activate if( temp_cursor >= ( int )bio_list.size() ) { continue; } //relocate cursor to the bionic that was found cursor = temp_cursor; scroll_position = 0; while( scroll_position < max_scroll_position && cursor - scroll_position > LIST_HEIGHT - half_list_view_location ) { scroll_position++; } } } if( !tmp ) { // entered a key that is not mapped to any bionic, // -> leave screen break; } bio_last = tmp; const bionic_id &bio_id = tmp->id; const bionic_data &bio_data = bio_id.obj(); if( menu_mode == REMOVING ) { recalc = uninstall_bionic( bio_id ); redraw = true; continue; } if( menu_mode == ACTIVATING ) { if( bio_data.activated ) { int b = tmp - &( *my_bionics )[0]; if( tmp->powered ) { deactivate_bionic( b ); } else { activate_bionic( b ); // Clear the menu if we are firing a bionic gun if( tmp->info().gun_bionic ) { break; } } // update message log and the menu g->refresh_all(); redraw = true; if( moves < 0 ) { return; } continue; } else { popup( _( "You can not activate %s!\n" "To read a description of %s, press '!', then '%c'." ), bio_data.name.c_str(), bio_data.name.c_str(), tmp->invlet ); redraw = true; } } else if( menu_mode == EXAMINING ) { // Describing bionics, allow user to jump to description key redraw = true; if( action != "CONFIRM" ) { for( size_t i = 0; i < active.size(); i++ ) { if( active[i] == tmp ) { tab_mode = TAB_ACTIVE; cursor = static_cast<int>( i ); int max_scroll_check = std::max( 0, ( int )active.size() - LIST_HEIGHT ); if( static_cast<int>( i ) > max_scroll_check ) { scroll_position = max_scroll_check; } else { scroll_position = i; } break; } } for( size_t i = 0; i < passive.size(); i++ ) { if( passive[i] == tmp ) { tab_mode = TAB_PASSIVE; cursor = static_cast<int>( i ); int max_scroll_check = std::max( 0, ( int )passive.size() - LIST_HEIGHT ); if( static_cast<int>( i ) > max_scroll_check ) { scroll_position = max_scroll_check; } else { scroll_position = i; } break; } } } } } } }
void draw_connectors( const catacurses::window &win, const int start_y, const int start_x, const int last_x, const bionic_id &bio_id ) { const int LIST_START_Y = 6; // first: pos_y, second: occupied slots std::vector<std::pair<int, size_t>> pos_and_num; for( const auto &elem : bio_id->occupied_bodyparts ) { pos_and_num.emplace_back( static_cast<int>( elem.first ) + LIST_START_Y, elem.second ); } if( pos_and_num.empty() || !g->u.has_trait( trait_id( "DEBUG_CBM_SLOTS" ) ) ) { return; } // draw horizontal line from selected bionic const int turn_x = start_x + ( last_x - start_x ) * 2 / 3; mvwputch( win, start_y, start_x, BORDER_COLOR, '>' ); mvwhline( win, start_y, start_x + 1, LINE_OXOX, turn_x - start_x - 1 ); int min_y = start_y; int max_y = start_y; for( const auto &elem : pos_and_num ) { min_y = std::min( min_y, elem.first ); max_y = std::max( max_y, elem.first ); } if( max_y - min_y > 1 ) { mvwvline( win, min_y + 1, turn_x, LINE_XOXO, max_y - min_y - 1 ); } bool move_up = false; bool move_same = false; bool move_down = false; for( const auto &elem : pos_and_num ) { const int y = elem.first; if( !move_up && y < start_y ) { move_up = true; } if( !move_same && y == start_y ) { move_same = true; } if( !move_down && y > start_y ) { move_down = true; } // symbol is defined incorrectly for case ( y == start_y ) but // that's okay because it's overlapped by bionic_chr anyway long bp_chr = ( y > start_y ) ? LINE_XXOO : LINE_OXXO; if( ( max_y > y && y > start_y ) || ( min_y < y && y < start_y ) ) { bp_chr = LINE_XXXO; } mvwputch( win, y, turn_x, BORDER_COLOR, bp_chr ); // draw horizontal line to bodypart title mvwhline( win, y, turn_x + 1, LINE_OXOX, last_x - turn_x - 1 ); mvwputch( win, y, last_x, BORDER_COLOR, '<' ); // draw amount of consumed slots by this CBM const std::string fmt_num = string_format( "(%d)", elem.second ); mvwprintz( win, y, turn_x + std::max( 1, ( last_x - turn_x - utf8_width( fmt_num ) ) / 2 ), c_yellow, fmt_num ); } // define and draw a proper intersection character long bionic_chr = LINE_OXOX; // '-' // 001 if( move_up && !move_down && !move_same ) { // 100 bionic_chr = LINE_XOOX; // '_|' } else if( move_up && move_down && !move_same ) { // 110 bionic_chr = LINE_XOXX; // '-|' } else if( move_up && move_down && move_same ) { // 111 bionic_chr = LINE_XXXX; // '-|-' } else if( move_up && !move_down && move_same ) { // 101 bionic_chr = LINE_XXOX; // '_|_' } else if( !move_up && move_down && !move_same ) { // 010 bionic_chr = LINE_OOXX; // '^|' } else if( !move_up && move_down && move_same ) { // 011 bionic_chr = LINE_OXXX; // '^|^' } mvwputch( win, start_y, turn_x, BORDER_COLOR, bionic_chr ); }
bool player::eat( item &food, bool force ) { if( !food.is_food() ) { return false; } // Check if it's rotten before eating! food.calc_rot( global_square_location() ); const auto ret = force ? can_eat( food ) : will_eat( food, is_player() ); if( !ret.success() ) { return false; } if( food.type->has_use() ) { if( food.type->invoke( *this, food, pos() ) <= 0 ) { return false; } } // Note: the block below assumes we decided to eat it // No coming back from here const bool hibernate = has_active_mutation( trait_id( "HIBERNATE" ) ); const int nutr = nutrition_for( food ); const int quench = food.type->comestible->quench; const bool spoiled = food.rotten(); // The item is solid food const bool chew = food.type->comestible->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); // This item is a drink and not a solid food (and not a thick soup) const bool drinkable = !chew && food.type->comestible->comesttype == "DRINK"; // If neither of the above is true then it's a drug and shouldn't get mealtime penalty/bonus if( hibernate && ( get_hunger() > -60 && get_thirst() > -60 ) && ( get_hunger() - nutr < -60 || get_thirst() - quench < -60 ) ) { add_memorial_log( pgettext( "memorial_male", "Began preparing for hibernation." ), pgettext( "memorial_female", "Began preparing for hibernation." ) ); add_msg_if_player( _( "You've begun stockpiling calories and liquid for hibernation. You get the feeling that you should prepare for bed, just in case, but...you're hungry again, and you could eat a whole week's worth of food RIGHT NOW." ) ); } const bool will_vomit = get_hunger() < 0 && nutr >= 5 && !has_trait( trait_id( "GOURMAND" ) ) && !hibernate && !has_trait( trait_id( "SLIMESPAWNER" ) ) && !has_trait( trait_id( "EATHEALTH" ) ) && rng( -200, 0 ) > get_hunger() - nutr; const bool saprophage = has_trait( trait_id( "SAPROPHAGE" ) ); if( spoiled && !saprophage ) { add_msg_if_player( m_bad, _( "Ick, this %s doesn't taste so good..." ), food.tname().c_str() ); if( !has_trait( trait_id( "SAPROVORE" ) ) && !has_trait( trait_id( "EATDEAD" ) ) && ( !has_bionic( bio_digestion ) || one_in( 3 ) ) ) { add_effect( effect_foodpoison, rng( 60, ( nutr + 1 ) * 60 ) ); } consume_effects( food ); } else if( spoiled && saprophage ) { add_msg_if_player( m_good, _( "Mmm, this %s tastes delicious..." ), food.tname().c_str() ); consume_effects( food ); } else { consume_effects( food ); } const bool amorphous = has_trait( trait_id( "AMORPHOUS" ) ); int mealtime = 250; if( drinkable || chew ) { // Those bonuses/penalties only apply to food // Not to smoking weed or applying bandages! if( has_trait( trait_id( "MOUTH_TENTACLES" ) ) || has_trait( trait_id( "MANDIBLES" ) ) ) { mealtime /= 2; } else if( has_trait( trait_id( "GOURMAND" ) ) ) { // Don't stack those two - that would be 25 moves per item mealtime -= 100; } if( has_trait( trait_id( "BEAK_HUM" ) ) && !drinkable ) { mealtime += 200; // Much better than PROBOSCIS but still optimized for fluids } else if( has_trait( trait_id( "SABER_TEETH" ) ) ) { mealtime += 250; // They get In The Way } if( amorphous ) { mealtime *= 1.1; // Minor speed penalty for having to flow around it // rather than just grab & munch } } moves -= mealtime; // If it's poisonous... poison us. // TODO: Move this to a flag if( food.poison > 0 && !has_trait( trait_id( "EATPOISON" ) ) && !has_trait( trait_id( "EATDEAD" ) ) ) { if( food.poison >= rng( 2, 4 ) ) { add_effect( effect_poison, food.poison * 100 ); } add_effect( effect_foodpoison, food.poison * 300 ); } if( amorphous ) { add_msg_player_or_npc( _( "You assimilate your %s." ), _( "<npcname> assimilates a %s." ), food.tname().c_str() ); } else if( drinkable ) { add_msg_player_or_npc( _( "You drink your %s." ), _( "<npcname> drinks a %s." ), food.tname().c_str() ); } else if( chew ) { add_msg_player_or_npc( _( "You eat your %s." ), _( "<npcname> eats a %s." ), food.tname().c_str() ); } if( item::find_type( food.type->comestible->tool )->tool ) { // Tools like lighters get used use_charges( food.type->comestible->tool, 1 ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL" ) ) { charge_power( rng( 50, 200 ) ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL_WEAK" ) ) { charge_power( rng( 25, 100 ) ); } if( has_bionic( bio_ethanol ) && food.type->can_use( "ALCOHOL_STRONG" ) ) { charge_power( rng( 75, 300 ) ); } if( food.has_flag( "CANNIBALISM" ) ) { // Sapiovores don't recognize humans as the same species. // But let them possibly feel cool about eating sapient stuff - treat like psycho const bool cannibal = has_trait( trait_id( "CANNIBAL" ) ); const bool psycho = has_trait( trait_id( "PSYCHOPATH" ) ) || has_trait( trait_id( "SAPIOVORE" ) ); const bool spiritual = has_trait( trait_id( "SPIRITUAL" ) ); if( cannibal && psycho && spiritual ) { add_msg_if_player( m_good, _( "You feast upon the human flesh, and in doing so, devour their spirit." ) ); // You're not really consuming anything special; you just think you are. add_morale( MORALE_CANNIBAL, 25, 300 ); } else if( cannibal && psycho ) { add_msg_if_player( m_good, _( "You feast upon the human flesh." ) ); add_morale( MORALE_CANNIBAL, 15, 200 ); } else if( cannibal && spiritual ) { add_msg_if_player( m_good, _( "You consume the sacred human flesh." ) ); // Boosted because you understand the philosophical implications of your actions, and YOU LIKE THEM. add_morale( MORALE_CANNIBAL, 15, 200 ); } else if( cannibal ) { add_msg_if_player( m_good, _( "You indulge your shameful hunger." ) ); add_morale( MORALE_CANNIBAL, 10, 50 ); } else if( psycho && spiritual ) { add_msg_if_player( _( "You greedily devour the taboo meat." ) ); // Small bonus for violating a taboo. add_morale( MORALE_CANNIBAL, 5, 50 ); } else if( psycho ) { add_msg_if_player( _( "Meh. You've eaten worse." ) ); } else if( spiritual ) { add_msg_if_player( m_bad, _( "This is probably going to count against you if there's still an afterlife." ) ); add_morale( MORALE_CANNIBAL, -60, -400, 600, 300 ); } else { add_msg_if_player( m_bad, _( "You feel horrible for eating a person." ) ); add_morale( MORALE_CANNIBAL, -60, -400, 600, 300 ); } } // Allergy check const auto allergy = allergy_type( food ); if( allergy != MORALE_NULL ) { add_msg_if_player( m_bad, _( "Yuck! How can anybody eat this stuff?" ) ); add_morale( allergy, -75, -400, 300, 240 ); } // Carnivores CAN eat junk food, but they won't like it much. // Pizza-scraping happens in consume_effects. if( has_trait( trait_id( "CARNIVORE" ) ) && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) ) { add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); add_morale( MORALE_NO_DIGEST, -25, -125, 300, 240 ); } if( !spoiled && chew && has_trait( trait_id( "SAPROPHAGE" ) ) ) { // It's OK to *drink* things that haven't rotted. Alternative is to ban water. D: add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); add_morale( MORALE_NO_DIGEST, -75, -400, 300, 240 ); } if( food.has_flag( "URSINE_HONEY" ) && ( !crossed_threshold() || has_trait( trait_id( "THRESH_URSINE" ) ) ) && mutation_category_level["MUTCAT_URSINE"] > 40 ) { //Need at least 5 bear mutations for effect to show, to filter out mutations in common with other mutcats int honey_fun = has_trait( trait_id( "THRESH_URSINE" ) ) ? std::min( mutation_category_level["MUTCAT_URSINE"] / 8, 20 ) : mutation_category_level["MUTCAT_URSINE"] / 12; if( honey_fun < 10 ) { add_msg_if_player( m_good, _( "You find the sweet taste of honey surprisingly palatable." ) ); } else { add_msg_if_player( m_good, _( "You feast upon the sweet honey." ) ); } add_morale( MORALE_HONEY, honey_fun, 100 ); } if( will_vomit ) { vomit(); } // chance to become parasitised if( !( has_bionic( bio_digestion ) || has_trait( trait_id( "PARAIMMUNE" ) ) ) ) { if( food.type->comestible->parasites > 0 && one_in( food.type->comestible->parasites ) ) { switch( rng( 0, 3 ) ) { case 0: if( !has_trait( trait_id( "EATHEALTH" ) ) ) { add_effect( effect_tapeworm, 1, num_bp, true ); } break; case 1: if( !has_trait( trait_id( "ACIDBLOOD" ) ) ) { add_effect( effect_bloodworms, 1, num_bp, true ); } break; case 2: add_effect( effect_brainworms, 1, num_bp, true ); break; case 3: add_effect( effect_paincysts, 1, num_bp, true ); } } } for( const auto &v : this->vitamins_from( food ) ) { auto qty = has_effect( effect_tapeworm ) ? v.second / 2 : v.second; // can never develop hypervitaminosis from consuming food vitamin_mod( v.first, qty ); } food.mod_charges( -1 ); return true; }
void player::consume_effects( const item &food ) { if( !food.is_comestible() ) { debugmsg( "called player::consume_effects with non-comestible" ); return; } const auto &comest = *food.type->comestible; const int capacity = stomach_capacity(); if( has_trait( trait_id( "THRESH_PLANT" ) ) && food.type->can_use( "PLANTBLECH" ) ) { // Just keep nutrition capped, to prevent vomiting cap_nutrition_thirst( *this, capacity, true, true ); return; } if( ( has_trait( trait_id( "HERBIVORE" ) ) || has_trait( trait_id( "RUMINANT" ) ) ) && food.has_any_flag( herbivore_blacklist ) ) { // No good can come of this. return; } // Rotten food causes health loss const float relative_rot = food.get_relative_rot(); if( relative_rot > 1.0f && !has_trait( trait_id( "SAPROPHAGE" ) ) && !has_trait( trait_id( "SAPROVORE" ) ) && !has_bionic( bio_digestion ) ) { const float rottedness = clamp( 2 * relative_rot - 2.0f, 0.1f, 1.0f ); // ~-1 health per 1 nutrition at halfway-rotten-away, ~0 at "just got rotten" // But always round down int h_loss = -rottedness * comest.nutr; mod_healthy_mod( h_loss, -200 ); add_msg( m_debug, "%d health from %0.2f%% rotten food", h_loss, rottedness ); } const auto nutr = nutrition_for( food ); mod_hunger( -nutr ); mod_thirst( -comest.quench ); mod_stomach_food( nutr ); mod_stomach_water( comest.quench ); if( comest.healthy != 0 ) { // Effectively no cap on health modifiers from food mod_healthy_mod( comest.healthy, ( comest.healthy >= 0 ) ? 200 : -200 ); } if( comest.stim != 0 && ( abs( stim ) < ( abs( comest.stim ) * 3 ) || sgn( stim ) != sgn( comest.stim ) ) ) { if( comest.stim < 0 ) { stim = std::max( comest.stim * 3, stim + comest.stim ); } else { stim = std::min( comest.stim * 3, stim + comest.stim ); } } add_addiction( comest.add, comest.addict ); if( addiction_craving( comest.add ) != MORALE_NULL ) { rem_morale( addiction_craving( comest.add ) ); } // Morale is in minutes int morale_time = HOURS( 2 ) / MINUTES( 1 ); if( food.has_flag( "HOT" ) && food.has_flag( "EATEN_HOT" ) ) { morale_time = HOURS( 3 ) / MINUTES( 1 ); int clamped_nutr = std::max( 5, std::min( 20, nutr / 10 ) ); add_morale( MORALE_FOOD_HOT, clamped_nutr, 20, morale_time, morale_time / 2 ); } std::pair<int, int> fun = fun_for( food ); if( fun.first < 0 ) { add_morale( MORALE_FOOD_BAD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } else if( fun.first > 0 ) { add_morale( MORALE_FOOD_GOOD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } const bool hibernate = has_active_mutation( trait_id( "HIBERNATE" ) ); if( hibernate ) { if( ( nutr > 0 && get_hunger() < -60 ) || ( comest.quench > 0 && get_thirst() < -60 ) ) { //Tell the player what's going on add_msg_if_player( _( "You gorge yourself, preparing to hibernate." ) ); if( one_in( 2 ) ) { //50% chance of the food tiring you mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -200 ) || ( comest.quench > 0 && get_thirst() < -200 ) ) { //Hibernation should cut burn to 60/day add_msg_if_player( _( "You feel stocked for a day or two. Got your bed all ready and secured?" ) ); if( one_in( 2 ) ) { //And another 50%, intended cumulative mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -400 ) || ( comest.quench > 0 && get_thirst() < -400 ) ) { add_msg_if_player( _( "Mmm. You can still fit some more in...but maybe you should get comfortable and sleep." ) ); if( !one_in( 3 ) ) { //Third check, this one at 66% mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -600 ) || ( comest.quench > 0 && get_thirst() < -600 ) ) { add_msg_if_player( _( "That filled a hole! Time for bed..." ) ); // At this point, you're done. Schlaf gut. mod_fatigue( nutr ); } } // Moved here and changed a bit - it was too complex // Incredibly minor stuff like this shouldn't require complexity if( !is_npc() && has_trait( trait_id( "SLIMESPAWNER" ) ) && ( get_hunger() < capacity + 40 || get_thirst() < capacity + 40 ) ) { add_msg_if_player( m_mixed, _( "You feel as though you're going to split open! In a good way?" ) ); mod_pain( 5 ); std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( g->is_empty( dest ) ) { valid.push_back( dest ); } } int numslime = 1; for( int i = 0; i < numslime && !valid.empty(); i++ ) { const tripoint target = random_entry_removed( valid ); if( monster *const slime = g->summon_mon( mon_player_blob, target ) ) { slime->friendly = -1; } } mod_hunger( 40 ); mod_thirst( 40 ); //~slimespawns have *small voices* which may be the Nice equivalent //~of the Rat King's ALL CAPS invective. Probably shared-brain telepathy. add_msg_if_player( m_good, _( "hey, you look like me! let's work together!" ) ); } // Last thing that happens before capping hunger if( get_hunger() < capacity && has_trait( trait_id( "EATHEALTH" ) ) ) { int excess_food = capacity - get_hunger(); add_msg_player_or_npc( _( "You feel the %s filling you out." ), _( "<npcname> looks better after eating the %s." ), food.tname().c_str() ); // Guaranteed 1 HP healing, no matter what. You're welcome. ;-) if( excess_food <= 5 ) { healall( 1 ); } else { // Straight conversion, except it's divided amongst all your body parts. healall( excess_food /= 5 ); } // Note: We want this here to prevent "you can't finish this" messages set_hunger( capacity ); } cap_nutrition_thirst( *this, capacity, nutr > 0, comest.quench > 0 ); }
/* selection of component if a recipe requirement has multiple options (e.g. 'duct tap' or 'welder') */ comp_selection<item_comp> player::select_item_component( const std::vector<item_comp> &components, int batch, inventory &map_inv, bool can_cancel ) { std::vector<item_comp> player_has; std::vector<item_comp> map_has; std::vector<item_comp> mixed; comp_selection<item_comp> selected; for( const auto &component : components ) { itype_id type = component.type; int count = ( component.count > 0 ) ? component.count * batch : abs( component.count ); bool pl = false, mp = false; if( item::count_by_charges( type ) && count > 0 ) { if( has_charges( type, count ) ) { player_has.push_back( component ); pl = true; } if( map_inv.has_charges( type, count ) ) { map_has.push_back( component ); mp = true; } if( !pl && !mp && charges_of( type ) + map_inv.charges_of( type ) >= count ) { mixed.push_back( component ); } } else { // Counting by units, not charges if( has_amount( type, count ) ) { player_has.push_back( component ); pl = true; } if( map_inv.has_components( type, count ) ) { map_has.push_back( component ); mp = true; } if( !pl && !mp && amount_of( type ) + map_inv.amount_of( type ) >= count ) { mixed.push_back( component ); } } } /* select 1 component to use */ if( player_has.size() + map_has.size() + mixed.size() == 1 ) { // Only 1 choice if( player_has.size() == 1 ) { selected.use_from = use_from_player; selected.comp = player_has[0]; } else if( map_has.size() == 1 ) { selected.use_from = use_from_map; selected.comp = map_has[0]; } else { selected.use_from = use_from_both; selected.comp = mixed[0]; } } else { // Let the player pick which component they want to use uimenu cmenu; // Populate options with the names of the items for( auto &map_ha : map_has ) { std::string tmpStr = item::nname( map_ha.type ) + _( " (nearby)" ); cmenu.addentry( tmpStr ); } for( auto &player_ha : player_has ) { cmenu.addentry( item::nname( player_ha.type ) ); } for( auto &elem : mixed ) { std::string tmpStr = item::nname( elem.type ) + _( " (on person & nearby)" ); cmenu.addentry( tmpStr ); } // Unlike with tools, it's a bad thing if there aren't any components available if( cmenu.entries.empty() ) { if( has_trait( trait_id( "DEBUG_HS" ) ) ) { selected.use_from = use_from_player; return selected; } debugmsg( "Attempted a recipe with no available components!" ); selected.use_from = cancel; return selected; } if( can_cancel ) { cmenu.addentry( -1, true, 'q', _( "Cancel" ) ); } // Get the selection via a menu popup cmenu.title = _( "Use which component?" ); cmenu.query(); if( cmenu.ret == static_cast<int>( map_has.size() + player_has.size() + mixed.size() ) ) { selected.use_from = cancel; return selected; } size_t uselection = static_cast<size_t>( cmenu.ret ); if( uselection < map_has.size() ) { selected.use_from = usage::use_from_map; selected.comp = map_has[uselection]; } else if( uselection < map_has.size() + player_has.size() ) { uselection -= map_has.size(); selected.use_from = usage::use_from_player; selected.comp = player_has[uselection]; } else { uselection -= map_has.size() + player_has.size(); selected.use_from = usage::use_from_both; selected.comp = mixed[uselection]; } } return selected; }
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 addict_effect( player &u, addiction &add ) { const int in = std::min( 20, add.intensity ); switch( add.type ) { case ADD_CIG: if( !one_in( 2000 - 20 * in ) ) { break; } u.add_msg_if_player( rng( 0, 6 ) < in ? _( "You need some nicotine." ) : _( "You could use some nicotine." ) ); u.add_morale( MORALE_CRAVING_NICOTINE, -15, -3 * in ); if( one_in( 800 - 50 * in ) ) { u.mod_fatigue( 1 ); } if( u.stim > -5 * in && one_in( 400 - 20 * in ) ) { u.stim--; } break; case ADD_CAFFEINE: if( !one_in( 2000 - 20 * in ) ) { break; } u.add_msg_if_player( m_warning, _( "You want some caffeine." ) ); u.add_morale( MORALE_CRAVING_CAFFEINE, -5, -30 ); if( u.stim > -10 * in && rng( 0, 10 ) < in ) { u.stim--; } if( rng( 8, 400 ) < in ) { u.add_msg_if_player( m_bad, _( "Your hands start shaking... you need it bad!" ) ); u.add_effect( effect_shakes, 2_minutes ); } break; case ADD_ALCOHOL: case ADD_DIAZEPAM: { static const std::string alc_1 = _( "You could use a drink. " ); static const std::string alc_2 = _( "Your hands start shaking... you need a drink bad!" ) ; static const std::string dia_1 = _( "You could use some diazepam." ); static const std::string dia_2 = _( "You're shaking... you need some diazepam!" ); const std::string &msg_1 = add.type == ADD_ALCOHOL ? alc_1 : dia_1; const std::string &msg_2 = add.type == ADD_ALCOHOL ? alc_2 : dia_2; const auto morale_type = add.type == ADD_ALCOHOL ? MORALE_CRAVING_ALCOHOL : MORALE_CRAVING_DIAZEPAM; u.mod_per_bonus( -1 ); u.mod_int_bonus( -1 ); if( x_in_y( in, HOURS( 2 ) ) ) { u.mod_healthy_mod( -1, -in * 10 ); } if( one_in( 20 ) && rng( 0, 20 ) < in ) { u.add_msg_if_player( m_warning, msg_1.c_str() ); u.add_morale( morale_type, -35, -10 * in ); } else if( rng( 8, 300 ) < in ) { u.add_msg_if_player( m_bad, msg_2.c_str() ); u.add_morale( morale_type, -35, -10 * in ); u.add_effect( effect_shakes, 5_minutes ); } else if( !u.has_effect( effect_hallu ) && rng( 10, 1600 ) < in ) { u.add_effect( effect_hallu, 6_hours ); } break; } case ADD_SLEEP: // No effects here--just in player::can_sleep() // EXCEPT! Prolong this addiction longer than usual. if( one_in( 2 ) && add.sated < 0_turns ) { add.sated += 1_turns; } break; case ADD_PKILLER: if( calendar::once_every( time_duration::from_turns( 100 - in * 4 ) ) && u.get_painkiller() > 20 - in ) { u.mod_painkiller( -1 ); // Tolerance increases! } if( u.get_painkiller() >= 35 ) { // No further effects if we're doped up. add.sated = 0_turns; break; } u.mod_str_bonus( -1 ); u.mod_per_bonus( -1 ); u.mod_dex_bonus( -1 ); if( u.get_pain() < in * 2 ) { u.mod_pain( 1 ); } if( one_in( 1200 - 30 * in ) ) { u.mod_healthy_mod( -1, -in * 30 ); } if( one_in( 20 ) && dice( 2, 20 ) < in ) { u.add_msg_if_player( m_bad, _( "Your hands start shaking... you need some painkillers." ) ); u.add_morale( MORALE_CRAVING_OPIATE, -40, -10 * in ); u.add_effect( effect_shakes, 2_minutes + in * 5_turns ); } else if( one_in( 20 ) && dice( 2, 30 ) < in ) { u.add_msg_if_player( m_bad, _( "You feel anxious. You need your painkillers!" ) ); u.add_morale( MORALE_CRAVING_OPIATE, -30, -10 * in ); } else if( one_in( 50 ) && dice( 3, 50 ) < in ) { u.vomit(); } break; case ADD_SPEED: { u.mod_int_bonus( -1 ); u.mod_str_bonus( -1 ); if( u.stim > -100 && x_in_y( in, 20 ) ) { u.stim--; } if( rng( 0, 150 ) <= in ) { u.mod_healthy_mod( -1, -in ); } if( dice( 2, 100 ) < in ) { u.add_msg_if_player( m_warning, _( "You feel depressed. Speed would help." ) ); u.add_morale( MORALE_CRAVING_SPEED, -25, -20 * in ); } else if( one_in( 10 ) && dice( 2, 80 ) < in ) { u.add_msg_if_player( m_bad, _( "Your hands start shaking... you need a pick-me-up." ) ); u.add_morale( MORALE_CRAVING_SPEED, -25, -20 * in ); u.add_effect( effect_shakes, in * 2_minutes ); } else if( one_in( 50 ) && dice( 2, 100 ) < in ) { u.add_msg_if_player( m_bad, _( "You stop suddenly, feeling bewildered." ) ); u.moves -= 300; } else if( !u.has_effect( effect_hallu ) && one_in( 20 ) && 8 + dice( 2, 80 ) < in ) { u.add_effect( effect_hallu, 6_hours ); } } break; case ADD_COKE: case ADD_CRACK: { static const std::string coke_msg = _( "You feel like you need a bump." ); static const std::string crack_msg = _( "You're shivering, you need some crack." ); const std::string &cur_msg = add.type == ADD_COKE ? coke_msg : crack_msg; const auto morale_type = add.type == ADD_COKE ? MORALE_CRAVING_COCAINE : MORALE_CRAVING_CRACK; u.mod_int_bonus( -1 ); u.mod_per_bonus( -1 ); if( one_in( 900 - 30 * in ) ) { u.add_msg_if_player( m_warning, cur_msg.c_str() ); u.add_morale( morale_type, -20, -15 * in ); } if( dice( 2, 80 ) <= in ) { u.add_msg_if_player( m_warning, cur_msg.c_str() ); u.add_morale( morale_type, -20, -15 * in ); if( u.stim > -150 ) { u.stim -= 3; } } break; } case ADD_MUTAGEN: if( u.has_trait( trait_id( "MUT_JUNKIE" ) ) ) { if( one_in( 600 - 50 * in ) ) { u.add_msg_if_player( m_warning, rng( 0, 6 ) < in ? _( "You so miss the exquisite rainbow of post-humanity." ) : _( "Your body is SOO booorrrring. Just a little sip to liven things up?" ) ); u.add_morale( MORALE_CRAVING_MUTAGEN, -20, -200 ); } if( u.focus_pool > 40 && one_in( 800 - 20 * in ) ) { u.focus_pool -= ( in ); u.add_msg_if_player( m_warning, _( "You daydream what it'd be like if you were *different*. Different is good." ) ); } } else if( in > 5 || one_in( 500 - 15 * in ) ) { u.add_msg_if_player( m_warning, rng( 0, 6 ) < in ? _( "You haven't had any mutagen lately." ) : _( "You could use some new parts..." ) ); u.add_morale( MORALE_CRAVING_MUTAGEN, -5, -50 ); } break; case ADD_MARLOSS_R: marloss_add( u, in, _( "You daydream about luscious pink berries as big as your fist." ) ); break; case ADD_MARLOSS_B: marloss_add( u, in, _( "You daydream about nutty cyan seeds as big as your hand." ) ); break; case ADD_MARLOSS_Y: marloss_add( u, in, _( "You daydream about succulent, pale golden gel, sweet but light." ) ); break; case ADD_NULL: break; } }
void mutation_branch::load( JsonObject &jo, const std::string & ) { mandatory( jo, was_loaded, "id", id ); mandatory( jo, was_loaded, "name", raw_name, translated_string_reader ); mandatory( jo, was_loaded, "description", raw_desc, translated_string_reader ); mandatory( jo, was_loaded, "points", points ); optional( jo, was_loaded, "visibility", visibility, 0 ); optional( jo, was_loaded, "ugliness", ugliness, 0 ); optional( jo, was_loaded, "starting_trait", startingtrait, false ); optional( jo, was_loaded, "mixed_effect", mixed_effect, false ); optional( jo, was_loaded, "active", activated, false ); optional( jo, was_loaded, "starts_active", starts_active, false ); optional( jo, was_loaded, "destroys_gear", destroys_gear, false ); optional( jo, was_loaded, "allow_soft_gear", allow_soft_gear, false ); optional( jo, was_loaded, "cost", cost, 0 ); optional( jo, was_loaded, "time", cooldown, 0 ); optional( jo, was_loaded, "hunger", hunger, false ); optional( jo, was_loaded, "thirst", thirst, false ); optional( jo, was_loaded, "fatigue", fatigue, false ); optional( jo, was_loaded, "valid", valid, true ); optional( jo, was_loaded, "purifiable", purifiable, true ); if( jo.has_object( "spawn_item" ) ) { auto si = jo.get_object( "spawn_item" ); optional( si, was_loaded, "type", spawn_item ); optional( si, was_loaded, "message", raw_spawn_item_message ); } if( jo.has_object( "ranged_mutation" ) ) { auto si = jo.get_object( "ranged_mutation" ); optional( si, was_loaded, "type", ranged_mutation ); optional( si, was_loaded, "message", raw_ranged_mutation_message ); } optional( jo, was_loaded, "initial_ma_styles", initial_ma_styles ); if( jo.has_array( "bodytemp_modifiers" ) ) { auto bodytemp_array = jo.get_array( "bodytemp_modifiers" ); bodytemp_min = bodytemp_array.get_int( 0 ); bodytemp_max = bodytemp_array.get_int( 1 ); } optional( jo, was_loaded, "bodytemp_sleep", bodytemp_sleep, 0 ); optional( jo, was_loaded, "threshold", threshold, false ); optional( jo, was_loaded, "profession", profession, false ); optional( jo, was_loaded, "debug", debug, false ); optional( jo, was_loaded, "player_display", player_display, true ); JsonArray vr = jo.get_array( "vitamin_rates" ); while( vr.has_more() ) { auto pair = vr.next_array(); vitamin_rates.emplace( vitamin_id( pair.get_string( 0 ) ), time_duration::from_turns( pair.get_int( 1 ) ) ); } auto vam = jo.get_array( "vitamins_absorb_multi" ); while( vam.has_more() ) { auto pair = vam.next_array(); std::map<vitamin_id, double> vit; auto vit_array = pair.get_array( 1 ); // fill the inner map with vitamins while( vit_array.has_more() ) { auto vitamins = vit_array.next_array(); vit.emplace( vitamin_id( vitamins.get_string( 0 ) ), vitamins.get_float( 1 ) ); } // assign the inner vitamin map to the material_id key vitamin_absorb_multi.emplace( material_id( pair.get_string( 0 ) ), vit ); } optional( jo, was_loaded, "healing_awake", healing_awake, 0.0f ); optional( jo, was_loaded, "healing_resting", healing_resting, 0.0f ); optional( jo, was_loaded, "hp_modifier", hp_modifier, 0.0f ); optional( jo, was_loaded, "hp_modifier_secondary", hp_modifier_secondary, 0.0f ); optional( jo, was_loaded, "hp_adjustment", hp_adjustment, 0.0f ); optional( jo, was_loaded, "stealth_modifier", stealth_modifier, 0.0f ); optional( jo, was_loaded, "str_modifier", str_modifier, 0.0f ); optional( jo, was_loaded, "dodge_modifier", dodge_modifier, 0.0f ); optional( jo, was_loaded, "speed_modifier", speed_modifier, 1.0f ); optional( jo, was_loaded, "movecost_modifier", movecost_modifier, 1.0f ); optional( jo, was_loaded, "movecost_flatground_modifier", movecost_flatground_modifier, 1.0f ); optional( jo, was_loaded, "movecost_obstacle_modifier", movecost_obstacle_modifier, 1.0f ); optional( jo, was_loaded, "attackcost_modifier", attackcost_modifier, 1.0f ); optional( jo, was_loaded, "max_stamina_modifier", max_stamina_modifier, 1.0f ); optional( jo, was_loaded, "weight_capacity_modifier", weight_capacity_modifier, 1.0f ); optional( jo, was_loaded, "hearing_modifier", hearing_modifier, 1.0f ); optional( jo, was_loaded, "noise_modifier", noise_modifier, 1.0f ); optional( jo, was_loaded, "metabolism_modifier", metabolism_modifier, 0.0f ); optional( jo, was_loaded, "thirst_modifier", thirst_modifier, 0.0f ); optional( jo, was_loaded, "fatigue_modifier", fatigue_modifier, 0.0f ); optional( jo, was_loaded, "fatigue_regen_modifier", fatigue_regen_modifier, 0.0f ); optional( jo, was_loaded, "stamina_regen_modifier", stamina_regen_modifier, 0.0f ); optional( jo, was_loaded, "overmap_sight", overmap_sight, 0.0f ); optional( jo, was_loaded, "overmap_multiplier", overmap_multiplier, 1.0f ); if( jo.has_object( "social_modifiers" ) ) { JsonObject sm = jo.get_object( "social_modifiers" ); social_mods = load_mutation_social_mods( sm ); } load_mutation_mods( jo, "passive_mods", mods ); /* Not currently supported due to inability to save active mutation state load_mutation_mods(jsobj, "active_mods", new_mut.mods); */ optional( jo, was_loaded, "prereqs", prereqs ); optional( jo, was_loaded, "prereqs2", prereqs2 ); optional( jo, was_loaded, "threshreq", threshreq ); optional( jo, was_loaded, "cancels", cancels ); optional( jo, was_loaded, "changes_to", replacements ); optional( jo, was_loaded, "leads_to", additions ); optional( jo, was_loaded, "flags", flags ); optional( jo, was_loaded, "types", types ); auto jsarr = jo.get_array( "category" ); while( jsarr.has_more() ) { std::string s = jsarr.next_string(); category.push_back( s ); mutations_category[s].push_back( trait_id( id ) ); } jsarr = jo.get_array( "wet_protection" ); while( jsarr.has_more() ) { JsonObject jo = jsarr.next_object(); std::string part_id = jo.get_string( "part" ); int ignored = jo.get_int( "ignored", 0 ); int neutral = jo.get_int( "neutral", 0 ); int good = jo.get_int( "good", 0 ); tripoint protect = tripoint( ignored, neutral, good ); protection[get_body_part_token( part_id )] = protect; } jsarr = jo.get_array( "encumbrance_always" ); while( jsarr.has_more() ) { JsonArray jo = jsarr.next_array(); std::string part_id = jo.next_string(); int enc = jo.next_int(); encumbrance_always[get_body_part_token( part_id )] = enc; } jsarr = jo.get_array( "encumbrance_covered" ); while( jsarr.has_more() ) { JsonArray jo = jsarr.next_array(); std::string part_id = jo.next_string(); int enc = jo.next_int(); encumbrance_covered[get_body_part_token( part_id )] = enc; } jsarr = jo.get_array( "restricts_gear" ); while( jsarr.has_more() ) { restricts_gear.insert( get_body_part_token( jsarr.next_string() ) ); } jsarr = jo.get_array( "armor" ); while( jsarr.has_more() ) { JsonObject jo = jsarr.next_object(); auto parts = jo.get_tags( "parts" ); std::set<body_part> bps; for( const std::string &part_string : parts ) { if( part_string == "ALL" ) { // Shorthand, since many mutations protect whole body bps.insert( all_body_parts.begin(), all_body_parts.end() ); } else { bps.insert( get_body_part_token( part_string ) ); } } resistances res = load_resistances_instance( jo ); for( body_part bp : bps ) { armor[ bp ] = res; } } if( jo.has_array( "attacks" ) ) { jsarr = jo.get_array( "attacks" ); while( jsarr.has_more() ) { JsonObject jo = jsarr.next_object(); attacks_granted.emplace_back( load_mutation_attack( jo ) ); } } else if( jo.has_object( "attacks" ) ) { JsonObject attack = jo.get_object( "attacks" ); attacks_granted.emplace_back( load_mutation_attack( attack ) ); } }