void mdeath::worm( monster &z ) { if( g->u.sees( z ) ) { if( z.type->dies.size() == 1 ) { add_msg( m_good, _( "The %s splits in two!" ), z.name() ); } else { add_msg( m_warning, _( "Two worms crawl out of the %s's corpse." ), z.name() ); } } std::vector <tripoint> wormspots; for( auto &&wormp : g->m.points_in_radius( z.pos(), 1 ) ) { // *NOPAD* if( g->m.has_flag( "DIGGABLE", wormp ) && g->is_empty( wormp ) ) { wormspots.push_back( wormp ); } } int worms = 0; while( worms < 2 && !wormspots.empty() ) { const tripoint target = random_entry_removed( wormspots ); if( !g->critter_at( target ) ) { g->summon_mon( mon_halfworm, target ); worms++; } } }
void mdeath::blobsplit( monster &z ) { int speed = z.get_speed() - rng( 30, 50 ); g->m.spawn_item( z.pos(), "slime_scrap", 1, 0, calendar::turn ); if( z.get_speed() <= 0 ) { if( g->u.sees( z ) ) { // TODO: Add vermin-tagged tiny versions of the splattered blob :) add_msg( m_good, _( "The %s splatters apart." ), z.name() ); } return; } if( g->u.sees( z ) ) { if( z.type->dies.size() == 1 ) { add_msg( m_good, _( "The %s splits in two!" ), z.name() ); } else { add_msg( m_bad, _( "Two small blobs slither out of the corpse." ) ); } } std::vector <tripoint> valid; for( auto &&dest : g->m.points_in_radius( z.pos(), 1 ) ) { // *NOPAD* if( g->is_empty( dest ) && z.can_move_to( dest ) ) { valid.push_back( dest ); } } for( int s = 0; s < 2 && !valid.empty(); s++ ) { const tripoint target = random_entry_removed( valid ); if( monster *const blob = g->summon_mon( speed < 50 ? mon_blob_small : mon_blob, target ) ) { blob->make_ally( z ); blob->set_speed_base( speed ); blob->set_hp( speed ); } } }
void mdeath::acid( monster &z ) { if( g->u.sees( z ) ) { if( z.type->dies.size() == 1 ) { //If this death function is the only function. The corpse gets dissolved. add_msg( m_mixed, _( "The %s's body dissolves into acid." ), z.name() ); } else { add_msg( m_warning, _( "The %s's body leaks acid." ), z.name() ); } } g->m.add_field( z.pos(), fd_acid, 3 ); }
bool melee_actor::call( monster &z ) const { Creature *target = find_target( z ); if( target == nullptr ) { return false; } z.mod_moves( -move_cost ); add_msg( m_debug, "%s attempting to melee_attack %s", z.name().c_str(), target->disp_name().c_str() ); const int acc = accuracy >= 0 ? accuracy : z.type->melee_skill; int hitspread = target->deal_melee_attack( &z, dice( acc, 10 ) ); if( hitspread < 0 ) { auto msg_type = target == &g->u ? m_warning : m_info; sfx::play_variant_sound( "mon_bite", "bite_miss", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); target->add_msg_player_or_npc( msg_type, miss_msg_u.c_str(), miss_msg_npc.c_str(), z.name().c_str() ); return true; } damage_instance damage = damage_max_instance; double multiplier = rng_float( min_mul, max_mul ); damage.mult_damage( multiplier ); body_part bp_hit = body_parts.empty() ? target->select_body_part( &z, hitspread ) : *body_parts.pick(); target->on_hit( &z, bp_hit ); dealt_damage_instance dealt_damage = target->deal_damage( &z, bp_hit, damage ); dealt_damage.bp_hit = bp_hit; int damage_total = dealt_damage.total_damage(); add_msg( m_debug, "%s's melee_attack did %d damage", z.name().c_str(), damage_total ); if( damage_total > 0 ) { on_damage( z, *target, dealt_damage ); } else { sfx::play_variant_sound( "mon_bite", "bite_miss", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); target->add_msg_player_or_npc( no_dmg_msg_u.c_str(), no_dmg_msg_npc.c_str(), z.name().c_str(), body_part_name_accusative( bp_hit ).c_str() ); } return true; }
bool gun_actor::call( monster &z ) const { Creature *target; if( z.friendly != 0 ) { // Attacking monsters, not the player! int boo_hoo; target = z.auto_find_hostile_target( range, boo_hoo ); if( target == nullptr ) { // Couldn't find any targets! if( boo_hoo > 0 && g->u.sees( z ) ) { // because that stupid oaf was in the way! add_msg( m_warning, ngettext( "Pointed in your direction, the %s emits an IFF warning beep.", "Pointed in your direction, the %s emits %d annoyed sounding beeps.", boo_hoo ), z.name().c_str(), boo_hoo ); } return false; } shoot( z, *target ); return true; } // Not friendly; hence, firing at the player too target = z.attack_target(); if( target == nullptr || rl_dist( z.pos(), target->pos() ) > range || !z.sees( *target ) ) { return false; } shoot( z, *target ); return true; }
void mdeath::boomer_glow( monster &z ) { std::string explode = string_format( _( "a %s explode!" ), z.name() ); sounds::sound( z.pos(), 24, sounds::sound_t::combat, explode, false, "explosion", "small" ); for( auto &&dest : g->m.points_in_radius( z.pos(), 1 ) ) { // *NOPAD* g->m.bash( dest, 10 ); if( monster *const z = g->critter_at<monster>( dest ) ) { z->stumble(); z->moves -= 250; } if( Creature *const critter = g->critter_at( dest ) ) { critter->add_env_effect( effect_boomered, bp_eyes, 5, 25_turns ); for( int i = 0; i < rng( 2, 4 ); i++ ) { body_part bp = random_body_part(); critter->add_env_effect( effect_glowing, bp, 4, 4_minutes ); if( critter != nullptr && critter->has_effect( effect_glowing ) ) { break; } } } } g->m.propagate_field( z.pos(), fd_bile, 30, 2 ); }
void mdefense::zapback( monster &m, Creature *const source, dealt_projectile_attack const *const proj ) { // Not a melee attack, attacker lucked out or out of range if( source == nullptr || proj != nullptr || rng( 0, 100 ) > m.def_chance || rl_dist( m.pos(), source->pos() ) > 1 ) { return; } if( source->is_elec_immune() ) { return; } // Players/NPCs can avoid the shock by using non-conductive weapons player const *const foe = dynamic_cast<player *>( source ); if( foe != nullptr && !foe->weapon.conductive() && !foe->unarmed_attack() ) { return; } damage_instance const shock { DT_ELECTRIC, static_cast<float>( rng( 1, 5 ) ) }; source->deal_damage( &m, bp_arm_l, shock ); source->deal_damage( &m, bp_arm_r, shock ); if( g->u.sees( source->pos() ) ) { auto const msg_type = ( source == &g->u ) ? m_bad : m_info; add_msg( msg_type, _( "Striking the %1$s shocks %2$s!" ), m.name().c_str(), source->disp_name().c_str() ); } source->check_dead_state(); }
void mdeath::darkman( monster &z ) { g->u.remove_effect( effect_darkness ); if( g->u.sees( z ) ) { add_msg( m_good, _( "The %s melts away." ), z.name() ); } }
void mdeath::broken_ammo( monster &z ) { if( g->u.sees( z.pos() ) ) { //~ %s is the possessive form of the monster's name add_msg( m_info, _( "The %s's interior compartment sizzles with destructive energy." ), z.name() ); } mdeath::broken( z ); }
void mdeath::conflagration( monster &z ) { for( const auto &dest : g->m.points_in_radius( z.pos(), 1 ) ) { g->m.propagate_field( dest, fd_fire, 18, 3 ); } const std::string explode = string_format( _( "a %s explode!" ), z.name() ); sounds::sound( z.pos(), 24, sounds::sound_t::combat, explode, false, "explosion", "small" ); }
void mdeath::fireball( monster &z ) { if( one_in( 10 ) ) { g->m.propagate_field( z.pos(), fd_fire, 15, 3 ); std::string explode = string_format( _( "an explosion of tank of the %s's flamethrower!" ), z.name() ); sounds::sound( z.pos(), 24, sounds::sound_t::combat, explode, false, "explosion", "default" ); add_msg( m_good, _( "I love the smell of burning zed in the morning." ) ); } else { normal( z ); } }
void Creature_tracker::remove( const monster &critter ) { const auto iter = std::find_if( monsters_list.begin(), monsters_list.end(), [&]( const std::shared_ptr<monster> &ptr ) { return ptr.get() == &critter; } ); if( iter == monsters_list.end() ) { debugmsg( "Tried to remove invalid monster %s", critter.name() ); return; } remove_from_location_map( critter ); monsters_list.erase( iter ); }
void mdeath::broken( monster &z ) { // Bail out if flagged (simulates eyebot flying away) if( z.no_corpse_quiet ) { return; } std::string item_id = z.type->id.str(); if (item_id.compare(0, 4, "mon_") == 0) { item_id.erase(0, 4); } // make "broken_manhack", or "broken_eyebot", ... item_id.insert(0, "broken_"); g->m.spawn_item( z.pos(), item_id, 1, 0, calendar::turn ); if( g->u.sees( z.pos() ) ) { add_msg( m_good, _( "The %s collapses!" ), z.name().c_str() ); } }
void mdeath::boomer( monster &z ) { std::string explode = string_format( _( "a %s explode!" ), z.name() ); sounds::sound( z.pos(), 24, sounds::sound_t::combat, explode, false, "explosion", "small" ); for( auto &&dest : g->m.points_in_radius( z.pos(), 1 ) ) { // *NOPAD* g->m.bash( dest, 10 ); if( monster *const z = g->critter_at<monster>( dest ) ) { z->stumble(); z->moves -= 250; } } if( rl_dist( z.pos(), g->u.pos() ) == 1 ) { g->u.add_env_effect( effect_boomered, bp_eyes, 2, 24_turns ); } g->m.propagate_field( z.pos(), fd_bile, 15, 1 ); }
void melee_actor::on_damage( monster &z, Creature &target, dealt_damage_instance &dealt ) const { if( target.is_player() ) { sfx::play_variant_sound( "mon_bite", "bite_hit", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); sfx::do_player_death_hurt( dynamic_cast<player &>( target ), false ); } auto msg_type = target.attitude_to( g->u ) == Creature::A_FRIENDLY ? m_bad : m_neutral; const body_part bp = dealt.bp_hit; target.add_msg_player_or_npc( msg_type, hit_dmg_u, hit_dmg_npc, z.name(), body_part_name_accusative( bp ) ); for( const auto &eff : effects ) { if( x_in_y( eff.chance, 100 ) ) { const body_part affected_bp = eff.affect_hit_bp ? bp : eff.bp; target.add_effect( eff.id, time_duration::from_turns( eff.duration ), affected_bp, eff.permanent ); } } }
bool gun_actor::call( monster &z ) const { Creature *target; if( z.friendly ) { int max_range = 0; for( const auto &e : ranges ) { max_range = std::max( std::max( max_range, e.first.first ), e.first.second ); } int hostiles; // hostiles which cannot be engaged without risking friendly fire target = z.auto_find_hostile_target( max_range, hostiles ); if( !target ) { if( hostiles > 0 && g->u.sees( z ) ) { add_msg( m_warning, ngettext( "Pointed in your direction, the %s emits an IFF warning beep.", "Pointed in your direction, the %s emits %d annoyed sounding beeps.", hostiles ), z.name(), hostiles ); } return false; } } else { target = z.attack_target(); if( !target || !z.sees( *target ) ) { return false; } } int dist = rl_dist( z.pos(), target->pos() ); for( const auto &e : ranges ) { if( dist >= e.first.first && dist <= e.first.second ) { shoot( z, *target, e.second ); return true; } } return false; }
void mdefense::zapback( monster &m, Creature *const source, dealt_projectile_attack const * ) { if( source == nullptr ) { return; } player const *const foe = dynamic_cast<player *>( source ); // Players/NPCs can avoid the shock by using non-conductive weapons if( foe != nullptr && foe->is_armed() && !foe->weapon.conductive() ) { return; } // Ranged weapons get no zapback, unless they have an active MELEE mode. if( foe != nullptr && foe->weapon.is_gun() && !foe->weapon.gun_current_mode().melee() ) { return; } if( source->is_elec_immune() ) { return; } if( g->u.sees( source->pos() ) ) { auto const msg_type = ( source == &g->u ) ? m_bad : m_info; add_msg( msg_type, _( "Striking the %1$s shocks %2$s!" ), m.name().c_str(), source->disp_name().c_str() ); } damage_instance const shock { DT_ELECTRIC, static_cast<float>( rng( 1, 5 ) ) }; source->deal_damage( &m, bp_arm_l, shock ); source->deal_damage( &m, bp_arm_r, shock ); source->check_dead_state(); }
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() ); } 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, static_cast<int>( std::floor( corpse_damage * itype::damage_scale ) ) ); } // if mdeath::splatter was set along normal makes sure it is not called twice bool splatt = false; for( const auto &deathfunction : z.type->dies ) { if( deathfunction == mdeath::splatter ) { splatt = true; } } if( !splatt ) { splatter( z ); } }
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 mdeath::guilt( monster &z ) { const int MAX_GUILT_DISTANCE = 5; int kill_count = g->kill_count( z.type->id ); int maxKills = 100; // this is when the player stop caring altogether. // different message as we kill more of the same monster std::string msg = _( "You feel guilty for killing %s." ); // default guilt message game_message_type msgtype = m_bad; // default guilt message type std::map<int, std::string> guilt_tresholds; guilt_tresholds[75] = _( "You feel ashamed for killing %s." ); guilt_tresholds[50] = _( "You regret killing %s." ); guilt_tresholds[25] = _( "You feel remorse for killing %s." ); if( g->u.has_trait( trait_PSYCHOPATH ) || g->u.has_trait( trait_PRED3 ) || g->u.has_trait( trait_PRED4 ) ) { return; } if( rl_dist( z.pos(), g->u.pos() ) > MAX_GUILT_DISTANCE ) { // Too far away, we can deal with it. return; } if( z.get_hp() >= 0 ) { // We probably didn't kill it return; } if( kill_count >= maxKills ) { // player no longer cares if( kill_count == maxKills ) { //~ Message after killing a lot of monsters which would normally affect the morale negatively. %s is the monster name, it will be pluralized with a number of 100. add_msg( m_good, _( "After killing so many bloody %s you no longer care " "about their deaths anymore." ), z.name( maxKills ) ); } return; } else if( ( g->u.has_trait( trait_PRED1 ) ) || ( g->u.has_trait( trait_PRED2 ) ) ) { msg = ( _( "Culling the weak is distasteful, but necessary." ) ); msgtype = m_neutral; } else { msgtype = m_bad; for( auto &guilt_treshold : guilt_tresholds ) { if( kill_count >= guilt_treshold.first ) { msg = guilt_treshold.second; break; } } } add_msg( msgtype, msg, z.name() ); int moraleMalus = -50 * ( 1.0 - ( static_cast<float>( kill_count ) / maxKills ) ); int maxMalus = -250 * ( 1.0 - ( static_cast<float>( kill_count ) / maxKills ) ); time_duration duration = 30_minutes * ( 1.0 - ( static_cast<float>( kill_count ) / maxKills ) ); time_duration decayDelay = 3_minutes * ( 1.0 - ( static_cast<float>( kill_count ) / maxKills ) ); if( z.type->in_species( ZOMBIE ) ) { moraleMalus /= 10; if( g->u.has_trait( trait_PACIFIST ) ) { moraleMalus *= 5; } else if( g->u.has_trait( trait_PRED1 ) ) { moraleMalus /= 4; } else if( g->u.has_trait( trait_PRED2 ) ) { moraleMalus /= 5; } } g->u.add_morale( MORALE_KILLED_MONSTER, moraleMalus, maxMalus, duration, decayDelay ); }
void gun_actor::shoot( monster &z, Creature &target ) const { // Make sure our ammo isn't weird. if( z.ammo[ammo_type] > max_ammo ) { debugmsg( "Generated too much ammo (%d) of type %s for %s in gun_actor::shoot", z.ammo[ammo_type], ammo_type.c_str(), z.name().c_str() ); z.ammo[ammo_type] = max_ammo; } const bool require_targeting = ( require_targeting_player && target.is_player() ) || ( require_targeting_npc && target.is_npc() ) || ( require_targeting_monster && target.is_monster() ); const bool not_targeted = require_targeting && !z.has_effect( effect_targeted ); const bool not_laser_locked = require_targeting && laser_lock && !target.has_effect( effect_was_laserlocked ); if( not_targeted || not_laser_locked ) { if( !targeting_sound.empty() ) { sounds::sound( z.pos(), targeting_volume, _( targeting_sound.c_str() ) ); } if( not_targeted ) { z.add_effect( effect_targeted, targeting_timeout ); } if( not_laser_locked ) { target.add_effect( effect_laserlocked, 5 ); target.add_effect( effect_was_laserlocked, 5 ); target.add_msg_if_player( m_warning, _( "You're not sure why you've got a laser dot on you..." ) ); } z.moves -= targeting_cost; return; } // It takes a while z.moves -= move_cost; if( z.ammo[ammo_type] <= 0 && !no_ammo_sound.empty() ) { sounds::sound( z.pos(), 10, _( no_ammo_sound.c_str() ) ); return; } if( g->u.sees( z ) ) { add_msg( m_warning, _( description.c_str() ) ); } npc tmp; tmp.name = _( "The " ) + z.name(); tmp.set_fake( true ); tmp.recoil = 0; tmp.driving_recoil = 0; tmp.setpos( z.pos() ); tmp.str_max = fake_str; tmp.dex_max = fake_dex; tmp.int_max = fake_int; tmp.per_max = fake_per; tmp.str_cur = fake_str; tmp.dex_cur = fake_dex; tmp.int_cur = fake_int; tmp.per_cur = fake_per; tmp.weapon = item( gun_type, 0 ); tmp.weapon.set_curammo( ammo_type ); tmp.weapon.charges = z.ammo[ammo_type]; if( z.friendly != 0 ) { tmp.attitude = NPCATT_DEFEND; } else { tmp.attitude = NPCATT_KILL; } for( const auto &pr : fake_skills ) { tmp.skillLevel( pr.first ).level( pr.second ); } const auto distance = rl_dist( z.pos(), target.pos() ); int burst_size = std::min( burst_limit, tmp.weapon.burst_size() ); if( distance > range_no_burst || burst_size < 1 ) { burst_size = 1; } tmp.fire_gun( target.pos(), burst_size ); z.ammo[ammo_type] = tmp.weapon.charges; if( require_targeting ) { z.add_effect( effect_targeted, targeting_timeout_extend ); } if( laser_lock ) { // To prevent spamming laser locks when the player can tank that stuff somehow target.add_effect( effect_was_laserlocked, 5 ); } }
void shoot_monster(game *g, player &p, monster &mon, int &dam, double goodhit, item* weapon) { // Gunmods don't have a type, so use the player weapon type. it_gun* firing = dynamic_cast<it_gun*>(p.weapon.type); std::string message; bool u_see_mon = g->u_see(&(mon)); if (mon.has_flag(MF_HARDTOSHOOT) && !one_in(4) && weapon->curammo->phase != LIQUID && weapon->curammo->accuracy >= 4) { // Buckshot hits anyway if (u_see_mon) g->add_msg("The shot passes through the %s without hitting.", mon.name().c_str()); goodhit = 1; } else { // Not HARDTOSHOOT // Armor blocks BEFORE any critical effects. int zarm = mon.armor_cut(); zarm -= weapon->curammo->pierce; if (weapon->curammo->phase == LIQUID) zarm = 0; else if (weapon->curammo->accuracy < 4) // Shot doesn't penetrate armor well zarm *= rng(2, 4); if (zarm > 0) dam -= zarm; if (dam <= 0) { if (u_see_mon) g->add_msg("The shot reflects off the %s!", mon.name_with_armor().c_str()); dam = 0; goodhit = 1; } if (goodhit < .1 && !mon.has_flag(MF_NOHEAD)) { message = "Headshot!"; dam = rng(5 * dam, 8 * dam); p.practice(g->turn, firing->skill_used, 5); } else if (goodhit < .2) { message = "Critical!"; dam = rng(dam * 2, dam * 3); p.practice(g->turn, firing->skill_used, 2); } else if (goodhit < .4) { dam = rng(int(dam * .9), int(dam * 1.5)); p.practice(g->turn, firing->skill_used, rng(0, 2)); } else if (goodhit <= .7) { message = "Grazing hit."; dam = rng(0, dam); } else dam = 0; // Find the zombie at (x, y) and hurt them, MAYBE kill them! if (dam > 0) { mon.moves -= dam * 5; if (&p == &(g->u) && u_see_mon) g->add_msg("%s You hit the %s for %d damage.", message.c_str(), mon.name().c_str(), dam); else if (u_see_mon) g->add_msg("%s %s shoots the %s.", message.c_str(), p.name.c_str(), mon.name().c_str()); bool bMonDead = mon.hurt(dam); hit_animation(mon.posx - g->u.posx + VIEWX - g->u.view_offset_x, mon.posy - g->u.posy + VIEWY - g->u.view_offset_y, red_background(mon.type->color), (bMonDead) ? '%' : mon.symbol()); if (bMonDead) g->kill_mon(g->mon_at(mon.posx, mon.posy), (&p == &(g->u))); else if (weapon->curammo->ammo_effects != 0) g->hit_monster_with_flags(mon, weapon->curammo->ammo_effects); dam = 0; } } }
bool bite_actor::call( monster &z ) const { if( !z.can_act() ) { return false; } Creature *target = z.attack_target(); if( target == nullptr || !is_adjacent( z, *target ) ) { return false; } z.mod_moves( -move_cost ); add_msg( m_debug, "%s attempting to bite %s", z.name().c_str(), target->disp_name().c_str() ); int hitspread = target->deal_melee_attack( &z, z.hit_roll() ); if( hitspread < 0 ) { auto msg_type = target == &g->u ? m_warning : m_info; sfx::play_variant_sound( "mon_bite", "bite_miss", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); target->add_msg_player_or_npc( msg_type, _( "The %s lunges at you, but you dodge!" ), _( "The %s lunges at <npcname>, but they dodge!" ), z.name().c_str() ); return true; } damage_instance damage = damage_max_instance; dealt_damage_instance dealt_damage; body_part hit; double multiplier = rng_float( min_mul, max_mul ); damage.mult_damage( multiplier ); target->deal_melee_hit( &z, hitspread, false, damage, dealt_damage ); hit = dealt_damage.bp_hit; int damage_total = dealt_damage.total_damage(); add_msg( m_debug, "%s's bite did %d damage", z.name().c_str(), damage_total ); if( damage_total > 0 ) { auto msg_type = target == &g->u ? m_bad : m_info; //~ 1$s is monster name, 2$s bodypart in accusative if( target->is_player() ) { sfx::play_variant_sound( "mon_bite", "bite_hit", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); sfx::do_player_death_hurt( *dynamic_cast<player *>( target ), 0 ); } target->add_msg_player_or_npc( msg_type, _( "The %1$s bites your %2$s!" ), _( "The %1$s bites <npcname>'s %2$s!" ), z.name().c_str(), body_part_name_accusative( hit ).c_str() ); if( one_in( no_infection_chance - damage_total ) ) { if( target->has_effect( effect_bite, hit ) ) { target->add_effect( effect_bite, 400, hit, true ); } else if( target->has_effect( effect_infected, hit ) ) { target->add_effect( effect_infected, 250, hit, true ); } else { target->add_effect( effect_bite, 1, hit, true ); } } } else { sfx::play_variant_sound( "mon_bite", "bite_miss", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); target->add_msg_player_or_npc( _( "The %1$s bites your %2$s, but fails to penetrate armor!" ), _( "The %1$s bites <npcname>'s %2$s, but fails to penetrate armor!" ), z.name().c_str(), body_part_name_accusative( hit ).c_str() ); } return true; }
void mdeath::disintegrate( monster &z ) { if( g->u.sees( z ) ) { add_msg( m_good, _( "The %s disintegrates!" ), z.name() ); } }
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 ); } } } }
void mdeath::melt( monster &z ) { if( g->u.sees( z ) ) { add_msg( m_good, _( "The %s melts away." ), z.name() ); } }
void mdeath::disappear( monster &z ) { if( g->u.sees( z ) ) { add_msg( m_good, _( "The %s disappears." ), z.name() ); } }
void mdeath::detonate( monster &z ) { weighted_int_list<std::string> amm_list; for( const auto &amm : z.ammo ) { amm_list.add( amm.first, amm.second ); } std::vector<std::string> pre_dets; for( int i = 0; i < 3; i++ ) { if( amm_list.get_weight() <= 0 ) { break; } // Grab one item std::string tmp = *amm_list.pick(); // and reduce its weight by 1 amm_list.add_or_replace( tmp, amm_list.get_specific_weight( tmp ) - 1 ); // and stash it for use pre_dets.push_back( tmp ); } // Update any hardcoded explosion equivalencies std::vector<std::pair<std::string, long>> dets; for( const std::string &bomb_id : pre_dets ) { if( bomb_id == "bot_grenade_hack" ) { dets.push_back( std::make_pair( "grenade_act", 5 ) ); } else if( bomb_id == "bot_flashbang_hack" ) { dets.push_back( std::make_pair( "flashbang_act", 5 ) ); } else if( bomb_id == "bot_gasbomb_hack" ) { dets.push_back( std::make_pair( "gasbomb_act", 20 ) ); } else if( bomb_id == "bot_c4_hack" ) { dets.push_back( std::make_pair( "c4armed", 10 ) ); } else if( bomb_id == "bot_mininuke_hack" ) { dets.push_back( std::make_pair( "mininuke_act", 20 ) ); } else { // Get the transformation item const iuse_transform *actor = dynamic_cast<const iuse_transform *>( item::find_type( bomb_id )->get_use( "transform" )->get_actor_ptr() ); if( actor == nullptr ) { // Invalid bomb item, move to the next ammo item add_msg( m_debug, "Invalid bomb type in detonate mondeath for %s.", z.name() ); continue; } dets.emplace_back( actor->target, actor->ammo_qty ); } } if( g->u.sees( z ) ) { if( dets.empty() ) { //~ %s is the possessive form of the monster's name add_msg( m_info, _( "The %s's hands fly to its pockets, but there's nothing left in them." ), z.name() ); } else { //~ %s is the possessive form of the monster's name add_msg( m_bad, _( "The %s's hands fly to its remaining pockets, opening them!" ), z.name() ); } } // HACK, used to stop them from having ammo on respawn z.add_effect( effect_no_ammo, 1_turns, num_bp, true ); // First die normally mdeath::normal( z ); // Then detonate our suicide bombs for( const auto &bombs : dets ) { item bomb_item( bombs.first, 0 ); bomb_item.charges = bombs.second; bomb_item.active = true; g->m.add_item_or_charges( z.pos(), bomb_item ); } }
void mdeath::gameover( monster &z ) { add_msg( m_bad, _( "The %s was destroyed! GAME OVER!" ), z.name() ); g->u.hp_cur[hp_torso] = 0; }
void mdeath::smokeburst( monster &z ) { std::string explode = string_format( _( "a %s explode!" ), z.name() ); sounds::sound( z.pos(), 24, sounds::sound_t::combat, explode, false, "explosion", "small" ); g->m.emit_field( z.pos(), emit_id( "emit_smoke_blast" ) ); }