/* Monster hit animation */ void game::draw_hit_mon(int x, int y, monster m, bool dead) { if (use_tiles) { //int iTimeout = 0; tilecontext->init_draw_hit(x, y, m.type->id); wrefresh(w_terrain); try_update(); timespec tspec; tspec.tv_sec = 0; tspec.tv_nsec = 1000000 * OPTIONS["ANIMATION_DELAY"]; if( tspec.tv_nsec != 0 ) { nanosleep(&tspec, NULL); } } else { nc_color cMonColor = m.type->color; const std::string &sMonSym = m.symbol(); hit_animation(POSX + (x - (u.posx + u.view_offset_x)), POSY + (y - (u.posy + u.view_offset_y)), red_background(cMonColor), dead ? "%" : sMonSym); } }
virtual void select(int entnum, uimenu *menu) { if ( ! started ) { started = true; setup(menu); } if (entnum != lastent) { lastent = entnum; tmp = monster(GetMType(entnum)); if (friendly) { tmp.friendly = -1; } } werase(w_info); tmp.print_info(w_info); std::string header = string_format("#%d: %s", entnum, GetMType(entnum)->nname().c_str()); mvwprintz(w_info, 1, ( getmaxx(w_info) - header.size() ) / 2, c_cyan, "%s", header.c_str()); mvwprintz(w_info, getmaxy(w_info) - 3, 0, c_green, "%s", msg.c_str()); msg = padding; mvwprintw(w_info, getmaxy(w_info) - 2, 0, _("[/] find, [f] friendly, [q]uit")); }
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::kill_vines( monster &z ) { const std::vector<Creature *> vines = g->get_creatures_if( [&]( const Creature & critter ) { const monster *const mon = dynamic_cast<const monster *>( &critter ); return mon && mon->type->id == mon_creeper_vine; } ); const std::vector<Creature *> hubs = g->get_creatures_if( [&]( const Creature & critter ) { const monster *const mon = dynamic_cast<const monster *>( &critter ); return mon && mon != &z && mon->type->id == mon_creeper_hub; } ); for( Creature *const vine : vines ) { int dist = rl_dist( vine->pos(), z.pos() ); bool closer = false; for( auto &j : hubs ) { if( rl_dist( vine->pos(), j->pos() ) < dist ) { break; } } if( !closer ) { // TODO: closer variable is not being updated and is always false! vine->die( &z ); } } }
void mdeath::preg_roach( monster &z ) { int num_roach = rng( 1, 3 ); std::vector <tripoint> roachspots; for( const auto &roachp : g->m.points_in_radius( z.pos(), 1 ) ) { if( g->is_empty( roachp ) ) { roachspots.push_back( roachp ); } } while( !roachspots.empty() ) { const tripoint target = random_entry_removed( roachspots ); if( !g->critter_at( target ) ) { g->summon_mon( mon_giant_cockroach_nymph, target ); num_roach--; if( g->u.sees( z ) ) { add_msg( m_warning, _( "A cockroach nymph crawls out of the pregnant giant cockroach corpse." ) ); } } if( num_roach == 0 ) { break; } } }
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 ); } }
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::thing( monster &z ) { g->summon_mon( mon_thing, z.pos() ); }
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" ) ); }
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 mdeath::melt( monster &z ) { if( g->u.sees( z ) ) { add_msg( m_good, _( "The %s melts away." ), z.name() ); } }
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 ); } } } }
void mdeath::disappear( monster &z ) { if( g->u.sees( z ) ) { add_msg( m_good, _( "The %s disappears." ), z.name() ); } }
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; } } }
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 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 ); } }
bool Creature_tracker::update_pos(const monster &critter, const int new_x_pos, const int new_y_pos) { if (critter.posx() == new_x_pos && critter.posy() == new_y_pos) { return true; // success? } bool success = false; const int dead_critter_id = dead_mon_at(point(critter.posx(), critter.posy())); const int live_critter_id = mon_at(point(critter.posx(), critter.posy())); const int critter_id = critter.dead ? dead_critter_id : live_critter_id; const int new_critter_id = mon_at(new_x_pos, new_y_pos); if (new_critter_id >= 0 && !_old_monsters_list[new_critter_id]->dead) { debugmsg("update_zombie_pos: new location %d,%d already has zombie %d", new_x_pos, new_y_pos, new_critter_id); } else if (critter_id >= 0) { if (&critter == _old_monsters_list[critter_id]) { _old_monsters_by_location.erase(point(critter.posx(), critter.posy())); _old_monsters_by_location[point(new_x_pos, new_y_pos)] = critter_id; success = true; } else { debugmsg("update_zombie_pos: old location %d,%d had zombie %d instead", critter.posx(), critter.posy(), critter_id); } } else { // We're changing the x/y coordinates of a zombie that hasn't been added // to the game yet. add_zombie() will update _old_monsters_by_location for us. debugmsg("update_zombie_pos: no such zombie at %d,%d (moving to %d,%d)", critter.posx(), critter.posy(), new_x_pos, new_y_pos); } return success; }
void mdeath::gas( monster &z ) { std::string explode = string_format( _( "a %s explode!" ), z.name() ); sounds::sound( z.pos(), 24, sounds::sound_t::combat, explode ); g->m.emit_field( z.pos(), emit_id( "emit_toxic_blast" ) ); }
// MATERIALS-TODO: use fire resistance void game::hit_monster_with_flags(monster &z, unsigned int effects) { if (effects & mfb(AMMO_FLAME)) { if (z.made_of("veggy") || z.made_of("cotton") || z.made_of("wool") || z.made_of("paper") || z.made_of("wood")) z.add_effect(ME_ONFIRE, rng(8, 20)); else if (z.made_of("flesh")) z.add_effect(ME_ONFIRE, rng(5, 10)); } else if (effects & mfb(AMMO_INCENDIARY)) { if (z.made_of("veggy") || z.made_of("cotton") || z.made_of("wool") || z.made_of("paper") || z.made_of("wood")) z.add_effect(ME_ONFIRE, rng(2, 6)); else if (z.made_of("flesh") && one_in(4)) z.add_effect(ME_ONFIRE, rng(1, 4)); } else if (effects & mfb(AMMO_IGNITE)) { if (z.made_of("veggy") || z.made_of("cotton") || z.made_of("wool") || z.made_of("paper") || z.made_of("wood")) z.add_effect(ME_ONFIRE, rng(6, 6)); else if (z.made_of("flesh")) z.add_effect(ME_ONFIRE, rng(10, 10)); } }
// MATERIALS-TODO: use fire resistance void game::hit_monster_with_flags(monster &z, const std::set<std::string> &effects) { if (effects.count("FLAME")) { if (z.made_of("veggy") || z.made_of("cotton") || z.made_of("wool") || z.made_of("paper") || z.made_of("wood")) z.add_effect(ME_ONFIRE, rng(8, 20)); else if (z.made_of("flesh")) z.add_effect(ME_ONFIRE, rng(5, 10)); } else if (effects.count("INCENDIARY")) { if (z.made_of("veggy") || z.made_of("cotton") || z.made_of("wool") || z.made_of("paper") || z.made_of("wood")) z.add_effect(ME_ONFIRE, rng(2, 6)); else if (z.made_of("flesh") && one_in(4)) z.add_effect(ME_ONFIRE, rng(1, 4)); } else if (effects.count("IGNITE")) { if (z.made_of("veggy") || z.made_of("cotton") || z.made_of("wool") || z.made_of("paper") || z.made_of("wood")) z.add_effect(ME_ONFIRE, rng(6, 6)); else if (z.made_of("flesh")) z.add_effect(ME_ONFIRE, rng(10, 10)); } if (effects.count("BOUNCE")) { z.add_effect(ME_BOUNCED, 1); } int stun_strength = 0; if (effects.count("BEANBAG")) { stun_strength = 4; } if (effects.count("LARGE_BEANBAG")) { stun_strength = 16; } if( stun_strength > 0 ) { switch( z.type->size ) { case MS_TINY: stun_strength *= 4; break; case MS_SMALL: stun_strength *= 2; break; case MS_MEDIUM: default: break; case MS_LARGE: stun_strength /= 2; break; case MS_HUGE: stun_strength /= 4; break; } z.add_effect( ME_STUNNED, rng(stun_strength / 2, stun_strength) ); } }
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)); int adjusted_damage = dam; if (mon.has_flag(MF_HARDTOSHOOT) && !one_in(10 - 10 * (.8 - goodhit)) && // Maxes out at 50% chance with perfect hit weapon->curammo->phase != LIQUID && !weapon->curammo->ammo_effects.count("SHOT") && !weapon->curammo->ammo_effects.count("BOUNCE")) { 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->ammo_effects.count("SHOT")) // Shot doesn't penetrate armor well zarm *= rng(2, 3); if (zarm > 0) adjusted_damage -= zarm; if (adjusted_damage <= 0) { if (u_see_mon) g->add_msg(_("The shot reflects off the %s!"), mon.name_with_armor().c_str()); adjusted_damage = 0; goodhit = 1; } if (goodhit <= .1 && !mon.has_flag(MF_NOHEAD)) { message = _("Headshot!"); adjusted_damage = rng(5 * adjusted_damage, 8 * adjusted_damage); p.practice(g->turn, firing->skill_used, 5); } else if (goodhit <= .2) { message = _("Critical!"); adjusted_damage = rng(adjusted_damage * 2, adjusted_damage * 3); p.practice(g->turn, firing->skill_used, 3); } else if (goodhit <= .4) { message = _("Good hit!"); adjusted_damage = rng(adjusted_damage , adjusted_damage * 2); p.practice(g->turn, firing->skill_used, 2); } else if (goodhit <= .6) { adjusted_damage = rng(adjusted_damage / 2, adjusted_damage); p.practice(g->turn, firing->skill_used, 1); } else if (goodhit <= .8) { message = _("Grazing hit."); adjusted_damage = rng(0, adjusted_damage); } else { adjusted_damage = 0; } if(item(weapon->curammo, 0).has_flag("NOGIB")) { adjusted_damage = std::min(adjusted_damage, mon.hp+10); } // Find the zombie at (x, y) and hurt them, MAYBE kill them! if (adjusted_damage > 0) { switch (mon.type->size) { case MS_TINY: mon.moves -= rng(0, adjusted_damage * 5); break; case MS_SMALL: mon.moves -= rng(0, adjusted_damage * 3); break; case MS_MEDIUM: mon.moves -= rng(0, adjusted_damage); break; case MS_LARGE: mon.moves -= rng(0, adjusted_damage / 3); break; case MS_HUGE: mon.moves -= rng(0, adjusted_damage / 5); break; } 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(), adjusted_damage); } 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(adjusted_damage, dam); if( u_see_mon ) { 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.empty()) { g->hit_monster_with_flags(mon, weapon->curammo->ammo_effects); } adjusted_damage = 0; } } dam = adjusted_damage; }
void gun_actor::shoot( monster &z, Creature &target, const gun_mode_id &mode ) const { if( require_sunlight && !g->is_in_sunlight( z.pos() ) ) { if( one_in( 3 ) && g->u.sees( z ) ) { add_msg( _( failure_msg ), z.name() ); } return; } 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_volume > 0 && !targeting_sound.empty() ) { sounds::sound( z.pos(), targeting_volume, sounds::sound_t::alarm, _( targeting_sound ) ); } if( not_targeted ) { z.add_effect( effect_targeted, time_duration::from_turns( targeting_timeout ) ); } if( not_laser_locked ) { target.add_effect( effect_laserlocked, 5_turns ); target.add_effect( effect_was_laserlocked, 5_turns ); 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; } z.moves -= move_cost; item gun( gun_type ); gun.gun_set_mode( mode ); itype_id ammo = ( ammo_type != "null" ) ? ammo_type : gun.ammo_default(); if( ammo != "null" ) { gun.ammo_set( ammo, z.ammo[ ammo ] ); } if( !gun.ammo_sufficient() ) { if( !no_ammo_sound.empty() ) { sounds::sound( z.pos(), 10, sounds::sound_t::combat, _( no_ammo_sound ) ); } return; } standard_npc tmp( _( "The " ) + z.name(), {}, 8, fake_str, fake_dex, fake_int, fake_per ); tmp.set_fake( true ); tmp.setpos( z.pos() ); tmp.set_attitude( z.friendly ? NPCATT_FOLLOW : NPCATT_KILL ); tmp.recoil = 0; // no need to aim for( const auto &pr : fake_skills ) { tmp.set_skill_level( pr.first, pr.second ); } tmp.weapon = gun; tmp.i_add( item( "UPS_off", calendar::turn, 1000 ) ); if( g->u.sees( z ) ) { add_msg( m_warning, _( description ), z.name(), tmp.weapon.tname() ); } z.ammo[ammo] -= tmp.fire_gun( target.pos(), gun.gun_current_mode().qty ); if( require_targeting ) { z.add_effect( effect_targeted, time_duration::from_turns( 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_turns ); } }
void mdeath::smokeburst( monster &z ) { std::string explode = string_format( _( "a %s explode!" ), z.name().c_str() ); sounds::sound( z.pos(), 24, explode ); g->m.emit_field( z.pos(), emit_id( "emit_smoke_blast" ) ); }
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 game::hit_monster_with_flags(monster &z, unsigned int effects) { if (effects & mfb(AMMO_FLAME)) { if (z.made_of(VEGGY) || z.made_of(COTTON) || z.made_of(WOOL) || z.made_of(PAPER) || z.made_of(WOOD)) z.add_effect(ME_ONFIRE, rng(8, 20)); else if (z.made_of(FLESH)) z.add_effect(ME_ONFIRE, rng(5, 10)); } else if (effects & mfb(AMMO_INCENDIARY)) { if (z.made_of(VEGGY) || z.made_of(COTTON) || z.made_of(WOOL) || z.made_of(PAPER) || z.made_of(WOOD)) z.add_effect(ME_ONFIRE, rng(2, 6)); else if (z.made_of(FLESH) && one_in(4)) z.add_effect(ME_ONFIRE, rng(1, 4)); } }
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 ); } }
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() ); } }
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 shoot_monster(player &p, monster &mon, int &dam, double goodhit, item* weapon, const std::set<std::string> &effects) { // 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)); int adjusted_damage = dam; if (mon.has_flag(MF_HARDTOSHOOT) && !one_in(10 - 10 * (.8 - goodhit)) && // Maxes out at 50% chance with perfect hit weapon->curammo->phase != LIQUID && !effects.count("SHOT") && !effects.count("BOUNCE")) { if (u_see_mon) g->add_msg(_("The shot passes through the %s without hitting."), mon.name().c_str()); } else { // Not HARDTOSHOOT // Bounce applies whether it does damage or not. if (effects.count("BOUNCE")) { mon.add_effect("bounced", 1); } // Armor blocks BEFORE any critical effects. int zarm = mon.get_armor_cut(bp_torso); zarm -= weapon->gun_pierce(); if (weapon->curammo->phase == LIQUID) zarm = 0; else if (effects.count("SHOT")) // Shot doesn't penetrate armor well zarm *= rng(2, 3); if (zarm > 0) adjusted_damage -= zarm; if (adjusted_damage <= 0) { if (u_see_mon) g->add_msg(_("The shot reflects off the %s!"), mon.name_with_armor().c_str()); adjusted_damage = 0; goodhit = 1; } if (goodhit <= .1 && !mon.has_flag(MF_NOHEAD)) { message = _("Headshot!"); adjusted_damage = rng(5 * adjusted_damage, 8 * adjusted_damage); p.practice(g->turn, firing->skill_used, 5); p.lifetime_stats()->headshots++; } else if (goodhit <= .2) { message = _("Critical!"); adjusted_damage = rng(adjusted_damage * 2, adjusted_damage * 3); p.practice(g->turn, firing->skill_used, 3); } else if (goodhit <= .4) { message = _("Good hit!"); adjusted_damage = rng(adjusted_damage , adjusted_damage * 2); p.practice(g->turn, firing->skill_used, 2); } else if (goodhit <= .6) { adjusted_damage = rng(adjusted_damage / 2, adjusted_damage); p.practice(g->turn, firing->skill_used, 1); } else if (goodhit <= .8) { message = _("Grazing hit."); adjusted_damage = rng(0, adjusted_damage); } else { adjusted_damage = 0; } if(item(weapon->curammo, 0).has_flag("NOGIB")) { adjusted_damage = std::min(adjusted_damage, mon.hp+10); } // Find the zombie at (x, y) and hurt them, MAYBE kill them! if (adjusted_damage > 0) { switch (mon.type->size) { case MS_TINY: mon.moves -= rng(0, adjusted_damage * 5); break; case MS_SMALL: mon.moves -= rng(0, adjusted_damage * 3); break; case MS_MEDIUM: mon.moves -= rng(0, adjusted_damage); break; case MS_LARGE: mon.moves -= rng(0, adjusted_damage / 3); break; case MS_HUGE: mon.moves -= rng(0, adjusted_damage / 5); break; } 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(), adjusted_damage); } else if (u_see_mon) { g->add_msg(_("%s %s shoots the %s."), message.c_str(), p.name.c_str(), mon.name().c_str()); } g->hit_monster_with_flags(mon, effects); damage_instance d; d.add_damage(DT_CUT, adjusted_damage, weapon->gun_pierce(), effects.count("SHOT")?rng(2,3):1); // Shot doesn't penetrate armor well mon.deal_damage(&p, bp_torso, -1, d); if( u_see_mon ) { g->draw_hit_mon(mon.posx(), mon.posy(), mon, mon.is_dead_state()); } } } dam = adjusted_damage; }