void start_location::surround_with_monsters( const tripoint &omtstart, const mongroup_id &type, float expected_points ) const { for( int x_offset = -1; x_offset <= 1; x_offset++ ) { for( int y_offset = -1; y_offset <= 1; y_offset++ ) { if( x_offset != 0 || y_offset != 0 ) { add_monsters( omtstart + point( x_offset, y_offset ), type, roll_remainder( expected_points / 8.0f ) ); } } } }
/** * Thunder. * Flavor messages. Very wet. */ void weather_effect::thunder() { very_wet(); if( !g->u.has_effect( effect_sleep ) && !g->u.is_deaf() && one_in( THUNDER_CHANCE ) ) { if( g->get_levz() >= 0 ) { add_msg( _( "You hear a distant rumble of thunder." ) ); sfx::play_variant_sound( "environment", "thunder_far", 80, rng( 0, 359 ) ); } else if( one_in( std::max( roll_remainder( 2.0f * g->get_levz() / g->u.mutation_value( "hearing_modifier" ) ), 1 ) ) ) { add_msg( _( "You hear a rumble of thunder from above." ) ); sfx::play_variant_sound( "environment", "thunder_far", ( 80 * g->u.mutation_value( "hearing_modifier" ) ), rng( 0, 359 ) ); } } }
stomach_absorb_rates stomach_contents::get_absorb_rates( bool stomach, needs_rates metabolic_rates ) { stomach_absorb_rates rates; if( !stomach ) { rates.min_kcal = roll_remainder( metabolic_rates.kcal / 24.0 * metabolic_rates.hunger ); rates.percent_kcal = 0.05f * metabolic_rates.hunger; rates.min_vitamin_default = round( 100.0 / 24.0 * metabolic_rates.hunger ); rates.percent_vitamin_default = 0.05f * metabolic_rates.hunger; } else { rates.min_kcal = 0; rates.percent_kcal = 0.0f; rates.min_vitamin_default = 0; rates.percent_vitamin_default = 0.0f; } return rates; }
void Creature::deal_damage_handle_type( const damage_unit &du, body_part bp, int &damage, int &pain ) { // Handles ACIDPROOF, electric immunity etc. if( is_immune_damage( du.type ) ) { return; } // Apply damage multiplier from skill, critical hits or grazes after all other modifications. const int adjusted_damage = du.amount * du.damage_multiplier; if( adjusted_damage <= 0 ) { return; } float div = 4.0f; switch( du.type ) { case DT_BASH: // Bashing damage is less painful div = 5.0f; break; case DT_HEAT: // heat damage sets us on fire sometimes if( rng( 0, 100 ) < adjusted_damage ) { add_effect( effect_onfire, rng( 1_turns, 3_turns ), bp ); } break; case DT_ELECTRIC: // Electrical damage adds a major speed/dex debuff add_effect( effect_zapped, 1_turns * std::max( adjusted_damage, 2 ) ); break; case DT_ACID: // Acid damage and acid burns are more painful div = 3.0f; break; default: break; } damage += adjusted_damage; pain += roll_remainder( adjusted_damage / div ); }
void SkillLevel::train( int amount, bool skip_scaling ) { // Working off rust to regain levels goes twice as fast as reaching levels in the first place if( _level < _highestLevel ) { amount *= 2; } if( skip_scaling ) { _exercise += amount; } else { const double scaling = get_option<float>( "SKILL_TRAINING_SPEED" ); if( scaling > 0.0 ) { _exercise += roll_remainder( amount * scaling ); } } if( _exercise >= 100 * ( _level + 1 ) * ( _level + 1 ) ) { _exercise = 0; ++_level; if( _level > _highestLevel ) { _highestLevel = _level; } } }
int divide_roll_remainder( double dividend, double divisor ) { const double div = dividend / divisor; return roll_remainder( div ); }
dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tripoint &source, const tripoint &target_arg, dispersion_sources dispersion, Creature *origin, const vehicle *in_veh ) { const bool do_animation = get_option<bool>( "ANIMATIONS" ); double range = rl_dist( source, target_arg ); Creature *target_critter = g->critter_at( target_arg ); double target_size = target_critter != nullptr ? target_critter->ranged_target_size() : g->m.ranged_target_size( target_arg ); projectile_attack_aim aim = projectile_attack_roll( dispersion, range, target_size ); // TODO: move to-hit roll back in here dealt_projectile_attack attack { proj_arg, nullptr, dealt_damage_instance(), source, aim.missed_by }; if( source == target_arg ) { // No suicidal shots debugmsg( "Projectile_attack targeted own square." ); return attack; } projectile &proj = attack.proj; const auto &proj_effects = proj.proj_effects; const bool stream = proj_effects.count( "STREAM" ) > 0 || proj_effects.count( "STREAM_BIG" ) > 0 || proj_effects.count( "JET" ) > 0; const char bullet = stream ? '#' : '*'; const bool no_item_damage = proj_effects.count( "NO_ITEM_DAMAGE" ) > 0; const bool do_draw_line = proj_effects.count( "DRAW_AS_LINE" ) > 0; const bool null_source = proj_effects.count( "NULL_SOURCE" ) > 0; // Determines whether it can penetrate obstacles const bool is_bullet = proj_arg.speed >= 200 && std::any_of( proj_arg.impact.damage_units.begin(), proj_arg.impact.damage_units.end(), []( const damage_unit & dam ) { return dam.type == DT_CUT; } ); // If we were targetting a tile rather than a monster, don't overshoot // Unless the target was a wall, then we are aiming high enough to overshoot const bool no_overshoot = proj_effects.count( "NO_OVERSHOOT" ) || ( g->critter_at( target_arg ) == nullptr && g->m.passable( target_arg ) ); double extend_to_range = no_overshoot ? range : proj_arg.range; tripoint target = target_arg; std::vector<tripoint> trajectory; if( aim.missed_by_tiles >= 1.0 ) { // We missed enough to target a different tile double dx = target_arg.x - source.x; double dy = target_arg.y - source.y; double rad = atan2( dy, dx ); // cap wild misses at +/- 30 degrees rad += ( one_in( 2 ) ? 1 : -1 ) * std::min( ARCMIN( aim.dispersion ), DEGREES( 30 ) ); // @todo: This should also represent the miss on z axis const int offset = std::min<int>( range, sqrtf( aim.missed_by_tiles ) ); int new_range = no_overshoot ? range + rng( -offset, offset ) : rng( range - offset, proj_arg.range ); new_range = std::max( new_range, 1 ); target.x = source.x + roll_remainder( new_range * cos( rad ) ); target.y = source.y + roll_remainder( new_range * sin( rad ) ); if( target == source ) { target.x = source.x + sgn( dx ); target.y = source.y + sgn( dy ); } // Don't extend range further, miss here can mean hitting the ground near the target range = rl_dist( source, target ); extend_to_range = range; sfx::play_variant_sound( "bullet_hit", "hit_wall", sfx::get_heard_volume( target ), sfx::get_heard_angle( target ) ); // TODO: Z dispersion // If we missed, just draw a straight line. trajectory = line_to( source, target ); } else { // Go around obstacles a little if we're on target. trajectory = g->m.find_clear_path( source, target ); } add_msg( m_debug, "missed_by_tiles: %.2f; missed_by: %.2f; target (orig/hit): %d,%d,%d/%d,%d,%d", aim.missed_by_tiles, aim.missed_by, target_arg.x, target_arg.y, target_arg.z, target.x, target.y, target.z ); // Trace the trajectory, doing damage in order tripoint &tp = attack.end_point; tripoint prev_point = source; trajectory.insert( trajectory.begin(), source ); // Add the first point to the trajectory static emit_id muzzle_smoke( "emit_smoke_plume" ); if( proj_effects.count( "MUZZLE_SMOKE" ) ) { g->m.emit_field( trajectory.front(), muzzle_smoke ); } if( !no_overshoot && range < extend_to_range ) { // Continue line is very "stiff" when the original range is short // @todo: Make it use a more distant point for more realistic extended lines std::vector<tripoint> trajectory_extension = continue_line( trajectory, extend_to_range - range ); trajectory.reserve( trajectory.size() + trajectory_extension.size() ); trajectory.insert( trajectory.end(), trajectory_extension.begin(), trajectory_extension.end() ); } // Range can be 0 size_t traj_len = trajectory.size(); while( traj_len > 0 && rl_dist( source, trajectory[traj_len - 1] ) > proj_arg.range ) { --traj_len; } const float projectile_skip_multiplier = 0.1; // Randomize the skip so that bursts look nicer int projectile_skip_calculation = range * projectile_skip_multiplier; int projectile_skip_current_frame = rng( 0, projectile_skip_calculation ); bool has_momentum = true; for( size_t i = 1; i < traj_len && ( has_momentum || stream ); ++i ) { prev_point = tp; tp = trajectory[i]; if( ( tp.z > prev_point.z && g->m.has_floor( tp ) ) || ( tp.z < prev_point.z && g->m.has_floor( prev_point ) ) ) { // Currently strictly no shooting through floor // TODO: Bash the floor tp = prev_point; traj_len = --i; break; } // Drawing the bullet uses player g->u, and not player p, because it's drawn // relative to YOUR position, which may not be the gunman's position. if( do_animation && !do_draw_line ) { // TODO: Make this draw thrown item/launched grenade/arrow if( projectile_skip_current_frame >= projectile_skip_calculation ) { g->draw_bullet( tp, ( int )i, trajectory, bullet ); projectile_skip_current_frame = 0; // If we missed recalculate the skip factor so they spread out. projectile_skip_calculation = std::max( ( size_t )range, i ) * projectile_skip_multiplier; } else { projectile_skip_current_frame++; } } if( in_veh != nullptr ) { int part; vehicle *other = g->m.veh_at( tp, part ); if( in_veh == other && other->is_inside( part ) ) { continue; // Turret is on the roof and can't hit anything inside } } Creature *critter = g->critter_at( tp ); if( origin == critter ) { // No hitting self with "weird" attacks. critter = nullptr; } monster *mon = dynamic_cast<monster *>( critter ); // ignore non-point-blank digging targets (since they are underground) if( mon != nullptr && mon->digging() && rl_dist( source, tp ) > 1 ) { critter = nullptr; mon = nullptr; } // Reset hit critter from the last iteration attack.hit_critter = nullptr; // If we shot us a monster... // TODO: add size effects to accuracy // If there's a monster in the path of our bullet, and either our aim was true, // OR it's not the monster we were aiming at and we were lucky enough to hit it double cur_missed_by = aim.missed_by; // unintentional hit on something other than our actual target // don't re-roll for the actual target, we already decided on a missed_by value for that // at the start, misses should stay as misses if( critter != nullptr && tp != target_arg ) { // Unintentional hit cur_missed_by = std::max( rng_float( 0.1, 1.5 - aim.missed_by ) / critter->ranged_target_size(), 0.4 ); } if( critter != nullptr && cur_missed_by < 1.0 ) { if( in_veh != nullptr && g->m.veh_at( tp ) == in_veh && critter->is_player() ) { // Turret either was aimed by the player (who is now ducking) and shoots from above // Or was just IFFing, giving lots of warnings and time to get out of the line of fire continue; } attack.missed_by = cur_missed_by; critter->deal_projectile_attack( null_source ? nullptr : origin, attack ); // Critter can still dodge the projectile // In this case hit_critter won't be set if( attack.hit_critter != nullptr ) { const size_t bt_len = blood_trail_len( attack.dealt_dam.total_damage() ); if( bt_len > 0 ) { const tripoint &dest = move_along_line( tp, trajectory, bt_len ); g->m.add_splatter_trail( critter->bloodType(), tp, dest ); } sfx::do_projectile_hit( *attack.hit_critter ); has_momentum = false; } else { attack.missed_by = aim.missed_by; } } else if( in_veh != nullptr && g->m.veh_at( tp ) == in_veh ) { // Don't do anything, especially don't call map::shoot as this would damage the vehicle } else { g->m.shoot( tp, proj, !no_item_damage && tp == target ); has_momentum = proj.impact.total_damage() > 0; } if( ( !has_momentum || !is_bullet ) && g->m.impassable( tp ) ) { // Don't let flamethrowers go through walls // TODO: Let them go through bars traj_len = i; break; } } // Done with the trajectory! if( do_animation && do_draw_line && traj_len > 2 ) { trajectory.erase( trajectory.begin() ); trajectory.resize( traj_len-- ); g->draw_line( tp, trajectory ); g->draw_bullet( tp, int( traj_len-- ), trajectory, bullet ); } if( g->m.impassable( tp ) ) { tp = prev_point; } drop_or_embed_projectile( attack ); apply_ammo_effects( tp, proj.proj_effects ); const auto &expl = proj.get_custom_explosion(); if( expl.power > 0.0f ) { g->explosion( tp, proj.get_custom_explosion() ); } // TODO: Move this outside now that we have hit point in return values? if( proj.proj_effects.count( "BOUNCE" ) ) { // Add effect so the shooter is not targeted itself. if( origin && !origin->has_effect( effect_bounced ) ) { origin->add_effect( effect_bounced, 1 ); } Creature *mon_ptr = g->get_creature_if( [&]( const Creature & z ) { // search for creatures in radius 4 around impact site if( rl_dist( z.pos(), tp ) <= 4 && g->m.sees( z.pos(), tp, -1 ) ) { // don't hit targets that have already been hit if( !z.has_effect( effect_bounced ) ) { return true; } } return false; } ); if( mon_ptr ) { Creature &z = *mon_ptr; add_msg( _( "The attack bounced to %s!" ), z.get_name().c_str() ); z.add_effect( effect_bounced, 1 ); projectile_attack( proj, tp, z.pos(), dispersion, origin, in_veh ); sfx::play_variant_sound( "fire_gun", "bio_lightning_tail", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); } } return attack; }