tripoint mission_util::target_om_ter_random( const std::string &omter, int reveal_rad, mission *miss, bool must_see, int range, tripoint loc ) { if( loc == overmap::invalid_tripoint ) { loc = g->u.global_omt_location(); } auto places = overmap_buffer.find_all( loc, omter, range, must_see ); if( places.empty() ) { return g->u.global_omt_location(); } const auto loc_om = overmap_buffer.get_existing_om_global( loc ); std::vector<tripoint> places_om; for( auto &i : places ) { if( loc_om == overmap_buffer.get_existing_om_global( i ) ) { places_om.push_back( i ); } } const tripoint place = random_entry( places_om ); if( reveal_rad >= 0 ) { overmap_buffer.reveal( place, reveal_rad ); } miss->set_target( place ); return place; }
void player::mutate_category( const std::string &cat ) { bool force_bad = one_in(3); bool force_good = false; if (has_trait("ROBUST") && force_bad) { // Robust Genetics gives you a 33% chance for a good mutation, // instead of the 33% chance of a bad one. force_bad = false; force_good = true; } // Pull the category's list for valid mutations std::vector<std::string> valid; valid = mutations_category[cat]; // Remove anything we already have, that we have a child of, or that // goes against our intention of a good/bad mutation for (size_t i = 0; i < valid.size(); i++) { if (!mutation_ok(valid[i], force_good, force_bad)) { valid.erase(valid.begin() + i); i--; } } // if we can't mutate in the category do nothing if (valid.empty()) { return; } mutate_towards( random_entry( valid ) ); }
void talk_function::buy_100_logs( npc &p ) { std::vector<tripoint> places = overmap_buffer.find_all( g->u.global_omt_location(), "ranch_camp_67", 1, false ); if( places.empty() ) { debugmsg( "Couldn't find %s", "ranch_camp_67" ); return; } const auto &cur_om = g->get_cur_om(); std::vector<tripoint> places_om; for( auto &i : places ) { if( &cur_om == overmap_buffer.get_existing_om_global( i ) ) { places_om.push_back( i ); } } const tripoint site = random_entry( places_om ); tinymap bay; bay.load( site.x * 2, site.y * 2, site.z, false ); bay.spawn_item( 7, 15, "log", 100 ); bay.save(); p.add_effect( effect_currently_busy, 7_days ); add_msg( m_good, _( "%s drops the logs off in the garage..." ), p.name ); }
/* Random walking even when we've moved * To simulate zombie stumbling and ineffective movement * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed. * Most of the time (out in the open) this effect is insignificant compared to * the negative effects, but in a hallway it's perfectly even */ void monster::stumble( bool moved ) { // don't stumble every turn. every 3rd turn, or 8th when walking. if( ( moved && !one_in( 8 ) ) || !one_in( 3 ) ) { return; } std::vector<tripoint> valid_stumbles; const bool avoid_water = has_flag( MF_NO_BREATHE ) && !has_flag( MF_SWIMS ) && !has_flag( MF_AQUATIC ); for( int i = -1; i <= 1; i++ ) { for( int j = -1; j <= 1; j++ ) { tripoint dest( posx() + i, posy() + j, posz() ); if( ( i || j ) && can_move_to( dest ) && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !( avoid_water && g->m.has_flag( "SWIMMABLE", dest ) && !g->m.has_flag( "SWIMMABLE", pos3() ) ) && g->critter_at( dest ) == nullptr ) { valid_stumbles.push_back( dest ); } } } if( g->m.has_zlevels() ) { tripoint below( posx(), posy(), posz() - 1 ); tripoint above( posx(), posy(), posz() + 1 ); if( g->m.valid_move( pos(), below, false, true ) && can_move_to( below ) ) { valid_stumbles.push_back( below ); } // More restrictions for moving up // It should happen during "shambling around", but not as actual stumbling if( !moved && one_in( 5 ) && has_flag( MF_FLIES ) && g->m.valid_move( pos(), above, false, true ) && can_move_to( above ) ) { valid_stumbles.push_back( above ); } } if( valid_stumbles.empty() ) { //nowhere to stumble? return; } move_to( random_entry( valid_stumbles ), false ); // Here we have to fix our plans[] list, // acquiring a new path to the previous target. // target == either end of current plan, or the player. int bresenham_slope, junk; if( !plans.empty() ) { if( g->m.sees( pos3(), plans.back(), -1, bresenham_slope, junk ) ) { set_dest( plans.back(), bresenham_slope ); } else if( sees( g->u, bresenham_slope ) ) { set_dest( g->u.pos(), bresenham_slope ); } else { //durr, i'm suddenly calm. what was i doing? plans.clear(); } } }
void mdefense::acidsplash( monster &m, Creature *const source, dealt_projectile_attack const *const proj ) { // Would be useful to have the attack data here, for cutting vs. bashing etc. if( proj != nullptr && proj->dealt_dam.total_damage() <= 0 ) { // Projectile didn't penetrate the target, no acid will splash out of it. return; } if( proj != nullptr && !one_in( 3 ) ) { return; //Less likely for a projectile to deliver enough force } size_t num_drops = rng( 4, 6 ); player const *const foe = dynamic_cast<player *>( source ); if( proj == nullptr && foe != nullptr ) { if( foe->weapon.is_melee( DT_CUT ) || foe->weapon.is_melee( DT_STAB ) ) { num_drops += rng( 3, 4 ); } if( foe->unarmed_attack() ) { damage_instance const burn { DT_ACID, static_cast<float>( rng( 1, 5 ) ) }; if( one_in( 2 ) ) { source->deal_damage( &m, bp_hand_l, burn ); } else { source->deal_damage( &m, bp_hand_r, burn ); } source->add_msg_if_player( m_bad, _( "Acid covering %s burns your hand!" ), m.disp_name().c_str() ); } } tripoint initial_target = source == nullptr ? m.pos() : source->pos(); // Don't splatter directly on the `m`, that doesn't work well auto pts = closest_tripoints_first( 1, initial_target ); pts.erase( std::remove( pts.begin(), pts.end(), m.pos() ), pts.end() ); projectile prj; prj.speed = 10; prj.range = 4; prj.proj_effects.insert( "DRAW_AS_LINE" ); prj.proj_effects.insert( "NO_DAMAGE_SCALING" ); prj.impact.add_damage( DT_ACID, rng( 1, 3 ) ); for( size_t i = 0; i < num_drops; i++ ) { const tripoint &target = random_entry( pts ); projectile_attack( prj, m.pos(), target, { 1200 } ); } if( g->u.sees( m.pos() ) ) { add_msg( m_warning, _( "Acid sprays out of %s as it is hit!" ), m.disp_name().c_str() ); } }
void player::mutate_category( const std::string &cat ) { // Hacky ID comparison is better than separate hardcoded branch used before // @todo: Turn it into the null id if( cat == "MUTCAT_ANY" ) { mutate(); return; } bool force_bad = one_in(3); bool force_good = false; if (has_trait( trait_ROBUST ) && force_bad) { // Robust Genetics gives you a 33% chance for a good mutation, // instead of the 33% chance of a bad one. force_bad = false; force_good = true; } // Pull the category's list for valid mutations std::vector<trait_id> valid; valid = mutations_category[cat]; // Remove anything we already have, that we have a child of, or that // goes against our intention of a good/bad mutation for (size_t i = 0; i < valid.size(); i++) { if (!mutation_ok(valid[i], force_good, force_bad)) { valid.erase(valid.begin() + i); i--; } } // if we can't mutate in the category do nothing if (valid.empty()) { return; } if (mutate_towards(random_entry(valid))) { return; } else { // if mutation failed (errors, post-threshold pick), try again once. mutate_towards(random_entry(valid)); } }
const mtype_id &MonsterGenerator::get_valid_hallucination() const { std::vector<mtype_id> potentials; for( auto &elem : mon_templates ) { if( elem.first != NULL_ID && elem.first != mon_generator ) { potentials.push_back( elem.first ); } } return random_entry( potentials ); }
mtype_id MonsterGenerator::get_valid_hallucination() const { std::vector<mtype_id> potentials; for( const auto &mon : mon_templates->get_all() ) { if( mon.id != NULL_ID && mon.id != mon_generator ) { potentials.push_back( mon.id ); } } return random_entry( potentials ); }
mtype_id MonsterGenerator::get_valid_hallucination() const { std::vector<mtype_id> potentials; for( auto &elem : mon_templates->all_ref() ) { const mtype &mon = elem.second; if( mon.id != NULL_ID && mon.id != mon_generator ) { potentials.push_back( mon.id ); } } return random_entry( potentials ); }
const SpeechBubble &get_speech( const std::string label ) { const std::map<std::string, std::vector<SpeechBubble> >::iterator speech_type = speech.find( label ); if( speech_type == speech.end() || speech_type->second.empty() ) { // Bad lookup, return a fake sound, also warn? return nullSpeech; } return random_entry( speech_type->second ); }
mission_type_id mission_type::get_random_id( const mission_origin origin, const tripoint &p ) { std::vector<mission_type_id> valid; for( auto &t : get_all() ) { if( std::find( t.origins.begin(), t.origins.end(), origin ) == t.origins.end() ) { continue; } if( t.place( p ) ) { valid.push_back( t.id ); } } return random_entry( valid, mission_type_id::NULL_ID() ); }
const Skill* Skill::random_skill_with_tag(const std::string& tag) { std::vector<Skill const*> valid; for (auto const &s : skills) { if (s._tags.count(tag)) { valid.push_back(&s); } } if( valid.empty() ) { debugmsg( "could not find a skill with the %s tag", tag.c_str() ); return &skills.front(); } return random_entry( valid ); }
std::string obscure_message( const std::string &str, std::function<char()> f ) { //~ translators: place some random 1-width characters here in your language if possible, or leave it as is std::string gibberish_narrow = _( "abcdefghijklmnopqrstuvwxyz" ); //~ translators: place some random 2-width characters here in your language if possible, or leave it as is std::string gibberish_wide = _( "に坂索トし荷測のンおク妙免イロコヤ梅棋厚れ表幌" ); std::wstring w_gibberish_narrow = utf8_to_wstr( gibberish_narrow ); std::wstring w_gibberish_wide = utf8_to_wstr( gibberish_wide ); std::wstring w_str = utf8_to_wstr( str ); char transformation[2] = { 0 }; // a trailing NULL terminator is necessary for utf8_width function for( size_t i = 0; i < w_str.size(); ++i ) { transformation[0] = f(); std::string this_char = wstr_to_utf8( std::wstring( 1, w_str[i] ) ); if( transformation[0] == -1 ) { continue; } else if( transformation[0] == 0 ) { if( utf8_width( this_char ) == 1 ) { w_str[i] = random_entry( w_gibberish_narrow ); } else { w_str[i] = random_entry( w_gibberish_wide ); } } else { // Only support the case e.g. replace current character to symbols like # or ? if( utf8_width( transformation ) != 1 ) { debugmsg( "target character isn't narrow" ); } // A 2-width wide character in the original string should be replace by two narrow characters w_str.replace( i, 1, utf8_to_wstr( std::string( utf8_width( this_char ), transformation[0] ) ) ); } } std::string result = wstr_to_utf8( w_str ); if( utf8_width( str ) != utf8_width( result ) ) { debugmsg( "utf8_width differ between original string and obscured string" ); } return result; }
const npc_class_id &npc_class::random_common() { std::list<const npc_class_id *> common_classes; for( const auto &pr : npc_class_factory.get_all() ) { if( pr.common ) { common_classes.push_back( &pr.id ); } } if( common_classes.empty() || one_in( common_classes.size() ) ) { return NC_NONE; } return *random_entry( common_classes ); }
/** * Stumble in a random direction, but with some caveats. */ void monster::stumble( ) { // Only move every 3rd turn. if( !one_in( 3 ) ) { return; } std::vector<tripoint> valid_stumbles; const bool avoid_water = has_flag( MF_NO_BREATHE ) && !has_flag( MF_SWIMS ) && !has_flag( MF_AQUATIC ); for( int i = -1; i <= 1; i++ ) { for( int j = -1; j <= 1; j++ ) { tripoint dest( posx() + i, posy() + j, posz() ); if( ( i || j ) && can_move_to( dest ) && //Stop zombies and other non-breathing monsters wandering INTO water //(Unless they can swim/are aquatic) //But let them wander OUT of water if they are there. !( avoid_water && g->m.has_flag( TFLAG_SWIMMABLE, dest ) && !g->m.has_flag( TFLAG_SWIMMABLE, pos3() ) ) && ( g->critter_at( dest, is_hallucination() ) == nullptr ) ) { valid_stumbles.push_back( dest ); } } } if( g->m.has_zlevels() ) { tripoint below( posx(), posy(), posz() - 1 ); tripoint above( posx(), posy(), posz() + 1 ); if( g->m.valid_move( pos(), below, false, true ) && can_move_to( below ) ) { valid_stumbles.push_back( below ); } // More restrictions for moving up if( one_in( 5 ) && has_flag( MF_FLIES ) && g->m.valid_move( pos(), above, false, true ) && can_move_to( above ) ) { valid_stumbles.push_back( above ); } } if( valid_stumbles.empty() ) { //nowhere to stumble? return; } move_to( random_entry( valid_stumbles ), false ); }
tripoint monster::scent_move() { // @todo Remove when scentmap is 3D if( abs( posz() - g->get_levz() ) > 1 ) { return { -1, -1, INT_MIN }; } std::vector<tripoint> smoves; int bestsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if( has_flag( MF_KEENNOSE ) ) { bestsmell = 1; smell_threshold = 400; } const bool fleeing = is_fleeing( g->u ); if( fleeing ) { bestsmell = g->scent.get( pos() ); } tripoint next( -1, -1, posz() ); if( ( !fleeing && g->scent.get( pos() ) > smell_threshold ) || ( fleeing && bestsmell == 0 ) ) { return next; } const bool can_bash = bash_skill() > 0; for( const auto &dest : g->m.points_in_radius( pos(), 1, 1 ) ) { int smell = g->scent.get( dest ); if( g->m.valid_move( pos(), dest, can_bash, true ) && ( can_move_to( dest ) || ( dest == g->u.pos() ) || ( can_bash && g->m.bash_rating( bash_estimate(), dest ) > 0 ) ) ) { if( ( !fleeing && smell > bestsmell ) || ( fleeing && smell < bestsmell ) ) { smoves.clear(); smoves.push_back( dest ); bestsmell = smell; } else if( ( !fleeing && smell == bestsmell ) || ( fleeing && smell == bestsmell ) ) { smoves.push_back( dest ); } } } return random_entry( smoves, next ); }
void trapfunc::tripwire( Creature *c, const tripoint &p ) { // tiny animals don't trigger tripwires, they just squeeze under it if( c != nullptr && c->get_size() == MS_TINY ) { return; } if( c != nullptr ) { c->add_memorial_log( pgettext( "memorial_male", "Tripped on a tripwire." ), pgettext( "memorial_female", "Tripped on a tripwire." ) ); c->add_msg_player_or_npc( m_bad, _( "You trip over a tripwire!" ), _( "<npcname> trips over a tripwire!" ) ); monster *z = dynamic_cast<monster *>( c ); player *n = dynamic_cast<player *>( c ); if( z != nullptr ) { z->stumble(); if( rng( 0, 10 ) > z->get_dodge() ) { z->apply_damage( nullptr, bp_torso, rng( 1, 4 ) ); } } else if( n != nullptr ) { std::vector<tripoint> valid; tripoint jk = p; for( jk.x = p.x - 1; jk.x <= p.x + 1; jk.x++ ) { for( jk.y = p.y - 1; jk.y <= p.y + 1; jk.y++ ) { if( g->is_empty( jk ) ) { // No monster, NPC, or player, plus valid for movement valid.push_back( jk ); } } } if( !valid.empty() ) { n->setpos( random_entry( valid ) ); } n->moves -= 150; if( rng( 5, 20 ) > n->dex_cur ) { n->hurtall( rng( 1, 4 ), nullptr ); } if( c == &g->u ) { g->update_map( &g->u ); } } c->check_dead_state(); } }
tripoint monster::scent_move() { std::vector<tripoint> smoves; int bestsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if( has_flag( MF_KEENNOSE ) ) { bestsmell = 1; smell_threshold = 400; } const bool fleeing = is_fleeing( g->u ); if( fleeing ) { bestsmell = g->scent( pos() ); } tripoint next( -1, -1, posz() ); if( ( !fleeing && g->scent( pos() ) > smell_threshold ) || ( fleeing && bestsmell == 0 ) ) { return next; } const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES ); for( const auto &dest : g->m.points_in_radius( pos(), 1 ) ) { int smell = g->scent( dest ); int mon = g->mon_at( dest ); if( ( mon == -1 || g->zombie( mon ).friendly != 0 || has_flag( MF_ATTACKMON ) ) && ( can_move_to( dest ) || ( dest == g->u.pos3() ) || ( can_bash && g->m.bash_rating( bash_estimate(), dest ) >= 0 ) ) ) { if( ( !fleeing && smell > bestsmell ) || ( fleeing && smell < bestsmell ) ) { smoves.clear(); smoves.push_back( dest ); bestsmell = smell; } else if( ( !fleeing && smell == bestsmell ) || ( fleeing && smell == bestsmell ) ) { smoves.push_back( dest ); } } } return random_entry( smoves, next ); }
mtype_id MonsterGenerator::get_valid_hallucination() const { return random_entry( hallucination_monsters ); }
void sounds::process_sound_markers( player *p ) { bool is_deaf = p->is_deaf(); const float volume_multiplier = p->hearing_ability(); const int safe_volume = p->worn_with_flag("PARTIAL_DEAF") ? 100 : 9999; const int weather_vol = weather_data( g->weather ).sound_attn; for( const auto &sound_event_pair : sounds_since_last_turn ) { const int volume = std::min(safe_volume, (int)(sound_event_pair.second.volume * volume_multiplier)); const std::string& sfx_id = sound_event_pair.second.id; const std::string& sfx_variant = sound_event_pair.second.variant; const int max_volume = std::max( volume, sound_event_pair.second.volume ); // For deafness checks int dist = rl_dist( p->pos(), sound_event_pair.first ); bool ambient = sound_event_pair.second.ambient; // Too far away, we didn't hear it! if( dist > volume ) { continue; } if( is_deaf ) { // Has to be here as well to work for stacking deafness (loud noises prolong deafness) if( !p->is_immune_effect( effect_deaf ) && rng( ( max_volume - dist ) / 2, ( max_volume - dist ) ) >= 150 ) { // Prolong deafness, but not as much as if it was freshly applied int duration = std::min( 40, ( max_volume - dist - 130 ) / 8 ); p->add_effect( effect_deaf, duration ); if( !p->has_trait( "DEADENED" ) ) { p->add_msg_if_player( m_bad, _( "Your eardrums suddenly ache!" ) ); if( p->get_pain() < 10 ) { p->mod_pain( rng( 0, 2 ) ); } } } // We're deaf, skip rest of processing. continue; } // Player volume meter includes all sounds from their tile and adjacent tiles // TODO: Add noises from vehicle player is in. if( dist <= 1 ) { p->volume = std::max( p->volume, volume ); } // Check for deafness if( !p->is_immune_effect( effect_deaf ) && rng((max_volume - dist) / 2, (max_volume - dist)) >= 150 ) { int duration = (max_volume - dist - 130) / 4; p->add_effect( effect_deaf, duration ); if( p->is_deaf() ) { // Need to check for actual deafness is_deaf = true; sfx::do_hearing_loss( duration ); continue; } } // At this point we are dealing with attention (as opposed to physical effects) // so reduce volume by the amount of ambient noise from the weather. const int mod_vol = ( sound_event_pair.second.volume - weather_vol ) * volume_multiplier; // The noise was drowned out by the surroundings. if( mod_vol - dist < 0 ) { continue; } // See if we need to wake someone up if( p->has_effect( effect_sleep ) ) { if( ( !( p->has_trait( "HEAVYSLEEPER" ) || p->has_trait( "HEAVYSLEEPER2" ) ) && dice( 2, 15 ) < mod_vol - dist ) || ( p->has_trait( "HEAVYSLEEPER" ) && dice( 3, 15 ) < mod_vol - dist ) || ( p->has_trait( "HEAVYSLEEPER2" ) && dice( 6, 15 ) < mod_vol - dist ) ) { //Not kidding about sleep-thru-firefight p->wake_up(); add_msg( m_warning, _( "Something is making noise." ) ); } else { continue; } } const tripoint &pos = sound_event_pair.first; const std::string &description = sound_event_pair.second.description; if( !ambient && ( pos != p->pos() ) && !g->m.pl_sees( pos, dist ) ) { if( p->activity.ignore_trivial != true ) { std::string query; if( description.empty() ) { query = _( "Heard a noise!" ); } else { query = string_format( _( "Heard %s!" ), sound_event_pair.second.description.c_str() ); } if( g->cancel_activity_or_ignore_query( query.c_str() ) ) { p->activity.ignore_trivial = true; for( auto activity : p->backlog ) { activity.ignore_trivial = true; } } } } // Only print a description if it exists if( !description.empty() ) { // If it came from us, don't print a direction if( pos == p->pos() ) { add_msg( _( "You hear %s" ), description.c_str() ); } else { // Else print a direction as well std::string direction = direction_name( direction_from( p->pos(), pos ) ); add_msg( m_warning, _( "From the %s you hear %s" ), direction.c_str(), description.c_str() ); } } // Play the sound effect, if any. if( !sfx_id.empty() ) { // for our sfx API, 100 is "normal" volume, so scale accordingly int heard_volume = sfx::get_heard_volume( pos ); sfx::play_variant_sound( sfx_id, sfx_variant, heard_volume ); //add_msg("Playing sound effect %s, %s, %d", sfx_id.c_str(), sfx_variant.c_str(), heard_volume); } // If Z coord is different, draw even when you can see the source const bool diff_z = pos.z != p->posz(); // Place footstep markers. if( pos == p->pos() || p->sees( pos ) ) { // If we are or can see the source, don't draw a marker. continue; } int err_offset; if( mod_vol / dist < 2 ) { err_offset = 3; } else if( mod_vol / dist < 3 ) { err_offset = 2; } else { err_offset = 1; } // Enumerate the valid points the player *cannot* see. // Unless the source is on a different z-level, then any point is fine std::vector<tripoint> unseen_points; tripoint newp = pos; int &newx = newp.x; int &newy = newp.y; for( newx = pos.x - err_offset; newx <= pos.x + err_offset; newx++ ) { for( newy = pos.y - err_offset; newy <= pos.y + err_offset; newy++ ) { if( diff_z || !p->sees( newp ) ) { unseen_points.emplace_back( newp ); } } } // Then place the sound marker in a random one. if( !unseen_points.empty() ) { sound_markers.emplace( random_entry( unseen_points ), sound_event_pair.second ); } } sounds_since_last_turn.clear(); }
bool leap_actor::call( monster &z ) const { if( !z.can_act() ) { return false; } std::vector<tripoint> options; tripoint target = z.move_target(); float best_float = trig_dist( z.pos(), target ); if( best_float < min_consider_range || best_float > max_consider_range ) { return false; } // We wanted the float for range check // int here will make the jumps more random int best = ( int )best_float; if( !allow_no_target && z.attack_target() == nullptr ) { return false; } for( const tripoint &dest : g->m.points_in_radius( z.pos(), max_range ) ) { if( dest == z.pos() ) { continue; } if( !z.sees( dest ) ) { continue; } if( !g->is_empty( dest ) ) { continue; } int cur_dist = rl_dist( target, dest ); if( cur_dist > best ) { continue; } if( trig_dist( z.pos(), dest ) < min_range ) { continue; } bool blocked_path = false; // check if monster has a clear path to the proposed point std::vector<tripoint> line = g->m.find_clear_path( z.pos(), dest ); for( auto &i : line ) { if( g->m.impassable( i ) ) { blocked_path = true; break; } } if( blocked_path ) { continue; } if( cur_dist < best ) { // Better than any earlier one options.clear(); } options.push_back( dest ); best = cur_dist; } if( options.empty() ) { return false; // Nowhere to leap! } z.moves -= move_cost; const tripoint chosen = random_entry( options ); bool seen = g->u.sees( z ); // We can see them jump... z.setpos( chosen ); seen |= g->u.sees( z ); // ... or we can see them land if( seen ) { add_msg( _( "The %s leaps!" ), z.name().c_str() ); } return true; }
void player::mutate_towards( const std::string &mut ) { if (has_child_flag(mut)) { remove_child_flag(mut); return; } const auto &mdata = mutation_branch::get( mut ); bool has_prereqs = false; bool prereq1 = false; bool prereq2 = false; std::vector<std::string> canceltrait; std::vector<std::string> prereq = mdata.prereqs; std::vector<std::string> prereqs2 = mdata.prereqs2; std::vector<std::string> cancel = mdata.cancels; for (size_t i = 0; i < cancel.size(); i++) { if (!has_trait( cancel[i] )) { cancel.erase(cancel.begin() + i); i--; } else if (has_base_trait( cancel[i] )) { //If we have the trait, but it's a base trait, don't allow it to be removed normally canceltrait.push_back( cancel[i]); cancel.erase(cancel.begin() + i); i--; } } for (size_t i = 0; i < cancel.size(); i++) { if (!cancel.empty()) { std::string removed = cancel[i]; remove_mutation(removed); cancel.erase(cancel.begin() + i); i--; // This checks for cases where one trait knocks out several others // Probably a better way, but gets it Fixed Now--KA101 mutate_towards(mut); return; } } for (size_t i = 0; (!prereq1) && i < prereq.size(); i++) { if (has_trait(prereq[i])) { prereq1 = true; } } for (size_t i = 0; (!prereq2) && i < prereqs2.size(); i++) { if (has_trait(prereqs2[i])) { prereq2 = true; } } if (prereq1 && prereq2) { has_prereqs = true; } if (!has_prereqs && (!prereq.empty() || !prereqs2.empty())) { if (!prereq1 && !prereq.empty()) { mutate_towards( random_entry( prereq ) ); return; } else if (!prereq2 && !prereqs2.empty()) { mutate_towards( random_entry( prereqs2 ) ); return; } } // Check for threshhold mutation, if needed bool threshold = mdata.threshold; bool profession = mdata.profession; bool has_threshreq = false; std::vector<std::string> threshreq = mdata.threshreq; // It shouldn't pick a Threshold anyway--they're supposed to be non-Valid // and aren't categorized--but if it does, just reroll if (threshold) { add_msg(_("You feel something straining deep inside you, yearning to be free...")); mutate(); return; } if (profession) { // Profession picks fail silently mutate(); return; } for (size_t i = 0; !has_threshreq && i < threshreq.size(); i++) { if (has_trait(threshreq[i])) { has_threshreq = true; } } // No crossing The Threshold by simply not having it // Rerolling proved more trouble than it was worth, so deleted if (!has_threshreq && !threshreq.empty()) { add_msg(_("You feel something straining deep inside you, yearning to be free...")); return; } // Check if one of the prereqs that we have TURNS INTO this one std::string replacing = ""; prereq = mdata.prereqs; // Reset it for( auto &elem : prereq ) { if( has_trait( elem ) ) { std::string pre = elem; const auto &p = mutation_branch::get( pre ); for (size_t j = 0; replacing == "" && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing = pre; } } } } // Loop through again for prereqs2 std::string replacing2 = ""; prereq = mdata.prereqs2; // Reset it for( auto &elem : prereq ) { if( has_trait( elem ) ) { std::string pre2 = elem; const auto &p = mutation_branch::get( pre2 ); for (size_t j = 0; replacing2 == "" && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing2 = pre2; } } } } set_mutation(mut); bool mutation_replaced = false; game_message_type rating; if (replacing != "") { const auto &replace_mdata = mutation_branch::get( replacing ); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points < 0) { rating = m_good; } else if(mdata.points - replace_mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } add_msg(rating, _("Your %1$s mutation turns into %2$s!"), replace_mdata.name.c_str(), mdata.name.c_str()); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), replace_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(replacing); mutation_loss_effect(replacing); mutation_effect(mut); mutation_replaced = true; } if (replacing2 != "") { const auto &replace_mdata = mutation_branch::get( replacing2 ); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points < 0) { rating = m_good; } else if(mdata.points - replace_mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } add_msg(rating, _("Your %1$s mutation turns into %2$s!"), replace_mdata.name.c_str(), mdata.name.c_str()); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), replace_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(replacing2); mutation_loss_effect(replacing2); mutation_effect(mut); mutation_replaced = true; } for (size_t i = 0; i < canceltrait.size(); i++) { const auto &cancel_mdata = mutation_branch::get( canceltrait[i] ); if(mdata.mixed_effect || cancel_mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points < cancel_mdata.points) { rating = m_bad; } else if(mdata.points > cancel_mdata.points) { rating = m_good; } else if(mdata.points == cancel_mdata.points) { rating = m_neutral; } else { rating = m_mixed; } // If this new mutation cancels a base trait, remove it and add the mutation at the same time add_msg(rating, _("Your innate %1$s trait turns into %2$s!"), cancel_mdata.name.c_str(), mdata.name.c_str()); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), cancel_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(canceltrait[i]); mutation_loss_effect(canceltrait[i]); mutation_effect(mut); mutation_replaced = true; } if (!mutation_replaced) { if(mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points > 0) { rating = m_good; } else if(mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } add_msg(rating, _("You gain a mutation called %s!"), mdata.name.c_str()); add_memorial_log(pgettext("memorial_male", "Gained the mutation '%s'."), pgettext("memorial_female", "Gained the mutation '%s'."), mdata.name.c_str()); mutation_effect(mut); } set_highest_cat_level(); drench_mut_calc(); }
void mdeath::splatter( monster &z ) { const bool gibbable = !z.type->has_flag( MF_NOGIB ); const int max_hp = std::max( z.get_hp_max(), 1 ); const float overflow_damage = std::max( -z.get_hp(), 0 ); const float corpse_damage = 2.5 * overflow_damage / max_hp; bool pulverized = corpse_damage > 5 && overflow_damage > z.get_hp_max(); // make sure that full splatter happens when this is a set death function, not part of normal for( const auto &deathfunction : z.type->dies ) { if( deathfunction == mdeath::splatter ) { pulverized = true; } } const field_id type_blood = z.bloodType(); const field_id type_gib = z.gibType(); if( gibbable ) { const auto area = g->m.points_in_radius( z.pos(), 1 ); int number_of_gibs = std::min( std::floor( corpse_damage ) - 1, 1 + max_hp / 5.0f ); if( pulverized && z.type->size >= MS_MEDIUM ) { number_of_gibs += rng( 1, 6 ); sfx::play_variant_sound( "mon_death", "zombie_gibbed", sfx::get_heard_volume( z.pos() ) ); } for( int i = 0; i < number_of_gibs; ++i ) { g->m.add_splatter( type_gib, random_entry( area ), rng( 1, i + 1 ) ); g->m.add_splatter( type_blood, random_entry( area ) ); } } // 1% of the weight of the monster is the base, with overflow damage as a multiplier int gibbed_weight = rng( 0, round( to_gram( z.get_weight() ) / 100 * ( overflow_damage / max_hp + 1 ) ) ); // limit gibbing to 15% gibbed_weight = std::min( gibbed_weight, to_gram( z.get_weight() ) * 15 / 100 ); if( pulverized && gibbable ) { float overflow_ratio = overflow_damage / max_hp + 1; int gib_distance = round( rng( 2, 4 ) ); for( const auto &entry : *z.type->harvest ) { // only flesh and bones survive. if( entry.type == "flesh" || entry.type == "bone" ) { // the larger the overflow damage, the less you get const int chunk_amt = entry.mass_ratio / overflow_ratio / 10 * to_gram( z.get_weight() ) / to_gram( ( item::find_type( entry.drop ) )->weight ); scatter_chunks( entry.drop, chunk_amt, z, gib_distance, chunk_amt / ( gib_distance - 1 ) ); gibbed_weight -= entry.mass_ratio / overflow_ratio / 20 * to_gram( z.get_weight() ); } } if( gibbed_weight > 0 ) { scatter_chunks( "ruined_chunks", gibbed_weight / to_gram( ( item::find_type( "ruined_chunks" ) ) ->weight ), z, gib_distance, gibbed_weight / to_gram( ( item::find_type( "ruined_chunks" ) )->weight ) / ( gib_distance + 1 ) ); } // add corpse with gib flag item corpse = item::make_corpse( z.type->id, calendar::turn, z.unique_name ); // Set corpse to damage that aligns with being pulped corpse.set_damage( 4000 ); corpse.set_flag( "GIBBED" ); if( z.has_effect( effect_no_ammo ) ) { corpse.set_var( "no_ammo", "no_ammo" ); } g->m.add_item_or_charges( z.pos(), corpse ); } }
void mdeath::normal( monster &z ) { if( z.no_corpse_quiet ) { return; } if( z.type->in_species( ZOMBIE ) ) { sfx::play_variant_sound( "mon_death", "zombie_death", sfx::get_heard_volume( z.pos() ) ); } if( g->u.sees( z ) ) { //Currently it is possible to get multiple messages that a monster died. add_msg( m_good, _( "The %s dies!" ), z.name().c_str() ); } const int max_hp = std::max( z.get_hp_max(), 1 ); const float overflow_damage = std::max( -z.get_hp(), 0 ); const float corpse_damage = 2.5 * overflow_damage / max_hp; const bool pulverized = corpse_damage > 5 && overflow_damage > z.get_hp_max(); z.bleed(); // leave some blood if we have to if( !pulverized ) { make_mon_corpse( z, int( std::floor( corpse_damage ) ) ); } // Limit chunking to flesh, veggy and insect creatures until other kinds are supported. const std::vector<material_id> gib_mats = {{ material_id( "flesh" ), material_id( "hflesh" ), material_id( "veggy" ), material_id( "iflesh" ), material_id( "bone" ) }}; const bool gibbable = !z.type->has_flag( MF_NOGIB ) && std::any_of( gib_mats.begin(), gib_mats.end(), [&z]( const material_id &gm ) { return z.made_of( gm ); } ); const field_id type_blood = z.bloodType(); const field_id type_gib = z.gibType(); if( gibbable ) { const auto area = g->m.points_in_radius( z.pos(), 1 ); int number_of_gibs = std::min( std::floor( corpse_damage ) - 1, 1 + max_hp / 5.0f ); if( pulverized && z.type->size >= MS_MEDIUM ) { number_of_gibs += rng( 1, 6 ); sfx::play_variant_sound( "mon_death", "zombie_gibbed", sfx::get_heard_volume( z.pos() ) ); } for( int i = 0; i < number_of_gibs; ++i ) { g->m.add_splatter( type_gib, random_entry( area ), rng( 1, i + 1 ) ); g->m.add_splatter( type_blood, random_entry( area ) ); } } const int num_chunks = z.type->get_meat_chunks_count(); if( pulverized && gibbable ) { const itype_id meat = z.type->get_meat_itype(); const item chunk( meat ); for( int i = 0; i < num_chunks; i++ ) { tripoint tarp( z.pos() + point( rng( -3, 3 ), rng( -3, 3 ) ) ); const auto traj = line_to( z.pos(), tarp ); for( size_t j = 0; j < traj.size(); j++ ) { tarp = traj[j]; if( one_in( 2 ) && type_blood != fd_null ) { g->m.add_splatter( type_blood, tarp ); } else { g->m.add_splatter( type_gib, tarp, rng( 1, j + 1 ) ); } if( g->m.impassable( tarp ) ) { g->m.bash( tarp, 3 ); if( g->m.impassable( tarp ) ) { // Target is obstacle, not destroyed by bashing, // stop trajectory in front of it, if this is the first // point (e.g. wall adjacent to monster) , make it invalid. if( j > 0 ) { tarp = traj[j - 1]; } else { tarp = tripoint_min; } break; } } } if( tarp != tripoint_min ) { g->m.add_item_or_charges( tarp, chunk ); } } } }
bool player::mutate_towards( const trait_id &mut ) { if (has_child_flag(mut)) { remove_child_flag(mut); return true; } const mutation_branch &mdata = mut.obj(); bool has_prereqs = false; bool prereq1 = false; bool prereq2 = false; std::vector<trait_id> canceltrait; std::vector<trait_id> prereq = mdata.prereqs; std::vector<trait_id> prereqs2 = mdata.prereqs2; std::vector<trait_id> cancel = mdata.cancels; for (size_t i = 0; i < cancel.size(); i++) { if (!has_trait( cancel[i] )) { cancel.erase(cancel.begin() + i); i--; } else if (has_base_trait( cancel[i] )) { //If we have the trait, but it's a base trait, don't allow it to be removed normally canceltrait.push_back( cancel[i]); cancel.erase(cancel.begin() + i); i--; } } for (size_t i = 0; i < cancel.size(); i++) { if (!cancel.empty()) { trait_id removed = cancel[i]; remove_mutation(removed); cancel.erase(cancel.begin() + i); i--; // This checks for cases where one trait knocks out several others // Probably a better way, but gets it Fixed Now--KA101 return mutate_towards(mut); } } for (size_t i = 0; (!prereq1) && i < prereq.size(); i++) { if (has_trait(prereq[i])) { prereq1 = true; } } for (size_t i = 0; (!prereq2) && i < prereqs2.size(); i++) { if (has_trait(prereqs2[i])) { prereq2 = true; } } if (prereq1 && prereq2) { has_prereqs = true; } if (!has_prereqs && (!prereq.empty() || !prereqs2.empty())) { if (!prereq1 && !prereq.empty()) { return mutate_towards( random_entry( prereq ) ); } else if (!prereq2 && !prereqs2.empty()) { return mutate_towards( random_entry( prereqs2 ) ); } } // Check for threshold mutation, if needed bool threshold = mdata.threshold; bool profession = mdata.profession; bool has_threshreq = false; std::vector<trait_id> threshreq = mdata.threshreq; // It shouldn't pick a Threshold anyway--they're supposed to be non-Valid // and aren't categorized. This can happen if someone makes a threshold mutation into a prerequisite. if (threshold) { add_msg_if_player(_("You feel something straining deep inside you, yearning to be free...")); return false; } if (profession) { // Profession picks fail silently return false; } for (size_t i = 0; !has_threshreq && i < threshreq.size(); i++) { if (has_trait(threshreq[i])) { has_threshreq = true; } } // No crossing The Threshold by simply not having it if (!has_threshreq && !threshreq.empty()) { add_msg_if_player(_("You feel something straining deep inside you, yearning to be free...")); return false; } // Check if one of the prerequisites that we have TURNS INTO this one trait_id replacing = trait_id::NULL_ID(); prereq = mdata.prereqs; // Reset it for( auto &elem : prereq ) { if( has_trait( elem ) ) { trait_id pre = elem; const auto &p = pre.obj(); for (size_t j = 0; !replacing && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing = pre; } } } } // Loop through again for prereqs2 trait_id replacing2 = trait_id::NULL_ID(); prereq = mdata.prereqs2; // Reset it for( auto &elem : prereq ) { if( has_trait( elem ) ) { trait_id pre2 = elem; const auto &p = pre2.obj(); for (size_t j = 0; !replacing2 && j < p.replacements.size(); j++) { if (p.replacements[j] == mut) { replacing2 = pre2; } } } } set_mutation(mut); bool mutation_replaced = false; game_message_type rating; if( replacing ) { const auto &replace_mdata = replacing.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points < 0) { rating = m_good; } else if(mdata.points - replace_mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } // TODO: Limit this to visible mutations // TODO: In case invisible mutation turns into visible or vice versa // print only the visible mutation appearing/disappearing add_msg_player_or_npc(rating, _("Your %1$s mutation turns into %2$s!"), _("<npcname>'s %1$s mutation turns into %2$s!"), replace_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), replace_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(replacing); mutation_loss_effect(replacing); mutation_effect(mut); mutation_replaced = true; } if( replacing2 ) { const auto &replace_mdata = replacing2.obj(); if(mdata.mixed_effect || replace_mdata.mixed_effect) { rating = m_mixed; } else if(replace_mdata.points - mdata.points < 0) { rating = m_good; } else if(mdata.points - replace_mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } add_msg_player_or_npc(rating, _("Your %1$s mutation turns into %2$s!"), _("<npcname>'s %1$s mutation turns into %2$s!"), replace_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), replace_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(replacing2); mutation_loss_effect(replacing2); mutation_effect(mut); mutation_replaced = true; } for (size_t i = 0; i < canceltrait.size(); i++) { const auto &cancel_mdata = canceltrait[i].obj(); if(mdata.mixed_effect || cancel_mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points < cancel_mdata.points) { rating = m_bad; } else if(mdata.points > cancel_mdata.points) { rating = m_good; } else if(mdata.points == cancel_mdata.points) { rating = m_neutral; } else { rating = m_mixed; } // If this new mutation cancels a base trait, remove it and add the mutation at the same time add_msg_player_or_npc( rating, _("Your innate %1$s trait turns into %2$s!"), _("<npcname>'s innate %1$s trait turns into %2$s!"), cancel_mdata.name.c_str(), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "'%s' mutation turned into '%s'"), pgettext("memorial_female", "'%s' mutation turned into '%s'"), cancel_mdata.name.c_str(), mdata.name.c_str()); unset_mutation(canceltrait[i]); mutation_loss_effect(canceltrait[i]); mutation_effect(mut); mutation_replaced = true; } if (!mutation_replaced) { if(mdata.mixed_effect) { rating = m_mixed; } else if(mdata.points > 0) { rating = m_good; } else if(mdata.points < 0) { rating = m_bad; } else { rating = m_neutral; } // TODO: Limit to visible mutations add_msg_player_or_npc( rating, _("You gain a mutation called %s!"), _("<npcname> gains a mutation called %s!"), mdata.name.c_str() ); add_memorial_log(pgettext("memorial_male", "Gained the mutation '%s'."), pgettext("memorial_female", "Gained the mutation '%s'."), mdata.name.c_str()); mutation_effect(mut); } set_highest_cat_level(); drench_mut_calc(); return true; }
void event::actualize() { switch( type ) { case EVENT_HELP: debugmsg("Currently disabled while NPC and monster factions are being rewritten."); break; case EVENT_ROBOT_ATTACK: { const auto u_pos = g->u.global_sm_location(); if (rl_dist(u_pos, map_point) <= 4) { const mtype_id& robot_type = one_in( 2 ) ? mon_copbot : mon_riotbot; g->u.add_memorial_log( pgettext("memorial_male", "Became wanted by the police!"), pgettext("memorial_female", "Became wanted by the police!")); int robx = (u_pos.x > map_point.x ? 0 - SEEX * 2 : SEEX * 4); int roby = (u_pos.y > map_point.y ? 0 - SEEY * 2 : SEEY * 4); g->summon_mon(robot_type, tripoint(robx, roby, g->u.posz())); } } break; case EVENT_SPAWN_WYRMS: { if (g->get_levz() >= 0) { return; } g->u.add_memorial_log(pgettext("memorial_male", "Drew the attention of more dark wyrms!"), pgettext("memorial_female", "Drew the attention of more dark wyrms!")); int num_wyrms = rng(1, 4); for (int i = 0; i < num_wyrms; i++) { int tries = 0; tripoint monp = g->u.pos(); do { monp.x = rng(0, SEEX * MAPSIZE); monp.y = rng(0, SEEY * MAPSIZE); tries++; } while (tries < 10 && !g->is_empty(monp) && rl_dist(g->u.pos(), monp) <= 2); if (tries < 10) { g->m.ter_set(monp, t_rock_floor); g->summon_mon(mon_dark_wyrm, monp); } } // You could drop the flag, you know. if (g->u.has_amount("petrified_eye", 1)) { sounds::sound(g->u.pos(), 60, ""); if (!g->u.is_deaf()) { add_msg(_("The eye you're carrying lets out a tortured scream!")); g->u.add_morale(MORALE_SCREAM, -15, 0, 30_minutes, 5_turns); } } if (!one_in(25)) { // They just keep coming! g->events.add( EVENT_SPAWN_WYRMS, calendar::turn + rng( 15_turns, 25_turns ) ); } } break; case EVENT_AMIGARA: { g->u.add_memorial_log(pgettext("memorial_male", "Angered a group of amigara horrors!"), pgettext("memorial_female", "Angered a group of amigara horrors!")); int num_horrors = rng(3, 5); int faultx = -1, faulty = -1; bool horizontal = false; for (int x = 0; x < SEEX * MAPSIZE && faultx == -1; x++) { for (int y = 0; y < SEEY * MAPSIZE && faulty == -1; y++) { if (g->m.ter(x, y) == t_fault) { faultx = x; faulty = y; horizontal = (g->m.ter(x - 1, y) == t_fault || g->m.ter(x + 1, y) == t_fault); } } } for (int i = 0; i < num_horrors; i++) { int tries = 0; int monx = -1, mony = -1; do { if (horizontal) { monx = rng(faultx, faultx + 2 * SEEX - 8); for (int n = -1; n <= 1; n++) { if (g->m.ter(monx, faulty + n) == t_rock_floor) { mony = faulty + n; } } } else { // Vertical fault mony = rng(faulty, faulty + 2 * SEEY - 8); for (int n = -1; n <= 1; n++) { if (g->m.ter(faultx + n, mony) == t_rock_floor) { monx = faultx + n; } } } tries++; } while ((monx == -1 || mony == -1 || !g->is_empty({monx, mony, g->u.posz()})) && tries < 10); if (tries < 10) { g->summon_mon(mon_amigara_horror, tripoint(monx, mony, g->u.posz())); } } } break; case EVENT_ROOTS_DIE: g->u.add_memorial_log(pgettext("memorial_male", "Destroyed a triffid grove."), pgettext("memorial_female", "Destroyed a triffid grove.")); for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_root_wall && one_in(3)) g->m.ter_set(x, y, t_underbrush); } } break; case EVENT_TEMPLE_OPEN: { g->u.add_memorial_log(pgettext("memorial_male", "Opened a strange temple."), pgettext("memorial_female", "Opened a strange temple.")); bool saw_grate = false; for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_grate) { g->m.ter_set(x, y, t_stairs_down); if (!saw_grate && g->u.sees(tripoint(x, y,g->get_levz()))) saw_grate = true; } } } if (saw_grate) add_msg(_("The nearby grates open to reveal a staircase!")); } break; case EVENT_TEMPLE_FLOOD: { bool flooded = false; ter_id flood_buf[SEEX*MAPSIZE][SEEY*MAPSIZE]; for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) flood_buf[x][y] = g->m.ter(x, y); } for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_water_sh) { bool deepen = false; for (int wx = x - 1; wx <= x + 1 && !deepen; wx++) { for (int wy = y - 1; wy <= y + 1 && !deepen; wy++) { if (g->m.ter(wx, wy) == t_water_dp) deepen = true; } } if (deepen) { flood_buf[x][y] = t_water_dp; flooded = true; } } else if (g->m.ter(x, y) == t_rock_floor) { bool flood = false; for (int wx = x - 1; wx <= x + 1 && !flood; wx++) { for (int wy = y - 1; wy <= y + 1 && !flood; wy++) { if (g->m.ter(wx, wy) == t_water_dp || g->m.ter(wx, wy) == t_water_sh) flood = true; } } if (flood) { flood_buf[x][y] = t_water_sh; flooded = true; } } } } if (!flooded) return; // We finished flooding the entire chamber! // Check if we should print a message if (flood_buf[g->u.posx()][g->u.posy()] != g->m.ter(g->u.posx(), g->u.posy())) { if (flood_buf[g->u.posx()][g->u.posy()] == t_water_sh) { add_msg(m_warning, _("Water quickly floods up to your knees.")); g->u.add_memorial_log(pgettext("memorial_male", "Water level reached knees."), pgettext("memorial_female", "Water level reached knees.")); } else { // Must be deep water! add_msg(m_warning, _("Water fills nearly to the ceiling!")); g->u.add_memorial_log(pgettext("memorial_male", "Water level reached the ceiling."), pgettext("memorial_female", "Water level reached the ceiling.")); g->plswim(g->u.pos()); } } // flood_buf is filled with correct tiles; now copy them back to g->m for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) g->m.ter_set(x, y, flood_buf[x][y]); } g->events.add( EVENT_TEMPLE_FLOOD, calendar::turn + rng( 2_turns, 3_turns ) ); } break; case EVENT_TEMPLE_SPAWN: { static const std::array<mtype_id, 4> temple_monsters = { { mon_sewer_snake, mon_dermatik, mon_spider_widow_giant, mon_spider_cellar_giant } }; const mtype_id &montype = random_entry( temple_monsters ); int tries = 0, x, y; do { x = rng(g->u.posx() - 5, g->u.posx() + 5); y = rng(g->u.posy() - 5, g->u.posy() + 5); tries++; } while (tries < 20 && !g->is_empty({x, y, g->u.posz()}) && rl_dist(x, y, g->u.posx(), g->u.posy()) <= 2); if (tries < 20) { g->summon_mon(montype, tripoint(x, y, g->u.posz())); } } break; default: break; // Nothing happens for other events } }
void player::mutate() { bool force_bad = one_in(3); bool force_good = false; if (has_trait( trait_ROBUST ) && force_bad) { // Robust Genetics gives you a 33% chance for a good mutation, // instead of the 33% chance of a bad one. force_bad = false; force_good = true; } // Determine the highest mutation category std::string cat = get_highest_category(); // See if we should upgrade/extend an existing mutation... std::vector<trait_id> upgrades; // ... or remove one that is not in our highest category std::vector<trait_id> downgrades; // For each mutation... for( auto &traits_iter : mutation_branch::get_all() ) { const auto &base_mutation = traits_iter.first; const auto &base_mdata = traits_iter.second; bool thresh_save = base_mdata.threshold; bool prof_save = base_mdata.profession; bool purify_save = base_mdata.purifiable; // ...that we have... if (has_trait(base_mutation)) { // ...consider the mutations that replace it. for( auto &mutation : base_mdata.replacements ) { bool valid_ok = mutation->valid; if ( (mutation_ok(mutation, force_good, force_bad)) && (valid_ok) ) { upgrades.push_back(mutation); } } // ...consider the mutations that add to it. for( auto &mutation : base_mdata.additions ) { bool valid_ok = mutation->valid; if ( (mutation_ok(mutation, force_good, force_bad)) && (valid_ok) ) { upgrades.push_back(mutation); } } // ...consider whether its in our highest category if( has_trait(base_mutation) && !has_base_trait(base_mutation) ) { // Starting traits don't count toward categories std::vector<trait_id> group = mutations_category[cat]; bool in_cat = false; for( auto &elem : group ) { if( elem == base_mutation ) { in_cat = true; break; } } // mark for removal // no removing Thresholds/Professions this way! if(!in_cat && !thresh_save && !prof_save) { // non-purifiable stuff should be pretty tenacious // category-enforcement only targets it 25% of the time // (purify_save defaults true, = false for non-purifiable) if( purify_save || ( one_in( 4 ) && !purify_save ) ) { downgrades.push_back(base_mutation); } } } } } // Preliminary round to either upgrade or remove existing mutations if(one_in(2)) { if (!upgrades.empty()) { // (upgrade count) chances to pick an upgrade, 4 chances to pick something else. size_t roll = rng(0, upgrades.size() + 4); if (roll < upgrades.size()) { // We got a valid upgrade index, so use it and return. mutate_towards(upgrades[roll]); return; } } } else { // Remove existing mutations that don't fit into our category if( !downgrades.empty() && !cat.empty() ) { size_t roll = rng(0, downgrades.size() + 4); if (roll < downgrades.size()) { remove_mutation(downgrades[roll]); return; } } } std::vector<trait_id> valid; // Valid mutations bool first_pass = true; do { // If we tried once with a non-NULL category, and couldn't find anything valid // there, try again with MUTCAT_NULL if (!first_pass) { cat.clear(); } if( cat.empty() ) { // Pull the full list for( auto &traits_iter : mutation_branch::get_all() ) { if( traits_iter.second.valid ) { valid.push_back( traits_iter.first ); } } } else { // Pull the category's list valid = mutations_category[cat]; } // Remove anything we already have, that we have a child of, or that // goes against our intention of a good/bad mutation for (size_t i = 0; i < valid.size(); i++) { if ( (!mutation_ok(valid[i], force_good, force_bad)) || (!valid[i]->valid) ) { valid.erase(valid.begin() + i); i--; } } if (valid.empty()) { // So we won't repeat endlessly first_pass = false; } } while ( valid.empty() && !cat.empty() ); if (valid.empty()) { // Couldn't find anything at all! return; } if (mutate_towards(random_entry(valid))) { return; } else { // if mutation failed (errors, post-threshold pick), try again once. mutate_towards(random_entry(valid)); } }
void trapfunc::sinkhole( Creature *c, const tripoint &p ) { player *pl = dynamic_cast<player*>( c ); if( pl == nullptr ) { // TODO: Handle monsters return; } const auto random_neighbor = []( tripoint center ) { center.x += rng( -1, 1 ); center.y += rng( -1, 1 ); return center; }; const auto safety_roll = [&]( const std::string &itemname, const int diff ) { const int roll = rng( pl->skillLevel( skill_throw ), pl->skillLevel( skill_throw ) + pl->str_cur + pl->dex_cur ); if( roll < diff ) { pl->add_msg_if_player( m_bad, _( "You fail to attach it..." ) ); pl->use_amount( itemname, 1 ); g->m.spawn_item( random_neighbor( pl->pos() ), itemname ); return false; } std::vector<tripoint> safe; tripoint tmp = pl->pos(); int &i = tmp.x; int &j = tmp.y; for( i = pl->posx() - 1; i <= pl->posx() + 1; i++ ) { for( j = pl->posy() - 1; j <= pl->posy() + 1; j++ ) { if( g->m.move_cost( tmp ) > 0 && g->m.tr_at( tmp ).loadid != tr_pit ) { safe.push_back( tmp ); } } } if( safe.empty() ) { pl->add_msg_if_player( m_bad, _( "There's nowhere to pull yourself to, and you sink!" ) ); pl->use_amount( itemname, 1 ); g->m.spawn_item( random_neighbor( pl->pos() ), itemname ); return false; } else { pl->add_msg_player_or_npc( m_good, _( "You pull yourself to safety!" ), _( "<npcname> steps on a sinkhole, but manages to pull themselves to safety." ) ); pl->setpos( random_entry( safe ) ); if( pl == &g->u ) { g->update_map( &g->u ); } return true; } }; pl->add_memorial_log( pgettext( "memorial_male", "Stepped into a sinkhole." ), pgettext( "memorial_female", "Stepped into a sinkhole." ) ); bool success = false; if( query_for_item( pl, "grapnel", _( "You step into a sinkhole! Throw your grappling hook out to try to catch something?" ) ) ) { success = safety_roll( "grapnel", 6 ); } else if( query_for_item( pl, "bullwhip", _( "You step into a sinkhole! Throw your whip out to try and snag something?" ) ) ) { success = safety_roll( "bullwhip", 8 ); } else if( query_for_item( pl, "rope_30", _( "You step into a sinkhole! Throw your rope out to try to catch something?" ) ) ) { success = safety_roll( "rope_30", 12 ); } pl->add_msg_player_or_npc( m_warning, _( "The sinkhole collapses!" ), _( "A sinkhole under <npcname> collapses!" ) ); g->m.remove_trap( p ); g->m.ter_set( p, t_pit ); if( success ) { return; } pl->moves -= 100; pl->add_msg_player_or_npc( m_bad, _( "You fall into the sinkhole!" ), _( "<npcname> falls into a sinkhole!" ) ); pit( c, p ); }
void mdeath::splatter( monster &z ) { // Limit chunking to flesh, veggy and insect creatures until other kinds are supported. const std::vector<material_id> gib_mats = {{ material_id( "flesh" ), material_id( "hflesh" ), material_id( "veggy" ), material_id( "iflesh" ), material_id( "bone" ) } }; const bool gibbable = !z.type->has_flag( MF_NOGIB ) && std::any_of( gib_mats.begin(), gib_mats.end(), [&z]( const material_id & gm ) { return z.made_of( gm ); } ); const int max_hp = std::max( z.get_hp_max(), 1 ); const float overflow_damage = std::max( -z.get_hp(), 0 ); const float corpse_damage = 2.5 * overflow_damage / max_hp; bool pulverized = corpse_damage > 5 && overflow_damage > z.get_hp_max(); // make sure that full splatter happens when this is a set death function, not part of normal for( const auto &deathfunction : z.type->dies ) { if( deathfunction == mdeath::splatter ) { pulverized = true; } } const field_id type_blood = z.bloodType(); const field_id type_gib = z.gibType(); if( gibbable ) { const auto area = g->m.points_in_radius( z.pos(), 1 ); int number_of_gibs = std::min( std::floor( corpse_damage ) - 1, 1 + max_hp / 5.0f ); if( pulverized && z.type->size >= MS_MEDIUM ) { number_of_gibs += rng( 1, 6 ); sfx::play_variant_sound( "mon_death", "zombie_gibbed", sfx::get_heard_volume( z.pos() ) ); } for( int i = 0; i < number_of_gibs; ++i ) { g->m.add_splatter( type_gib, random_entry( area ), rng( 1, i + 1 ) ); g->m.add_splatter( type_blood, random_entry( area ) ); } } int num_chunks = rng( 0, z.type->get_meat_chunks_count() / 4 ); num_chunks = std::min( num_chunks, 10 ); if( pulverized && gibbable ) { const itype_id meat = z.type->get_meat_itype(); const item chunk( meat ); for( int i = 0; i < num_chunks; i++ ) { bool drop_chunks = true; tripoint tarp( z.pos() + point( rng( -3, 3 ), rng( -3, 3 ) ) ); const auto traj = line_to( z.pos(), tarp ); for( size_t j = 0; j < traj.size(); j++ ) { tarp = traj[j]; if( one_in( 2 ) && type_blood != fd_null ) { g->m.add_splatter( type_blood, tarp ); } else { g->m.add_splatter( type_gib, tarp, rng( 1, j + 1 ) ); } if( g->m.impassable( tarp ) ) { g->m.bash( tarp, 3 ); if( g->m.impassable( tarp ) ) { // Target is obstacle, not destroyed by bashing, // stop trajectory in front of it, if this is the first // point (e.g. wall adjacent to monster), don't drop anything on it if( j > 0 ) { tarp = traj[j - 1]; } else { drop_chunks = false; } break; } } } if( drop_chunks ) { g->m.add_item_or_charges( tarp, chunk ); } } } }
tripoint overmapbuffer::find_random( const tripoint &origin, const std::string &type, int dist, bool must_be_seen ) { return random_entry( find_all( origin, type, dist, must_be_seen ), overmap::invalid_tripoint ); }