/* 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);
    }
}
Example #2
0
        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;
        }
    }
}
Example #6
0
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 );
    }
}
Example #7
0
void mdeath::gameover( monster &z )
{
    add_msg( m_bad, _( "The %s was destroyed!  GAME OVER!" ), z.name() );
    g->u.hp_cur[hp_torso] = 0;
}
Example #8
0
void mdeath::thing( monster &z )
{
    g->summon_mon( mon_thing, z.pos() );
}
Example #9
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" ) );
}
Example #10
0
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 );

}
Example #11
0
void mdeath::melt( monster &z )
{
    if( g->u.sees( z ) ) {
        add_msg( m_good, _( "The %s melts away." ), z.name() );
    }
}
Example #12
0
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 );
            }
        }
    }
}
Example #13
0
void mdeath::disappear( monster &z )
{
    if( g->u.sees( z ) ) {
        add_msg( m_good, _( "The %s disappears." ), z.name() );
    }
}
Example #14
0
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;
  }
 }
}
Example #15
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 );
    }
}
Example #16
0
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" ) );
}
Example #19
0
// 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));

 }
}
Example #20
0
// 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) );
 }
}
Example #21
0
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;
}
Example #22
0
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 );
    }
}
Example #23
0
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" ) );
}
Example #24
0
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 );
            }
        }
    }
}
Example #25
0
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));

 }
}
Example #26
0
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 );
    }
}
Example #27
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;
}
Example #28
0
void mdeath::disintegrate( monster &z )
{
    if( g->u.sees( z ) ) {
        add_msg( m_good, _( "The %s disintegrates!" ), z.name() );
    }
}
Example #29
0
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;
}
Example #30
0
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;
}