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, 20 ); } 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, 50 ); } else if( !u.has_effect( effect_hallu ) && rng( 10, 1600 ) < in ) { u.add_effect( effect_hallu, 3600 ); } 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 ) { add.sated++; } break; case ADD_PKILLER: if( calendar::once_every( 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; 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, 20 + in * 5 ); } 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 * 20 ); } 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, 3600 ); } } 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( "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; } }
/** * Generate textual weather forecast for the specified radio tower. */ std::string weather_forecast( point const &abs_sm_pos ) { std::ostringstream weather_report; // Local conditions const auto cref = overmap_buffer.closest_city( tripoint( abs_sm_pos, 0 ) ); const std::string city_name = cref ? cref.city->name : std::string( _( "middle of nowhere" ) ); // Current time weather_report << string_format( _("The current time is %s Eastern Standard Time. At %s in %s, it was %s. The temperature was %s. "), calendar::turn.print_time().c_str(), calendar::turn.print_time(true).c_str(), city_name.c_str(), weather_data(g->weather).name.c_str(), print_temperature(g->temperature).c_str() ); //weather_report << ", the dewpoint ???, and the relative humidity ???. "; //weather_report << "The wind was <direction> at ? mi/km an hour. "; //weather_report << "The pressure was ??? in/mm and steady/rising/falling."; // Regional conditions (simulated by choosing a random range containing the current conditions). // Adjusted for weather volatility based on how many weather changes are coming up. //weather_report << "Across <region>, skies ranged from <cloudiest> to <clearest>. "; // TODO: Add fake reports for nearby cities // TODO: weather forecast // forecasting periods are divided into 12-hour periods, day (6-18) and night (19-5) // Accumulate percentages for each period of various weather statistics, and report that // (with fuzz) as the weather chances. // int weather_proportions[NUM_WEATHER_TYPES] = {0}; double high = -100.0; double low = 100.0; const tripoint abs_ms_pos = tripoint( sm_to_ms_copy( abs_sm_pos ), 0 ); // TODO wind direction and speed int last_hour = calendar::turn - ( calendar::turn % HOURS(1) ); for(int d = 0; d < 6; d++) { weather_type forecast = WEATHER_NULL; const auto wgen = g->get_cur_weather_gen(); for(calendar i(last_hour + 7200 * d); i < last_hour + 7200 * (d + 1); i += 600) { w_point w = wgen.get_weather( abs_ms_pos, i, g->get_seed() ); forecast = std::max( forecast, wgen.get_weather_conditions( w ) ); high = std::max(high, w.temperature); low = std::min(low, w.temperature); } std::string day; bool started_at_night; calendar c(last_hour + 7200 * d); if(d == 0 && c.is_night()) { day = _("Tonight"); started_at_night = true; } else { day = _("Today"); started_at_night = false; } if(d > 0 && ((started_at_night && !(d % 2)) || (!started_at_night && d % 2))) { day = string_format( pgettext( "Mon Night", "%s Night" ), c.day_of_week().c_str() ); } else { day = c.day_of_week(); } weather_report << string_format( _("%s... %s. Highs of %s. Lows of %s. "), day.c_str(), weather_data(forecast).name.c_str(), print_temperature(high).c_str(), print_temperature(low).c_str() ); } return weather_report.str(); }
void tutorial_game::per_turn() { if( calendar::turn == HOURS( 12 ) ) { add_message( LESSON_INTRO ); add_message( LESSON_INTRO ); } else if( calendar::turn == HOURS( 12 ) + 3 ) { add_message( LESSON_INTRO ); } if( g->light_level( g->u.posz() ) == 1 ) { if( g->u.has_amount( "flashlight", 1 ) ) { add_message( LESSON_DARK ); } else { add_message( LESSON_DARK_NO_FLASH ); } } if( g->u.get_pain() > 0 ) { add_message( LESSON_PAIN ); } if( g->u.recoil >= MIN_RECOIL ) { add_message( LESSON_RECOIL ); } if( !tutorials_seen[LESSON_BUTCHER] ) { for( size_t i = 0; i < g->m.i_at( g->u.posx(), g->u.posy() ).size(); i++ ) { if( g->m.i_at( g->u.posx(), g->u.posy() )[i].is_corpse() ) { add_message( LESSON_BUTCHER ); i = g->m.i_at( g->u.posx(), g->u.posy() ).size(); } } } bool showed_message = false; for( int x = g->u.posx() - 1; x <= g->u.posx() + 1 && !showed_message; x++ ) { for( int y = g->u.posy() - 1; y <= g->u.posy() + 1 && !showed_message; y++ ) { if( g->m.ter( x, y ) == t_door_o ) { add_message( LESSON_OPEN ); showed_message = true; } else if( g->m.ter( x, y ) == t_door_c ) { add_message( LESSON_CLOSE ); showed_message = true; } else if( g->m.ter( x, y ) == t_window ) { add_message( LESSON_SMASH ); showed_message = true; } else if( g->m.furn( x, y ) == f_rack && !g->m.i_at( x, y ).empty() ) { add_message( LESSON_EXAMINE ); showed_message = true; } else if( g->m.ter( x, y ) == t_stairs_down ) { add_message( LESSON_STAIRS ); showed_message = true; } else if( g->m.ter( x, y ) == t_water_sh ) { add_message( LESSON_PICKUP_WATER ); showed_message = true; } } } if( !g->m.i_at( g->u.posx(), g->u.posy() ).empty() ) { add_message( LESSON_PICKUP ); } }
calendar::calendar(int Minute, int Hour, int Day, season_type Season, int Year) { turn_number = MINUTES(Minute) + HOURS(Hour) + DAYS(Day) + Season * to_days<int>( season_length() ) + Year * to_turns<int>( year_length() ); sync(); }
//Quantity is adjusted directly as a side effect of this function MonsterGroupResult MonsterGroupManager::GetResultFromGroup( std::string group_name, int *quantity, int turn ) { int spawn_chance = rng(1, 1000); MonsterGroup group = monsterGroupMap[group_name]; //Our spawn details specify, by default, a single instance of the default monster MonsterGroupResult spawn_details = MonsterGroupResult(group.defaultMonster,1); //If the default monster is too difficult, replace this with "mon_null" if(turn!=-1 && (turn + 900 < MINUTES(STARTING_MINUTES) + HOURS(GetMType(group.defaultMonster)->difficulty))){ spawn_details = MonsterGroupResult("mon_null",0); } bool monster_found = false; // Step through spawn definitions from the monster group until one is found or for (FreqDef_iter it = group.monsters.begin(); it != group.monsters.end() && !monster_found; ++it){ // There's a lot of conditions to work through to see if this spawn definition is valid bool valid_entry = true; // I don't know what turn == -1 is checking for, but it makes monsters always valid for difficulty purposes valid_entry = valid_entry && (turn == -1 || (turn+900) >= (MINUTES(STARTING_MINUTES) + HOURS(GetMType(it->name)->difficulty))); // If we are in classic mode, require the monster type to be either CLASSIC or WILDLIFE if(ACTIVE_WORLD_OPTIONS["CLASSIC_ZOMBIES"]){ valid_entry = valid_entry && (GetMType(it->name)->in_category("CLASSIC") || GetMType(it->name)->in_category("WILDLIFE")); } //Insure that the time is not before the spawn first appears or after it stops appearing valid_entry = valid_entry && (HOURS(it->starts) < calendar::turn.get_turn()); valid_entry = valid_entry && (it->lasts_forever() || HOURS(it->ends) > calendar::turn.get_turn()); std::vector<std::pair<int,int> > valid_times_of_day; bool season_limited = false; bool season_matched = false; //Collect the various spawn conditions, and then insure they are met appropriately for(std::vector<std::string>::iterator condition = it->conditions.begin(); condition != it->conditions.end(); ++condition){ //Collect valid time of day ranges if( (*condition) == "DAY" || (*condition) == "NIGHT" || (*condition) == "DUSK" || (*condition) == "DAWN" ){ int sunset = calendar::turn.sunset().get_turn(); int sunrise = calendar::turn.sunrise().get_turn(); if((*condition) == "DAY"){ valid_times_of_day.push_back( std::make_pair(sunrise,sunset) ); } else if((*condition) == "NIGHT"){ valid_times_of_day.push_back( std::make_pair(sunset,sunrise) ); } else if((*condition) == "DUSK"){ valid_times_of_day.push_back( std::make_pair(sunset-HOURS(1),sunset+HOURS(1)) ); } else if((*condition) == "DAWN"){ valid_times_of_day.push_back( std::make_pair(sunrise-HOURS(1),sunrise+HOURS(1)) ); } } //If we have any seasons listed, we know to limit by season, and if any season matches this season, we are good to spawn if( (*condition) == "SUMMER" || (*condition) == "WINTER" || (*condition) == "SPRING" || (*condition) == "AUTUMN" ){ season_limited = true; if( (calendar::turn.get_season() == SUMMER && (*condition) == "SUMMER") || (calendar::turn.get_season() == WINTER && (*condition) == "WINTER") || (calendar::turn.get_season() == SPRING && (*condition) == "SPRING") || (calendar::turn.get_season() == AUTUMN && (*condition) == "AUTUMN") ){ season_matched = true; } } } //Make sure the current time of day is within one of the valid time ranges for this spawn bool is_valid_time_of_day = false; if(valid_times_of_day.size() < 1){ //Then it can spawn whenever, since no times were defined is_valid_time_of_day = true; } else { //Otherwise, it's valid if it matches any of the times of day for(std::vector<std::pair<int,int> >::iterator time_pair = valid_times_of_day.begin(); time_pair != valid_times_of_day.end(); ++time_pair){ int time_now = calendar::turn.get_turn(); if(time_now > time_pair->first && time_now < time_pair->second){ is_valid_time_of_day = true; } } } if(!is_valid_time_of_day){ valid_entry = false; } //If we are limited by season, make sure we matched a season if(season_limited && !season_matched){ valid_entry = false; } //If the entry was valid, check to see if we actually spawn it if(valid_entry){ //If the monsters frequency is greater than the spawn_chance, select this spawn rule if(it->frequency >= spawn_chance){ if(it->pack_maximum > 1){ spawn_details = MonsterGroupResult(it->name, rng(it->pack_minimum,it->pack_maximum)); } else { spawn_details = MonsterGroupResult(it->name, 1); } //And if a quantity pointer with remaining value was passed, will modify the external value as a side effect //We will reduce it by the spawn rule's cost multiplier if(quantity){ *quantity -= it->cost_multiplier * spawn_details.pack_size; } monster_found = true; //Otherwise, subtract the frequency from spawn result for the next loop around }else{ spawn_chance -= it->frequency; } } } return spawn_details; }
calendar::calendar(int Minute, int Hour, int Day, season_type Season, int Year) { turn_number = MINUTES(Minute) + HOURS(Hour) + DAYS(Day) + Season * season_length() + Year * year_turns(); sync(); }
// Disabling 3d tests for now since 3d sight casting is actually // different (it sees round corners more). if( test_3d ) { INFO( "using 3d casting" ); fov_3d = true; test_all_transformations(); } { INFO( "using 2d casting" ); fov_3d = false; test_all_transformations(); } } }; static constexpr int midnight = HOURS( 0 ); static constexpr int midday = HOURS( 12 ); // The following characters are used in these setups: // ' ' - empty, outdoors // '-' - empty, indoors // 'U' - player, outdoors // 'u' - player, indoors // 'L' - light, indoors // '#' - wall // '=' - window frame TEST_CASE( "vision_daylight", "[shadowcasting][vision]" ) { vision_test_case t { {
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 ); }
constexpr int FULL_HOURS_IN( int n ) { return n / HOURS( 1 ); }