Example #1
0
bool map::process_fields(game *g)
{
 bool found_field = false;
 field *cur;
 field_id curtype;
 for (int x = 0; x < SEEX * 3; x++) {
  for (int y = 0; y < SEEY * 3; y++) {
   cur = &field_at(x, y);
   curtype = cur->type;
   if (!found_field && curtype != fd_null)
    found_field = true;
   if (cur->density > 3)
    debugmsg("Whoooooa density of %d", cur->density);

  if (cur->age == 0)	// Don't process "newborn" fields
   curtype = fd_null;

  switch (curtype) {

   case fd_null:
    break;	// Do nothing, obviously.  OBVIOUSLY.

   case fd_blood:
   case fd_bile:
    if (has_flag(swimmable, x, y))	// Dissipate faster in water
     cur->age += 250;
    break;

   case fd_acid:
    if (has_flag(swimmable, x, y))	// Dissipate faster in water
     cur->age += 20;
    for (int i = 0; i < i_at(x, y).size(); i++) {
     item *melting = &(i_at(x, y)[i]);
     if (melting->made_of(LIQUID) || melting->made_of(VEGGY)   ||
         melting->made_of(FLESH)  || melting->made_of(POWDER)  ||
         melting->made_of(COTTON) || melting->made_of(WOOL)    ||
         melting->made_of(PAPER)  || melting->made_of(PLASTIC) ||
         (melting->made_of(GLASS) && !one_in(3)) || one_in(4)) {
// Acid destructable objects here
      melting->damage++;
      if (melting->damage >= 5 ||
          (melting->made_of(PAPER) && melting->damage >= 3)) {
       cur->age += melting->volume();
       for (int m = 0; m < i_at(x, y)[i].contents.size(); m++)
        i_at(x, y).push_back( i_at(x, y)[i].contents[m] );
       i_at(x, y).erase(i_at(x, y).begin() + i);
       i--;
      }
     }
    }
    break;

   case fd_fire:
// Consume items as fuel to help us grow/last longer.
    bool destroyed;
    int vol;
    for (int i = 0; i < i_at(x, y).size(); i++) {
     destroyed = false;
     vol = i_at(x, y)[i].volume();
     if (i_at(x, y)[i].is_ammo()) {
      cur->age /= 2;
      cur->age -= 300;
      destroyed = true;
     } else if (i_at(x, y)[i].made_of(PAPER)) {
      cur->age -= vol * 10;
      destroyed = true;
     } else if ((i_at(x, y)[i].made_of(WOOD) || i_at(x, y)[i].made_of(VEGGY)) &&
                (vol <= cur->density*10-(cur->age>0 ? rng(0,cur->age/10) : 0) ||
                 cur->density == 3)) {
      cur->age -= vol * 10;
      destroyed = true;
     } else if ((i_at(x, y)[i].made_of(COTTON) || i_at(x, y)[i].made_of(FLESH)||
                 i_at(x, y)[i].made_of(WOOL)) &&
                (vol <= cur->density*2 || (cur->density == 3 && one_in(vol)))) {
      cur->age -= vol * 5;
      destroyed = true;
     } else if (i_at(x, y)[i].made_of(LIQUID) || i_at(x, y)[i].made_of(POWDER)||
                i_at(x, y)[i].made_of(PLASTIC)||
                (cur->density >= 2 && i_at(x, y)[i].made_of(GLASS)) ||
                (cur->density == 3 && i_at(x, y)[i].made_of(IRON))) {
      switch (i_at(x, y)[i].type->id) { // TODO: Make this be not a hack.
       case itm_whiskey:
       case itm_vodka:
       case itm_rum:
       case itm_tequila:
        cur->age -= 220;
        break;
      }
      destroyed = true;
     }
     if (destroyed) {
      for (int m = 0; m < i_at(x, y)[i].contents.size(); m++)
       i_at(x, y).push_back( i_at(x, y)[i].contents[m] );
      i_at(x, y).erase(i_at(x, y).begin() + i);
      i--;
     }
    }
// Consume the terrain we're on
    if (terlist[ter(x, y)].flags & mfb(flammable) && one_in(8 - cur->density)) {
     cur->age -= cur->density * cur->density * 40;
     if (cur->density == 3)
      ter(x, y) = t_rubble;
    } else if (terlist[ter(x, y)].flags & mfb(explodes)) {
     ter(x, y) = ter_id(int(ter(x, y)) + 1);
     cur->age = 0;
     cur->density = 3;
     g->explosion(x, y, 40, 0, true);
    } else if (terlist[ter(x, y)].flags & mfb(swimmable))
     cur->age += 800;	// Flames die quickly on water
// If we consumed a lot, the flames grow higher
    while (cur->density < 3 && cur->age < 0) {
     cur->age += 300;
     cur->density++;
    }
// If the flames are REALLY big, they contribute to adjacent flames
    if (cur->density == 3 && cur->age < 0) {
// If the flames are in a pit, it can't spread to non-pit
     bool in_pit = (ter(x, y) == t_pit);
// Randomly offset our x/y shifts by 0-2, to randomly pick a square to spread to
     int starti = rng(0, 2);
     int startj = rng(0, 2);
     for (int i = 0; i < 3 && cur->age < 0; i++) {
      for (int j = 0; j < 3 && cur->age < 0; j++) {
       if (field_at(x+((i+starti)%3), y+((j+startj)%3)).type == fd_fire &&
           field_at(x+((i+starti)%3), y+((j+startj)%3)).density < 3 &&
           (!in_pit || ter(x+((i+starti)%3), y+((j+startj)%3)) == t_pit)) {
        field_at(x+((i+starti)%3), y+((j+startj)%3)).density++; 
        field_at(x+((i+starti)%3), y+((j+startj)%3)).age = 0;
        cur->age = 0;
       }
      }
     }
    }
// Consume adjacent fuel / terrain to spread.
    for (int i = -1; i <= 1; i++) {
     for (int j = -1; j <= 1; j++) {
      if (x+i >= 0 && y+j >= 0 && x+i < SEEX * 3 && y+j <= SEEY * 3) {
       if (has_flag(explodes, x + i, y + j) && one_in(8 - cur->density)) {
        ter(x + i, y + i) = ter_id(int(ter(x + i, y + i)) + 1);
        g->explosion(x+i, y+j, 40, 0, true);
       } else if ((i != 0 || j != 0) && (i_at(x+i, y+j).size() > 0 ||
                  rng(15, 120) < cur->density * 10)) {
        if (field_at(x+i, y+j).type == fd_smoke)
         field_at(x+i, y+j) = field(fd_fire, 1, 0);
// Fire in pits can only spread to adjacent pits
        else if (ter(x, y) != t_pit || ter(x + i, y + j) == t_pit)
         add_field(g, x+i, y+j, fd_fire, 1);
// If we're not spreading, maybe we'll stick out some smoke, huh?
       } else if (move_cost(x+i, y+j) > 0 &&
                  rng(7, 40) < cur->density * 10 && cur->age < 1000) {
        add_field(g, x+i, y+j, fd_smoke, rng(1, cur->density));
       }
      }
     }
    }
   break;
  
   case fd_smoke:
    for (int i = -1; i <= 1; i++) {
     for (int j = -1; j <= 1; j++)
      g->scent(x+i, y+j) = 0;
    }
    if (is_outside(x, y))
     cur->age += 50;
    if (one_in(2)) {
     std::vector <point> spread;
     for (int a = -1; a <= 1; a++) {
      for (int b = -1; b <= 1; b++) {
       if ((field_at(x+a, y+b).type == fd_smoke &&
             field_at(x+a, y+b).density < 3)      ||
           (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0))
        spread.push_back(point(x+a, y+b));
      }
     }
     if (cur->density > 0 && cur->age > 0 && spread.size() > 0) {
      point p = spread[rng(0, spread.size() - 1)];
      if (field_at(p.x, p.y).type == fd_smoke &&
          field_at(p.x, p.y).density < 3) {
        field_at(p.x, p.y).density++;
        cur->density--;
      } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 &&
                 add_field(g, p.x, p.y, fd_smoke, 1)){
       cur->density--;
       field_at(p.x, p.y).age = cur->age;
      }
     }
    }
   break;

   case fd_tear_gas:
// Reset nearby scents to zero
    for (int i = -1; i <= 1; i++) {
     for (int j = -1; j <= 1; j++)
      g->scent(x+i, y+j) = 0;
    }
    if (is_outside(x, y))
     cur->age += 30;
// One in three chance that it spreads (less than smoke!)
    if (one_in(3)) {
     std::vector <point> spread;
// Pick all eligible points to spread to
     for (int a = -1; a <= 1; a++) {
      for (int b = -1; b <= 1; b++) {
       if (((field_at(x+a, y+b).type == fd_smoke ||
             field_at(x+a, y+b).type == fd_tear_gas) &&
             field_at(x+a, y+b).density < 3            )      ||
           (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0))
        spread.push_back(point(x+a, y+b));
      }
     }
// Then, spread to a nearby point
     if (cur->density > 0 && cur->age > 0 && spread.size() > 0) {
      point p = spread[rng(0, spread.size() - 1)];
// Nearby teargas grows thicker
      if (field_at(p.x, p.y).type == fd_tear_gas &&
          field_at(p.x, p.y).density < 3) {
        field_at(p.x, p.y).density++;
        cur->density--;
// Nearby smoke is converted into teargas
      } else if (field_at(p.x, p.y).type == fd_smoke) {
       field_at(p.x, p.y).type = fd_tear_gas;
// Or, just create a new field.
      } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 &&
                 add_field(g, p.x, p.y, fd_tear_gas, 1)) {
       cur->density--;
       field_at(p.x, p.y).age = cur->age;
      }
     }
    }
    break;

   case fd_nuke_gas:
// Reset nearby scents to zero
    for (int i = -1; i <= 1; i++) {
     for (int j = -1; j <= 1; j++)
      g->scent(x+i, y+j) = 0;
    }
    if (is_outside(x, y))
     cur->age += 40;
// Increase long-term radiation in the land underneath
    radiation(x, y) += rng(0, cur->density);
    if (one_in(2)) {
     std::vector <point> spread;
// Pick all eligible points to spread to
     for (int a = -1; a <= 1; a++) {
      for (int b = -1; b <= 1; b++) {
       if (((field_at(x+a, y+b).type == fd_smoke ||
             field_at(x+a, y+b).type == fd_tear_gas ||
             field_at(x+a, y+b).type == fd_nuke_gas   ) &&
             field_at(x+a, y+b).density < 3            )      ||
           (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0))
        spread.push_back(point(x+a, y+b));
      }
     }
// Then, spread to a nearby point
     if (cur->density > 0 && cur->age > 0 && spread.size() > 0) {
      point p = spread[rng(0, spread.size() - 1)];
// Nearby nukegas grows thicker
      if (field_at(p.x, p.y).type == fd_nuke_gas &&
          field_at(p.x, p.y).density < 3) {
        field_at(p.x, p.y).density++;
        cur->density--;
// Nearby smoke & teargas is converted into nukegas
      } else if (field_at(p.x, p.y).type == fd_smoke ||
                 field_at(p.x, p.y).type == fd_tear_gas) {
       field_at(p.x, p.y).type = fd_nuke_gas;
// Or, just create a new field.
      } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 &&
                 add_field(g, p.x, p.y, fd_nuke_gas, 1)) {
       cur->density--;
       field_at(p.x, p.y).age = cur->age;
      }
     }
    }
    break;

   case fd_electricity:
    if (!one_in(5)) {	// 4 in 5 chance to spread
     std::vector<point> valid;
     if (move_cost(x, y) == 0 && cur->density > 1) { // We're grounded
      int tries = 0;
      while (tries < 10 && cur->age < 50) {
       int cx = x + rng(-1, 1), cy = y + rng(-1, 1);
       if (move_cost(cx, cy) != 0 && field_at(cx, cy).is_null()) {
        add_field(g, cx, cy, fd_electricity, 1);
        cur->density--;
        tries = 0;
       } else
        tries++;
      }
     } else {	// We're not grounded; attempt to ground
      for (int a = -1; a <= 1; a++) {
       for (int b = -1; b <= 1; b++) {
        if (move_cost(x + a, y + b) == 0 && // Grounded tiles first
            field_at(x + a, y + b).is_null())
         valid.push_back(point(x + a, y + b));
       }
      }
      if (valid.size() == 0) {	// Spread to adjacent space, then
       int px = x + rng(-1, 1), py = y + rng(-1, 1);
       if (move_cost(px, py) > 0 && field_at(px, py).type == fd_electricity &&
           field_at(px, py).density < 3)
        field_at(px, py).density++;
       else if (move_cost(px, py) > 0)
        add_field(g, px, py, fd_electricity, 1);
       cur->density--;
      }
      while (valid.size() > 0 && cur->density > 0) {
       int index = rng(0, valid.size() - 1);
       add_field(g, valid[index].x, valid[index].y, fd_electricity, 1);
       cur->density--;
       valid.erase(valid.begin() + index);
      }
     }
    }
    break;

   case fd_fatigue:
    if (cur->density < 3 && g->turn % 3600 == 0 && one_in(10))
     cur->density++;
    else if (cur->density == 3 && one_in(3600)) { // Spawn nether creature!
     mon_id type = mon_id(rng(mon_flying_polyp, mon_blank));
     monster creature(g->mtypes[type]);
     creature.spawn(x + rng(-3, 3), y + rng(-3, 3));
     g->z.push_back(creature);
    }
    break;
   }
  
   if (fieldlist[cur->type].halflife > 0) {
    cur->age++;
    if (cur->age > 0 &&
        dice(3, cur->age) > dice(3, fieldlist[cur->type].halflife)) {
     cur->age = 0;
     cur->density--;
    }
    if (cur->density <= 0) // Totally dissapated.
     field_at(x, y) = field();
   }
  }
 }
 return found_field;
}
Example #2
0
void monster::plan( const mfactions &factions )
{
    // Bots are more intelligent than most living stuff
    bool electronic = has_flag( MF_ELECTRONIC );
    Creature *target = nullptr;
    // 8.6f is rating for tank drone 60 tiles away, moose 16 or boomer 33
    float dist = !electronic ? 1000 : 8.6f;
    bool fleeing = false;
    bool docile = has_flag( MF_VERMIN ) || ( friendly != 0 && has_effect( "docile" ) );
    bool angers_hostile_weak = type->anger.find( MTRIG_HOSTILE_WEAK ) != type->anger.end();
    int angers_hostile_near = ( type->anger.find( MTRIG_HOSTILE_CLOSE ) != type->anger.end() ) ? 5 : 0;
    int fears_hostile_near = ( type->fear.find( MTRIG_HOSTILE_CLOSE ) != type->fear.end() ) ? 5 : 0;
    bool group_morale = has_flag( MF_GROUP_MORALE ) && morale < type->morale;
    bool swarms = has_flag( MF_SWARMS );
    auto mood = attitude();

    // If we can see the player, move toward them or flee.
    if( friendly == 0 && sees( g->u ) ) {
        dist = rate_target( g->u, dist, electronic );
        fleeing = fleeing || is_fleeing( g->u );
        target = &g->u;
        if( dist <= 5 ) {
            anger += angers_hostile_near;
            morale -= fears_hostile_near;
        }
    } else if( friendly != 0 && !docile ) {
        // Target unfriendly monsters, only if we aren't interacting with the player.
        for( int i = 0, numz = g->num_zombies(); i < numz; i++ ) {
            monster &tmp = g->zombie( i );
            if( tmp.friendly == 0 ) {
                float rating = rate_target( tmp, dist, electronic );
                if( rating < dist ) {
                    target = &tmp;
                    dist = rating;
                }
            }
        }
    }

    if( docile ) {
        if( friendly != 0 && target != nullptr ) {
            set_dest( target->pos() );
        }

        return;
    }

    for( size_t i = 0; i < g->active_npc.size(); i++ ) {
        npc *me = g->active_npc[i];
        float rating = rate_target( *me, dist, electronic );
        bool fleeing_from = is_fleeing( *me );
        // Switch targets if closer and hostile or scarier than current target
        if( ( rating < dist && fleeing ) ||
            ( rating < dist && attitude( me ) == MATT_ATTACK ) ||
            ( !fleeing && fleeing_from ) ) {
            target = me;
            dist = rating;
        }
        fleeing = fleeing || fleeing_from;
        if( rating <= 5 ) {
            anger += angers_hostile_near;
            morale -= fears_hostile_near;
        }
    }

    fleeing = fleeing || ( mood == MATT_FLEE );
    if( friendly == 0 ) {
        for( const auto &fac : factions ) {
            auto faction_att = faction.obj().attitude( fac.first );
            if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) {
                continue;
            }

            for( int i : fac.second ) { // mon indices
                monster &mon = g->zombie( i );
                float rating = rate_target( mon, dist, electronic );
                if( rating < dist ) {
                    target = &mon;
                    dist = rating;
                }
                if( rating <= 5 ) {
                    anger += angers_hostile_near;
                    morale -= fears_hostile_near;
                }
            }
        }
    }

    // Friendly monsters here
    // Avoid for hordes of same-faction stuff or it could get expensive
    const auto actual_faction = friendly == 0 ? faction : mfaction_str_id( "player" );
    auto const &myfaction_iter = factions.find( actual_faction );
    if( myfaction_iter == factions.end() ) {
        DebugLog( D_ERROR, D_GAME ) << disp_name() << " tried to find faction "
                                    << actual_faction.id().str() << " which wasn't loaded in game::monmove";
        swarms = false;
        group_morale = false;
    }
    swarms = swarms && target == nullptr; // Only swarm if we have no target
    if( group_morale || swarms ) {
        for( const int i : myfaction_iter->second ) {
            monster &mon = g->zombie( i );
            float rating = rate_target( mon, dist, electronic );
            if( group_morale && rating <= 10 ) {
                morale += 10 - rating;
            }
            if( swarms ) {
                if( rating < 5 ) { // Too crowded here
                    wander_pos.x = posx() * rng( 1, 3 ) - mon.posx();
                    wander_pos.y = posy() * rng( 1, 3 ) - mon.posy();
                    wandf = 2;
                    target = nullptr;
                    // Swarm to the furthest ally you can see
                } else if( rating < INT_MAX && rating > dist && wandf <= 0 ) {
                    target = &mon;
                    dist = rating;
                }
            }
        }
    }

    if( target != nullptr ) {

        tripoint dest = target->pos();
        auto att_to_target = attitude_to( *target );
        if( att_to_target == Attitude::A_HOSTILE && !fleeing ) {
            set_dest( dest );
        } else if( fleeing ) {
            set_dest( tripoint( posx() * 2 - dest.x, posy() * 2 - dest.y, posz() ) );
        }
        if( angers_hostile_weak && att_to_target != Attitude::A_FRIENDLY ) {
            int hp_per = target->hp_percentage();
            if( hp_per <= 70 ) {
                anger += 10 - int( hp_per / 10 );
            }
        }
    } else if( friendly > 0 && one_in( 3 ) ) {
        // Grow restless with no targets
        friendly--;
    } else if( friendly < 0 && sees( g->u ) ) {
        if( rl_dist( pos3(), g->u.pos3() ) > 2 ) {
            set_dest( g->u.pos3() );
        } else {
            unset_dest();
        }
    }
}
Example #3
0
bool monster::move_to( const tripoint &p, bool force, float slope )
{
    const bool digs = digging();
    const bool flies = has_flag( MF_FLIES );
    const bool on_ground = !digs && !flies;
    const bool climbs = has_flag( MF_CLIMBS ) && g->m.has_flag( TFLAG_NO_FLOOR, p );
    // Allows climbing monsters to move on terrain with movecost <= 0
    Creature *critter = g->critter_at( p, is_hallucination() );
    if( g->m.has_flag( "CLIMBABLE", p ) ) {
        if( g->m.move_cost( p ) == 0 && critter == nullptr ) {
            if( flies ) {
                moves -= 100;
                force = true;
                if (g->u.sees( *this )){
                    add_msg(_("The %1$s flies over the %2$s."), name().c_str(),
                    g->m.has_flag_furn("CLIMBABLE", p) ? g->m.furnname(p).c_str() : g->m.tername(p).c_str());
                }
            } else if (has_flag(MF_CLIMBS)) {
                moves -= 150;
                force = true;
                if (g->u.sees( *this )){
                    add_msg(_("The %1$s climbs over the %2$s."), name().c_str(),
                    g->m.has_flag_furn("CLIMBABLE", p) ? g->m.furnname(p).c_str() : g->m.tername(p).c_str());
                }
            }
        }
    }

    if( critter != nullptr && !force ) {
        return false;
    }

    // Make sure that we can move there, unless force is true.
    if( !force && !can_move_to( p ) ) {
        return false;
    }

    if( !force ) {
        // This adjustment is to make it so that monster movement speed relative to the player
        // is consistent even if the monster stumbles,
        // and the same regardless of the distance measurement mode.
        const float stumble_multiplier = has_flag(MF_STUMBLES) ?
            (trigdist ? 0.83 : 1.0 - (0.25 * slope)) : 1.0;
        const int cost = stumble_multiplier *
            (float)(climbs ? calc_climb_cost( pos(), p ) : calc_movecost( pos(), p ));

       if( cost > 0 ) {
            moves -= cost;
        } else {
            return false;
        }
    }

    //Check for moving into/out of water
    bool was_water = g->m.is_divable( pos3() );
    bool will_be_water = on_ground && can_submerge() && g->m.is_divable( p );

    if( was_water && !will_be_water && g->u.sees( p ) ) {
        //Use more dramatic messages for swimming monsters
        add_msg( m_warning, _( "A %1$s %2$s from the %3$s!" ), name().c_str(),
                 has_flag( MF_SWIMS ) || has_flag( MF_AQUATIC ) ? _( "leaps" ) : _( "emerges" ),
                 g->m.tername( pos() ).c_str() );
    } else if( !was_water && will_be_water && g->u.sees( p ) ) {
        add_msg( m_warning, _( "A %1$s %2$s into the %3$s!" ), name().c_str(),
                 has_flag( MF_SWIMS ) || has_flag( MF_AQUATIC ) ? _( "dives" ) : _( "sinks" ),
                 g->m.tername( p ).c_str() );
    }

    setpos( p );
    footsteps( p );
    underwater = will_be_water;
    if( is_hallucination() ) {
        //Hallucinations don't do any of the stuff after this point
        return true;
    }
    // TODO: Make tanks stop taking damage from rubble, because it's just silly
    if( type->size != MS_TINY && on_ground ) {
        if( g->m.has_flag( "SHARP", pos() ) && !one_in( 4 ) ) {
            apply_damage( nullptr, bp_torso, rng( 1, 10 ) );
        }
        if( g->m.has_flag( "ROUGH", pos() ) && one_in( 6 ) ) {
            apply_damage( nullptr, bp_torso, rng( 1, 2 ) );
        }

    }

    if( g->m.has_flag( "UNSTABLE", p ) && on_ground ) {
        add_effect( "bouldering", 1, num_bp, true );
    } else if( has_effect( "bouldering" ) ) {
        remove_effect( "bouldering" );
    }
    g->m.creature_on_trap( *this );
    if( !will_be_water && ( has_flag( MF_DIGS ) || has_flag( MF_CAN_DIG ) ) ) {
        underwater = g->m.has_flag( "DIGGABLE", pos() );
    }
    // Diggers turn the dirt into dirtmound
    if( digging() ) {
        int factor = 0;
        switch( type->size ) {
            case MS_TINY:
                factor = 100;
                break;
            case MS_SMALL:
                factor = 30;
                break;
            case MS_MEDIUM:
                factor = 6;
                break;
            case MS_LARGE:
                factor = 3;
                break;
            case MS_HUGE:
                factor = 1;
                break;
        }
        if( has_flag( MF_VERMIN ) ) {
            factor *= 100;
        }
        if( one_in( factor ) ) {
            g->m.ter_set( pos(), t_dirtmound );
        }
    }
    // Acid trail monsters leave... a trail of acid
    if( has_flag( MF_ACIDTRAIL ) ) {
        g->m.add_field( pos(), fd_acid, 3, 0 );
    }

    if( has_flag( MF_SLUDGETRAIL ) ) {
        for( const tripoint &sludge_p : g->m.points_in_radius( pos(), 1 ) ) {
            const int fstr = 3 - ( abs( sludge_p.x - posx() ) + abs( sludge_p.y - posy() ) );
            if( fstr >= 2 ) {
                g->m.add_field( sludge_p, fd_sludge, fstr, 0 );
            }
        }
    }
    if( has_flag( MF_LEAKSGAS ) ) {
        if( one_in( 6 ) ) {
            tripoint dest( posx() + rng( -1, 1 ), posy() + rng( -1, 1 ), posz() );
            g->m.add_field( dest, fd_toxic_gas, 3, 0 );
        }
    }

    return true;
}
void activity_handlers::make_zlave_finish( player_activity *act, player *p )
{
    static const int full_pulp_threshold = 4;

    auto items = g->m.i_at(p->pos());
    std::string corpse_name = act->str_values[0];
    item *body = NULL;

    for( auto it = items.begin(); it != items.end(); ++it ) {
        if( it->display_name() == corpse_name ) {
            body = &*it;
        }
    }

    if( body == NULL ) {
        add_msg(m_info, _("There's no corpse to make into a zombie slave!"));
        return;
    }

    int success = act->values[0];

    if( success > 0 ) {

        p->practice("firstaid", rng(2, 5));
        p->practice("survival", rng(2, 5));

        p->add_msg_if_player(m_good,
                             _("You slice muscles and tendons, and remove body parts until you're confident the zombie won't be able to attack you when it reainmates."));

        body->set_var( "zlave", "zlave" );
        //take into account the chance that the body yet can regenerate not as we need.
        if( one_in(10) ) {
            body->set_var( "zlave", "mutilated" );
        }

    } else {

        if( success > -20 ) {

            p->practice("firstaid", rng(3, 6));
            p->practice("survival", rng(3, 6));

            p->add_msg_if_player(m_warning,
                                 _("You hack into the corpse and chop off some body parts.  You think the zombie won't be able to attack when it reanimates."));

            success += rng(1, 20);

            if( success > 0 && !one_in(5) ) {
                body->set_var( "zlave", "zlave" );
            } else {
                body->set_var( "zlave", "mutilated" );
            }

        } else {

            p->practice("firstaid", rng(1, 8));
            p->practice("survival", rng(1, 8));

            int pulp = rng(1, full_pulp_threshold);

            body->damage += pulp;

            if( body->damage >= full_pulp_threshold ) {
                body->damage = full_pulp_threshold;
                body->active = false;

                p->add_msg_if_player(m_warning, _("You cut up the corpse too much, it is thoroughly pulped."));
            } else {
                p->add_msg_if_player(m_warning,
                                     _("You cut into the corpse trying to make it unable to attack, but you don't think you have it right."));
            }
        }
    }
}
void activity_handlers::butcher_finish( player_activity *act, player *p )
{
    // corpses can disappear (rezzing!), so check for that
    if( static_cast<int>(g->m.i_at(p->pos()).size()) <= act->index ||
        !(g->m.i_at(p->pos())[act->index].is_corpse() ) ) {
        add_msg(m_info, _("There's no corpse to butcher!"));
        return;
    }
    const mtype *corpse = g->m.i_at(p->pos())[act->index].get_mtype();
    std::vector<item> contents = g->m.i_at(p->pos())[act->index].contents;
    int age = g->m.i_at(p->pos())[act->index].bday;
    g->m.i_rem(p->pos(), act->index);
    int factor = p->butcher_factor();
    int pieces = 0, skins = 0, bones = 0, fats = 0, sinews = 0, feathers = 0;
    bool stomach = false;

    switch (corpse->size) {
    case MS_TINY:
        pieces = 1;
        skins = 1;
        bones = 1;
        fats = 1;
        sinews = 1;
        feathers = 2;
        break;
    case MS_SMALL:
        pieces = 2;
        skins = 2;
        bones = 4;
        fats = 2;
        sinews = 4;
        feathers = 6;
        break;
    case MS_MEDIUM:
        pieces = 4;
        skins = 4;
        bones = 9;
        fats = 4;
        sinews = 9;
        feathers = 11;
        break;
    case MS_LARGE:
        pieces = 8;
        skins = 8;
        bones = 14;
        fats = 8;
        sinews = 14;
        feathers = 17;
        break;
    case MS_HUGE:
        pieces = 16;
        skins = 16;
        bones = 21;
        fats = 16;
        sinews = 21;
        feathers = 24;
        break;
    }

    int sSkillLevel = p->skillLevel("survival");

    auto roll_butchery = [&] () {
        double skill_shift = 0.;
        skill_shift += rng_float( 0, sSkillLevel - 3 );
        skill_shift += rng_float( 0, p->dex_cur - 8 ) / 4.0;
        if( p->str_cur < 4 ) {
            skill_shift -= rng_float( 0, 5 * ( 4 - p->str_cur ) ) / 4.0;
        }

        if( factor < 0 ) {
            skill_shift -= rng_float( 0, -factor / 5.0 );
        }

        return static_cast<int>( skill_shift );
    };

    int practice = std::max( 0, 4 + pieces + roll_butchery());

    p->practice("survival", practice);

    // Lose some meat, skins, etc if the rolls are low
    pieces +=   std::min( 0, roll_butchery() );
    skins +=    std::min( 0, roll_butchery() - 4 );
    bones +=    std::min( 0, roll_butchery() - 2 );
    fats +=     std::min( 0, roll_butchery() - 4 );
    sinews +=   std::min( 0, roll_butchery() - 8 );
    feathers += std::min( 0, roll_butchery() - 1 );
    stomach = (roll_butchery() >= 0);

    if( bones > 0 ) {
        if( corpse->has_material("veggy") ) {
            g->m.spawn_item(p->pos(), "plant_sac", bones, 0, age);
            add_msg(m_good, _("You harvest some fluid bladders!"));
        } else if( corpse->has_flag(MF_BONES) && corpse->has_flag(MF_POISON) ) {
            g->m.spawn_item(p->pos(), "bone_tainted", bones / 2, 0, age);
            add_msg(m_good, _("You harvest some salvageable bones!"));
        } else if( corpse->has_flag(MF_BONES) && corpse->has_flag(MF_HUMAN) ) {
            g->m.spawn_item(p->pos(), "bone_human", bones, 0, age);
            add_msg(m_good, _("You harvest some salvageable bones!"));
        } else if( corpse->has_flag(MF_BONES) ) {
            g->m.spawn_item(p->pos(), "bone", bones, 0, age);
            add_msg(m_good, _("You harvest some usable bones!"));
        }
    }

    if( sinews > 0 ) {
        if( corpse->has_flag(MF_BONES) && !corpse->has_flag(MF_POISON) ) {
            g->m.spawn_item(p->pos(), "sinew", sinews, 0, age);
            add_msg(m_good, _("You harvest some usable sinews!"));
        } else if( corpse->has_material("veggy") ) {
            g->m.spawn_item(p->pos(), "plant_fibre", sinews, 0, age);
            add_msg(m_good, _("You harvest some plant fibers!"));
        }
    }

    if( stomach ) {
        const itype_id meat = corpse->get_meat_itype();
        if( meat == "meat" ) {
            if( corpse->size == MS_SMALL || corpse->size == MS_MEDIUM ) {
                g->m.spawn_item(p->pos(), "stomach", 1, 0, age);
                add_msg(m_good, _("You harvest the stomach!"));
            } else if( corpse->size == MS_LARGE || corpse->size == MS_HUGE ) {
                g->m.spawn_item(p->pos(), "stomach_large", 1, 0, age);
                add_msg(m_good, _("You harvest the stomach!"));
            }
        }
    }

    if( (corpse->has_flag(MF_FUR) || corpse->has_flag(MF_LEATHER) ||
         corpse->has_flag(MF_CHITIN)) && skins > 0 ) {
        add_msg(m_good, _("You manage to skin the %s!"), corpse->nname().c_str());
        int fur = 0;
        int leather = 0;
        int chitin = 0;

        while (skins > 0 ) {
            if( corpse->has_flag(MF_CHITIN) ) {
                chitin = rng(0, skins);
                skins -= chitin;
                skins = std::max(skins, 0);
            }
            if( corpse->has_flag(MF_FUR) ) {
                fur = rng(0, skins);
                skins -= fur;
                skins = std::max(skins, 0);
            }
            if( corpse->has_flag(MF_LEATHER) ) {
                leather = rng(0, skins);
                skins -= leather;
                skins = std::max(skins, 0);
            }
        }

        if( chitin ) {
            g->m.spawn_item(p->pos(), "chitin_piece", chitin, 0, age);
        }
        if( fur ) {
            g->m.spawn_item(p->pos(), "raw_fur", fur, 0, age);
        }
        if( leather ) {
            g->m.spawn_item(p->pos(), "raw_leather", leather, 0, age);
        }
    }

    if( feathers > 0 ) {
        if( corpse->has_flag(MF_FEATHER) ) {
            g->m.spawn_item(p->pos(), "feather", feathers, 0, age);
            add_msg(m_good, _("You harvest some feathers!"));
        }
    }

    if( fats > 0 ) {
        if( corpse->has_flag(MF_FAT) && corpse->has_flag(MF_POISON) ) {
            g->m.spawn_item(p->pos(), "fat_tainted", fats, 0, age);
            add_msg(m_good, _("You harvest some gooey fat!"));
        } else if( corpse->has_flag(MF_FAT) ) {
            g->m.spawn_item(p->pos(), "fat", fats, 0, age);
            add_msg(m_good, _("You harvest some fat!"));
        }
    }

    //Add a chance of CBM recovery. For shocker and cyborg corpses.
    //As long as the factor is above -4 (the sinew cutoff), you will be able to extract cbms
    bool any_cbm = false;
    bool cbm = false;
    if( corpse->has_flag(MF_CBM_CIV) ) {
        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_item( "bio_power_storage", p->pos3(), age ) || cbm;
        }

        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_group( "bionics_common", p->pos3(), age ) || cbm;
        }
    }

    // Zombie scientist bionics
    if( corpse->has_flag(MF_CBM_SCI) ) {
        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_item( "bio_power_storage", p->pos3(), age ) || cbm;
        }

        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_group( "bionics_sci", p->pos3(), age ) || cbm;
        }
    }

    // Zombie technician bionics
    if( corpse->has_flag(MF_CBM_TECH) ) {
        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_item( "bio_power_storage", p->pos3(), age ) || cbm;
        }

        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_group( "bionics_tech", p->pos3(), age ) || cbm;
        }
    }

    // Substation mini-boss bionics
    if( corpse->has_flag(MF_CBM_SUBS) ) {
        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_item( "bio_power_storage", p->pos3(), age ) || cbm;
        }

        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_group( "bionics_subs", p->pos3(), age ) || cbm;
        }

        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_group( "bionics_subs", p->pos3(), age ) || cbm;
        }
    }

    // Payoff for butchering the zombie bio-op
    if( corpse->has_flag(MF_CBM_OP) ) {
        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_item( "bio_power_storage_mkII", p->pos3(), age ) || cbm;
        }

        if( roll_butchery() >= 0 ) {
            any_cbm = true;
            cbm = butcher_cbm_group( "bionics_op", p->pos3(), age ) || cbm;
        }
    }

    if( cbm ) {
        add_msg( m_good, _("You discover a CBM in the %s!"), corpse->nname().c_str() );
    } else if( any_cbm ) {
        add_msg( m_good, _("You discover a fused lump of bio-circuitry in the %s!"),
                         corpse->nname().c_str() );
    }

    //Add a chance of CBM power storage recovery.
    if( corpse->has_flag(MF_CBM_POWER) ) {
        //As long as the factor is above -4 (the sinew cutoff), you will be able to extract cbms
        if( roll_butchery() >= 0 ) {
            //To see if it spawns a battery
            if( one_in(3) ) { //The battery works 33% of the time.
                add_msg(m_good, _("You discover a power storage in the %s!"), corpse->nname().c_str());
                g->m.spawn_item( p->pos3(), "bio_power_storage", 1, 0, age);
            } else { //There is a burnt out CBM
                add_msg(m_good, _("You discover a fused lump of bio-circuitry in the %s!"),
                        corpse->nname().c_str());
                g->m.spawn_item( p->pos3(), "burnt_out_bionic", 1, 0, age);
            }
        }
    }


    // Recover hidden items
    for( auto &content : contents  ) {
        if( ( roll_butchery() + 10 ) * 5 > rng( 0, 100 ) ) {
            add_msg( m_good, _( "You discover a %s in the %s!" ), content.tname().c_str(),
                     corpse->nname().c_str() );
            g->m.add_item_or_charges( p->pos(), content );
        } else if( content.is_bionic()  ) {
            g->m.spawn_item(p->pos(), "burnt_out_bionic", 1, 0, age);
        }
    }

    if( pieces <= 0 ) {
        add_msg(m_bad, _("Your clumsy butchering destroys the meat!"));
    } else {
        add_msg(m_good, _("You butcher the corpse."));
        const itype_id meat = corpse->get_meat_itype();
        if( meat == "null" ) {
            return;
        }
        item tmpitem(meat, age);
        tmpitem.set_mtype( corpse );
        while ( pieces > 0 ) {
            pieces--;
            g->m.add_item_or_charges(p->pos(), tmpitem);
        }
    }
}
Example #6
0
// General movement.
// Currently, priority goes:
// 1) Special Attack
// 2) Sight-based tracking
// 3) Scent-based tracking
// 4) Sound-based tracking
void monster::move()
{
    // We decrement wandf no matter what.  We'll save our wander_to plans until
    // after we finish out set_dest plans, UNLESS they time out first.
    if( wandf > 0 ) {
        wandf--;
    }

    //Hallucinations have a chance of disappearing each turn
    if( is_hallucination() && one_in( 25 ) ) {
        die( nullptr );
        return;
    }

    //The monster can consume objects it stands on. Check if there are any.
    //If there are. Consume them.
    if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( "SEALED", pos() ) ) {
        if( !g->m.i_at( pos3() ).empty() ) {
            add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ),
                     name().c_str() );
            for( auto &elem : g->m.i_at( pos3() ) ) {
                hp += elem.volume(); // Yeah this means it can get more HP than normal.
            }
            g->m.i_clear( pos3() );
        }
    }

    // First, use the special attack, if we can!
    for( size_t i = 0; i < sp_timeout.size(); ++i ) {
        if( sp_timeout[i] > 0 ) {
            sp_timeout[i]--;
        }

        if( sp_timeout[i] == 0 && !has_effect( "pacified" ) && !is_hallucination() ) {
            type->sp_attack[i]( this, i );
        }
    }

    // The monster can sometimes hang in air due to last fall being blocked
    if( has_flag( MF_FLIES ) && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) {
        g->m.creature_on_trap( *this, false );
    }

    if( moves < 0 ) {
        return;
    }

    bool attacking = false;
    if( !move_effects(attacking) ) {
        moves = 0;
        return;
    }
    if( has_flag( MF_IMMOBILE ) ) {
        moves = 0;
        return;
    }
    if( has_effect( "stunned" ) ) {
        stumble( false );
        moves = 0;
        return;
    }
    if( friendly != 0 ) {
        if( friendly > 0 ) {
            friendly--;
        }
        friendly_move();
        return;
    }

    bool moved = false;
    tripoint next;

    // Set attitude to attitude to our current target
    monster_attitude current_attitude = attitude( nullptr );
    if( !plans.empty() ) {
        if( plans.back() == g->u.pos3() ) {
            current_attitude = attitude( &( g->u ) );
        } else {
            for( auto &i : g->active_npc ) {
                if( plans.back() == i->pos3() ) {
                    current_attitude = attitude( i );
                }
            }
        }
    }

    if( current_attitude == MATT_IGNORE ||
        ( current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST ) ) {
        moves -= 100;
        stumble( false );
        return;
    }

    // Fix possibly invalid plans
    // Also make sure the monster won't act across z-levels when it shouldn't.
    // Don't do it in plan(), because the mon can still use ranged special attacks using
    // the plans that are not valid for travel/melee.
    const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES );
    const bool can_fly = has_flag( MF_FLIES );
    if( !plans.empty() &&
        !g->m.valid_move( pos(), plans[0], can_bash, can_fly ) ) {
        plans.clear();
    }

    int mondex = !plans.empty() ? g->mon_at( plans[0] ) : -1;
    auto mon_att = mondex != -1 ? attitude_to( g->zombie( mondex ) ) : A_HOSTILE;

    if( !plans.empty() &&
        ( mon_att == A_HOSTILE || has_flag( MF_ATTACKMON ) || has_flag( MF_PUSH_MON ) ) &&
        ( can_move_to( plans[0] ) ||
          ( plans[0] == g->u.pos3() ) ||
          ( ( has_flag( MF_BASHES ) || has_flag( MF_BORES ) ) &&
            g->m.bash_rating( bash_estimate(), plans[0] ) >= 0 ) ) ) {
        // CONCRETE PLANS - Most likely based on sight
        next = plans[0];
        moved = true;
    } else if( has_flag( MF_SMELLS ) ) {
        // No sight... or our plans are invalid (e.g. moving through a transparent, but
        //  solid, square of terrain).  Fall back to smell if we have it.
        plans.clear();
        tripoint tmp = scent_move();
        if( tmp.x != -1 ) {
            next = tmp;
            moved = true;
        }
    }
    if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound
        plans.clear();
        tripoint tmp = wander_next();
        if( tmp != pos() ) {
            next = tmp;
            moved = true;
        }
    }

    // Finished logic section.  By this point, we should have chosen a square to
    //  move to (moved = true).
    if( moved ) { // Actual effects of moving to the square we've chosen
        // Note: The below works because C++ in A() || B() won't call B() if A() is true
        bool did_something = attack_at( next ) || bash_at( next ) ||
                             push_to( next, 0, 0 ) || move_to( next );
        if( !did_something ) {
            moves -= 100; // If we don't do this, we'll get infinite loops.
        }
    } else {
        moves -= 100;
    }

    // If we're close to our target, we get focused and don't stumble
    if( ( has_flag( MF_STUMBLES ) && ( plans.size() > 3 || plans.empty() ) ) ||
        !moved ) {
        stumble( moved );
    }
}
Example #7
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" ),
    }};
    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();
    const itype_id meat = z->type->get_meat_itype();

    if( pulverized && gibbable ) {
        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 #8
0
bool player::hit_player(player &p, body_part &bp, int &hitdam, int &hitcut)
{
// TODO: Add bionics and other bonus (e.g. heat drain, shock, etc)
 if (!is_npc() && p.is_npc()) {
  npc *foe = dynamic_cast<npc*>(&p);
  foe->make_angry();
 }
 bool unarmed = unarmed_attack(), bashing = weapon.is_bashing_weapon(),
      cutting = weapon.is_cutting_weapon();
 int hitit = hit_roll() - p.dodge_roll();
 if (hitit < 0) {	// They dodged
  practice(sk_melee, rng(2, 4));
  if (unarmed)
   practice(sk_unarmed, 3);
  if (bashing)
   practice(sk_bashing, 1);
  if (cutting)
   practice(sk_cutting, 2);
  return false;
 }

 if (hitit >= 15)
  bp = bp_eyes;
 else if (hitit >= 12)
  bp = bp_mouth;
 else if (hitit >= 10)
  bp = bp_head;
 else if (hitit >= 6)
  bp = bp_torso;
 else if (hitit >= 2)
  bp = bp_arms;
 else
  bp = bp_legs;
 
 hitdam = base_damage();

 if (unarmed) {// Unarmed bonuses
  hitdam += rng(0, sklevel[sk_unarmed]);
  if (sklevel[sk_unarmed] >= 5)
   hitdam += rng(sklevel[sk_unarmed], 3 * sklevel[sk_unarmed]);
  if (has_trait(PF_TALONS))
   hitcut += 10;
  if (sklevel[sk_unarmed] >= 8 &&
      (one_in(3) || rng(5, 20) < sklevel[sk_unarmed]))
   hitdam *= rng(2, 3);
 }
// Weapon adds (melee_dam / 4) to (melee_dam)
 hitdam += rng(weapon.damage_bash() / 4, weapon.damage_bash());
 if (bashing)
  hitdam += rng(0, sklevel[sk_bashing]) * sqrt(double(str_cur));

 hitdam += int(pow(1.5, double(sklevel[sk_melee])));
 hitcut = weapon.damage_cut();
 if (hitcut > 0)
  hitcut += int(sklevel[sk_cutting] / 3);
 if (hitdam < 0) hitdam = 0;
 if (hitdam > 0 || hitcut > 0) { // Practicing
  practice(sk_melee, rng(5, 10));
  if (unarmed)
   practice(sk_unarmed, rng(5, 10));
  if (bashing)
   practice(sk_bashing, rng(5, 10));
  if (cutting)
   practice(sk_cutting, rng(5, 10));
 } else { // Less practice if we missed
  practice(sk_melee, rng(2, 5));
  if (unarmed)
   practice(sk_unarmed, 2);
  if (bashing)
   practice(sk_bashing, 2);
  if (cutting)
   practice(sk_cutting, 3);
 }
 return true;
}
Example #9
0
bool player::scored_crit(int target_dodge)
{
 bool to_hit_crit = false, dex_crit = false, skill_crit = false;
 int num_crits = 0;

 int chance = 25;
 if (weapon.type->m_to_hit > 0) {
  for (int i = 1; i <= weapon.type->m_to_hit; i++)
   chance += (50 / (2 + i));
 } else if (chance < 0) {
  for (int i = 0; i > weapon.type->m_to_hit; i--)
   chance /= 2;
 }
 if (rng(0, 99) < chance)
  num_crits++;

 chance = 25;
 if (dex_cur > 8) {
  for (int i = 9; i <= dex_cur; i++)
   chance += (21 - i); // 12, 11, 10...
 } else {
  int decrease = 5;
  for (int i = 7; i >= dex_cur; i--) {
   chance -= decrease;
   if (i % 2 == 0)
    decrease--;
  }
 }
 if (rng(0, 99) < chance)
  num_crits++;

 int best_skill = 0;
 if (weapon.is_bashing_weapon() && sklevel[sk_bashing] > best_skill)
  best_skill = sklevel[sk_bashing];
 if (weapon.is_cutting_weapon() && sklevel[sk_cutting] > best_skill)
  best_skill = sklevel[sk_cutting];
 if ((weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) &&
     sklevel[sk_stabbing] > best_skill)
  best_skill = sklevel[sk_stabbing];
 if (unarmed_attack() && sklevel[sk_unarmed] > best_skill)
  best_skill = sklevel[sk_unarmed];

 best_skill += int(sklevel[sk_melee] / 2.5);

 chance = 25;
 if (best_skill > 3) {
  for (int i = 3; i < best_skill; i++)
   chance += (50 / (2 + i));
 } else if (chance < 3) {
  for (int i = 3; i > best_skill; i--)
   chance /= 2;
 }
 if (rng(0, 99) < chance)
  num_crits++;

 if (num_crits == 3)
  return true;
 else if (num_crits == 2)
  return (hit_roll() >= target_dodge * 1.5 && !one_in(4));

 return false;
}
Example #10
0
ter_id grass_or_dirt()
{
 if (one_in(4))
  return t_grass;
 return t_dirt;
}
Example #11
0
ter_id dirt_or_pile()
{
 if (one_in(4))
  return t_dirtmound;
 return t_dirt;
}
Example #12
0
void mapgen_forest_general(map *m, oter_id terrain_type, mapgendata dat, int turn)
{
    switch (terrain_type)
    {
        case ot_forest_thick:
            dat.fill(8);
            break;
        case ot_forest_water:
            dat.fill(4);
            break;
        case ot_forest:
            dat.fill(0);
            break;
    }
    for (int i = 0; i < 4; i++)
    {
        if (dat.t_nesw[i] == ot_forest || dat.t_nesw[i] == ot_forest_water)
        {
            dat.dir(i) += 14;
        }
        else if (dat.t_nesw[i] == ot_forest_thick)
        {
            dat.dir(i) += 18;
        }
    }
    for (int i = 0; i < SEEX * 2; i++)
    {
        for (int j = 0; j < SEEY * 2; j++)
        {
            int forest_chance = 0, num = 0;
            if (j < dat.n_fac)
            {
                forest_chance += dat.n_fac - j;
                num++;
            }
            if (SEEX * 2 - 1 - i < dat.e_fac)
            {
                forest_chance += dat.e_fac - (SEEX * 2 - 1 - i);
                num++;
            }
            if (SEEY * 2 - 1 - j < dat.s_fac)
            {
                forest_chance += dat.s_fac - (SEEY * 2 - 1 - j);
                num++;
            }
            if (i < dat.w_fac)
            {
                forest_chance += dat.w_fac - i;
                num++;
            }
            if (num > 0)
            {
                forest_chance /= num;
            }
            int rn = rng(0, forest_chance);
            if ((forest_chance > 0 && rn > 13) || one_in(100 - forest_chance))
            {
                if (one_in(250))
                {
                    m->ter_set(i, j, t_tree_apple);
                    m->spawn_item(i, j, "apple", turn);
                }
                else
                {
                    m->ter_set(i, j, t_tree);
                }
            }
            else if ((forest_chance > 0 && rn > 10) || one_in(100 - forest_chance))
            {
                m->ter_set(i, j, t_tree_young);
            }
            else if ((forest_chance > 0 && rn >  9) || one_in(100 - forest_chance))
            {
                if (one_in(250))
                {
                    m->ter_set(i, j, t_shrub_blueberry);
                }
                else
                {
                    m->ter_set(i, j, t_underbrush);
                }
            }
            else
            {
                m->ter_set(i, j, grass_or_dirt());
            }
        }
    }
    m->place_items("forest", 60, 0, 0, SEEX * 2 - 1, SEEY * 2 - 1, true, turn);

    if (terrain_type == ot_forest_water)
    {
        // Reset *_fac to handle where to place water
        for (int i = 0; i < 4; i++)
        {
            if (dat.t_nesw[i] == ot_forest_water)
            {
                dat.set_dir(i, 2);
            }
            else if (dat.t_nesw[i] >= ot_river_center && dat.t_nesw[i] <= ot_river_nw)
            {
                dat.set_dir(i, 3);
            }
            else if (dat.t_nesw[i] == ot_forest || dat.t_nesw[i] == ot_forest_thick)
            {
                dat.set_dir(i, 1);
            }
            else
            {
                dat.set_dir(i, 0);
            }
        }
        int x = SEEX / 2 + rng(0, SEEX), y = SEEY / 2 + rng(0, SEEY);
        for (int i = 0; i < 20; i++)
        {
            if (x >= 0 && x < SEEX * 2 && y >= 0 && y < SEEY * 2)
            {
                if (m->ter(x, y) == t_water_sh)
                {
                    m->ter_set(x, y, t_water_dp);
                }
                else if (m->ter(x, y) == t_dirt || m->ter(x, y) == t_grass ||
                         m->ter(x, y) == t_underbrush)
                {
                    m->ter_set(x, y, t_water_sh);
                }
            }
            else
            {
                i = 20;
            }
            x += rng(-2, 2);
            y += rng(-2, 2);
            if (x < 0 || x >= SEEX * 2)
            {
                x = SEEX / 2 + rng(0, SEEX);
            }
            if (y < 0 || y >= SEEY * 2)
            {
                y = SEEY / 2 + rng(0, SEEY);
            }
            for (int j = 0; j < dat.n_fac; j++)
            {
                int wx = rng(0, SEEX * 2 -1), wy = rng(0, SEEY - 1);
                if (m->ter(wx, wy) == t_dirt || m->ter(wx, wy) == t_grass ||
                    m->ter(wx, wy) == t_underbrush)
                {
                    m->ter_set(wx, wy, t_water_sh);
                }
            }
            for (int j = 0; j < dat.e_fac; j++)
            {
                int wx = rng(SEEX, SEEX * 2 - 1), wy = rng(0, SEEY * 2 - 1);
                if (m->ter(wx, wy) == t_dirt || m->ter(wx, wy) == t_grass ||
                    m->ter(wx, wy) == t_underbrush)
                {
                    m->ter_set(wx, wy, t_water_sh);
                }
            }
            for (int j = 0; j < dat.s_fac; j++)
            {
                int wx = rng(0, SEEX * 2 - 1), wy = rng(SEEY, SEEY * 2 - 1);
                if (m->ter(wx, wy) == t_dirt || m->ter(wx, wy) == t_grass ||
                    m->ter(wx, wy) == t_underbrush)
                {
                    m->ter_set(wx, wy, t_water_sh);
                }
            }
            for (int j = 0; j < dat.w_fac; j++)
            {
                int wx = rng(0, SEEX - 1), wy = rng(0, SEEY * 2 - 1);
                if (m->ter(wx, wy) == t_dirt || m->ter(wx, wy) == t_grass ||
                    m->ter(wx, wy) == t_underbrush)
                {
                    m->ter_set(wx, wy, t_water_sh);
                }
            }
        }
        int rn = rng(0, 2) * rng(0, 1) * (rng(0, 1) + rng(0, 1));// Good chance of 0
        for (int i = 0; i < rn; i++)
        {
            x = rng(0, SEEX * 2 - 1);
            y = rng(0, SEEY * 2 - 1);
            m->add_trap(x, y, tr_sinkhole);
            if (m->ter(x, y) != t_water_sh)
            {
                m->ter_set(x, y, grass_or_dirt());
            }
        }
    }

    if (one_in(10000)) {  //1-2 per overmap, very bad day for low level characters
        m->add_spawn("mon_jabberwock", 1, SEEX, SEEY);
    }

    if (one_in(1000000)) {  //Very rare easter egg, ~1 per 10 overmaps
        m->add_spawn("mon_shia", 1, SEEX, SEEY);
    }



    if (one_in(100)) // One in 100 forests has a spider living in it :o
    {
        for (int i = 0; i < SEEX * 2; i++)
        {
            for (int j = 0; j < SEEX * 2; j++)
            {
                if ((m->ter(i, j) == t_dirt || m->ter(i, j) == t_grass ||
                     m->ter(i, j) == t_underbrush) && !one_in(3))
                {
                    m->add_field(NULL, i, j, fd_web, rng(1, 3));
                }
            }
        }
        m->add_spawn("mon_spider_web", rng(1, 2), SEEX, SEEY);
    }
}
Example #13
0
void player::mutate(game *g)
{
    bool force_bad = one_in(3);
    bool force_good = false;
    if (has_trait(PF_ROBUST) && force_bad)
    {
        // Robust Genetics gives you a 33% chance for a good mutation,
        // instead of the 33% chance of a bad one.
        force_bad = false;
        force_good = true;
    }

    // Determine the mutation categorie
    mutation_category cat = MUTCAT_NULL;

    // Count up the players number of mutations in categories and find
    // the category with the highest single count.
    int total = 0, highest = 0;
    for (int i = 0; i < NUM_MUTATION_CATEGORIES; i++)
    {
        total += mutation_category_level[i];
        if (mutation_category_level[i] > highest)
        {
            cat = mutation_category(i);
            highest = mutation_category_level[i];
        }
    }

    // See if we should ugrade/extend an existing mutation...
    std::vector<pl_flag> upgrades;
    // ... or remove one that is not in our highest category
    std::vector<pl_flag> downgrades;
    // For each mutation...
    for (int base_mutation_index = 1; base_mutation_index < PF_MAX2; base_mutation_index++)
    {
        pl_flag base_mutation = (pl_flag) base_mutation_index;

        // ...that we have...
        if (has_trait(base_mutation))
        {
            // ...consider the mutations that replace it.
            for (int i = 0; i < g->mutation_data[base_mutation].replacements.size(); i++)
            {
                pl_flag mutation = g->mutation_data[base_mutation].replacements[i];

                if (mutation_ok(g, mutation, force_good, force_bad))
                {
                    upgrades.push_back(mutation);
                }
            }

            // ...consider the mutations that add to it.
            for (int i = 0; i < g->mutation_data[base_mutation].additions.size(); i++)
            {
                pl_flag mutation = g->mutation_data[base_mutation].additions[i];

                if (mutation_ok(g, mutation, force_good, force_bad))
                {
                    upgrades.push_back(mutation);
                }
            }

            // ...consider whether its in our highest category
            if( has_trait(base_mutation) && !has_base_trait(base_mutation) ){ // Starting traits don't count toward categories
                std::vector<pl_flag> group = mutations_from_category(cat);
                bool in_cat = false;
                for (int j = 0; j < group.size(); j++)
                {
                    if (group[j] == base_mutation)
                    {
                        in_cat = true;
                        break;
                    }
                }
                // mark for removal
                if(!in_cat) downgrades.push_back(base_mutation);
            }
        }
    }

    // Preliminary round to either upgrade or remove existing mutations
    if(one_in(2)){
      if (upgrades.size() > 0)
      {
          // (upgrade count) chances to pick an upgrade, 4 chances to pick something else.
          int roll = rng(0, upgrades.size() + 4);
          if (roll < upgrades.size())
          {
              // We got a valid upgrade index, so use it and return.
              mutate_towards(g, upgrades[roll]);
              return;
          }
      }
    }
    else {
      // Remove existing mutations that don't fit into our category
      if (downgrades.size() > 0 && cat != MUTCAT_NULL)
      {
          int roll = rng(0, downgrades.size() + 4);
          if (roll < downgrades.size())
          {
              remove_mutation(g, downgrades[roll]);
              return;
          }
      }
    }

    std::vector<pl_flag> valid; // Valid mutations
    bool first_pass = true;

    do
    {
        // If we tried once with a non-NULL category, and couldn't find anything valid
        // there, try again with MUTCAT_NULL
        if (!first_pass)
        {
            cat = MUTCAT_NULL;
        }

        if (cat == MUTCAT_NULL)
        {
            // Pull the full list
            for (int i = 1; i < PF_MAX2; i++)
            {
                if (g->mutation_data[i].valid)
                {
                    valid.push_back( pl_flag(i) );
                }
            }
        }
        else
        {
            // Pull the category's list
            valid = mutations_from_category(cat);
        }

        // Remove anything we already have, that we have a child of, or that
        // goes against our intention of a good/bad mutation
        for (int i = 0; i < valid.size(); i++)
        {
            if (!mutation_ok(g, valid[i], force_good, force_bad))
            {
                valid.erase(valid.begin() + i);
                i--;
            }
        }

        if (valid.empty())
        {
            // So we won't repeat endlessly
            first_pass = false;
        }

    }
    while (valid.empty() && cat != MUTCAT_NULL);


    if (valid.empty())
    {
        // Couldn't find anything at all!
        return;
    }

    pl_flag selection = valid[ rng(0, valid.size() - 1) ]; // Pick one!

    mutate_towards(g, selection);
}
Example #14
0
void player::mutate_towards(game *g, pl_flag mut)
{
 if (has_child_flag(g, mut)) {
  remove_child_flag(g, mut);
  return;
 }
 bool has_prereqs = false;
 pl_flag canceltrait = PF_NULL;
 std::vector<pl_flag> prereq = g->mutation_data[mut].prereqs;
 std::vector<pl_flag> cancel = g->mutation_data[mut].cancels;

 for (int i = 0; i < cancel.size(); i++) {
  if (!has_trait( cancel[i] )) {
   cancel.erase(cancel.begin() + i);
   i--;
  } else {
  //If we have the trait, but it's a base trait, don't allow it to be removed normally
   if (has_base_trait( cancel[i] )) {
    canceltrait = cancel[i];
    cancel.erase(cancel.begin() + i);
    i--;
   }
  }
  
 }

 if (!cancel.empty()) {
  pl_flag removed = cancel[ rng(0, cancel.size() - 1) ];
  remove_mutation(g, removed);
  return;
 }

 for (int i = 0; !has_prereqs && i < prereq.size(); i++) {
  if (has_trait(prereq[i]))
   has_prereqs = true;
 }
 if (!has_prereqs && !prereq.empty()) {
  pl_flag devel = prereq[ rng(0, prereq.size() - 1) ];
  mutate_towards(g, devel);
  return;
 }

// Check if one of the prereqs that we have TURNS INTO this one
 pl_flag replacing = PF_NULL;
 prereq = g->mutation_data[mut].prereqs; // Reset it
 for (int i = 0; i < prereq.size(); i++) {
  if (has_trait(prereq[i])) {
   pl_flag pre = prereq[i];
   for (int j = 0; replacing == PF_NULL &&
                   j < g->mutation_data[pre].replacements.size(); j++) {
    if (g->mutation_data[pre].replacements[j] == mut)
     replacing = pre;
   }
  }
 }

 toggle_mutation(mut);
 if (replacing != PF_NULL)
    {
        g->add_msg(_("Your %1$s mutation turns into %2$s!"), traits[replacing].name.c_str(),
                   traits[mut].name.c_str());
        toggle_mutation(replacing);
        mutation_loss_effect(g, *this, replacing);
        mutation_effect(g, *this, mut);
    }
  else
 // If this new mutation cancels a base trait, remove it and add the mutation at the same time
   if (canceltrait != PF_NULL)
    {
        g->add_msg(_("Your innate %1$s trait turns into %2$s!"), traits[canceltrait].name.c_str(),
                   traits[mut].name.c_str());
		toggle_mutation(canceltrait);
		mutation_loss_effect(g, *this, canceltrait);
		mutation_effect(g, *this, mut);
	}
  else
    {
        g->add_msg(_("You gain a mutation called %s!"), traits[mut].name.c_str());
        mutation_effect(g, *this, mut);
    }

// Weight us towards any categories that include this mutation
 for (int i = 0; i < NUM_MUTATION_CATEGORIES; i++) {
  std::vector<pl_flag> group = mutations_from_category(mutation_category(i));
  bool found = false;
  for (int j = 0; !found && j < group.size(); j++) {
   if (group[j] == mut)
    found = true;
  }
  if (found)
   mutation_category_level[i] += 8;
  else if (mutation_category_level[i] > 0 &&
           !one_in(mutation_category_level[i]))
   mutation_category_level[i]--;
 }

}
Example #15
0
void monster::explode()
{
    if( is_hallucination() ) {
        //Can't gib hallucinations
        return;
    }
    // Send body parts and blood all over!
    const itype_id meat = type->get_meat_itype();
    const field_id type_blood = bloodType();
    const field_id type_gib = gibType();
    if( meat != "null" || type_blood != fd_null || type_gib != fd_null ) {
        // Only create chunks if we know what kind to make.
        int num_chunks = 0;
        switch( type->size ) {
            case MS_TINY:
                num_chunks = 1;
                break;
            case MS_SMALL:
                num_chunks = 2;
                break;
            case MS_MEDIUM:
                num_chunks = 4;
                break;
            case MS_LARGE:
                num_chunks = 8;
                break;
            case MS_HUGE:
                num_chunks = 16;
                break;
        }

        for( int i = 0; i < num_chunks; i++ ) {
            int tarx = _posx + rng( -3, 3 ), tary = _posy + rng( -3, 3 );
            std::vector<point> traj = line_to( _posx, _posy, tarx, tary, 0 );

            for( size_t j = 0; j < traj.size(); j++ ) {
                tarx = traj[j].x;
                tary = traj[j].y;
                if( one_in( 2 ) && type_blood != fd_null ) {
                    g->m.add_field( tarx, tary, type_blood, 1 );
                } else if( type_gib != fd_null ) {
                    g->m.add_field( tarx, tary, type_gib, rng( 1, j + 1 ) );
                }
                if( g->m.move_cost( tarx, tary ) == 0 ) {
                    if( !g->m.bash( tarx, tary, 3 ).second ) {
                        // 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 ) {
                            tarx = traj[j - 1].x;
                            tary = traj[j - 1].y;
                        } else {
                            tarx = -1;
                        }
                        break;
                    }
                }
            }
            if( meat != "null" && tarx != -1 ) {
                g->m.spawn_item( tarx, tary, meat, 1, 0, calendar::turn );
            }
        }
    }
}
Example #16
0
int player::hit_mon(game *g, monster *z)
{
 bool is_u = (this == &(g->u));	// Affects how we'll display messages
 int j;
 bool can_see = (is_u || g->u_see(posx, posy, j));
 std::string You  = (is_u ? "You"  : name);
 std::string Your = (is_u ? "Your" : name + "'s");
 std::string your = (is_u ? "your" : (male ? "his" : "her"));

// Types of combat (may overlap!)
 bool unarmed  = unarmed_attack(), bashing = weapon.is_bashing_weapon(),
      cutting  = weapon.is_cutting_weapon(),
      stabbing = (weapon.has_flag(IF_SPEAR) ||
                  weapon.has_flag(IF_STAB)    );

// Recoil penalty
 if (recoil <= 30)
  recoil += 6;
// Movement cost
 moves -= weapon.attack_time() + 20 * encumb(bp_torso);
// Different sizes affect your chance to hit
 if (hit_roll() < z->dodge_roll() || one_in(2 + dex_cur)) {// A miss!
  stumble(g);
  return 0;
 }
// For very high hit rolls, we crit!
 bool critical_hit = scored_crit(z->dodge_roll());
 int dam = base_damage(true);
 int cutting_penalty = 0; // Moves lost from getting a cutting weapon stuck

// Drunken Master damage bonuses
 if (has_trait(PF_DRUNKEN) && has_disease(DI_DRUNK)) {
// Remember, a single drink gives 600 levels of DI_DRUNK
  int mindrunk, maxdrunk;
  if (unarmed) {
   mindrunk = disease_level(DI_DRUNK) / 600;
   maxdrunk = disease_level(DI_DRUNK) / 250;
  } else {
   mindrunk = disease_level(DI_DRUNK) / 900;
   maxdrunk = disease_level(DI_DRUNK) / 400;
  }
  dam += rng(mindrunk, maxdrunk);
 }

 if (unarmed) { // Unarmed bonuses
  dam += rng(0, sklevel[sk_unarmed]);
  if (has_trait(PF_TALONS) && z->type->armor - sklevel[sk_unarmed] < 10) {
   int z_armor = (z->type->armor - sklevel[sk_unarmed]);
   if (z_armor < 0)
    z_armor = 0;
   dam += 10 - z_armor;
  }
 }
 if (rng(1, 45 - dex_cur) < 2 * sklevel[sk_unarmed] &&
     rng(1, 65 - dex_cur) < 2 * sklevel[sk_unarmed]   ) {
// Bonus unarmed attack!
  if (is_u || can_see) {
   switch (rng(1, 4)) {
    case 1: g->add_msg("%s kick%s the %s!", You.c_str(), (is_u ? "" : "s"),
                       z->name().c_str()); break;
    case 2: g->add_msg("%s headbutt%s the %s!", You.c_str(), (is_u ? "" : "s"),
                       z->name().c_str()); break;
    case 3: g->add_msg("%s elbow%s the %s!", You.c_str(), (is_u ? "" : "s"),
                       z->name().c_str()); break;
    case 4: g->add_msg("%s knee%s the %s!", You.c_str(), (is_u ? "" : "s"),
                       z->name().c_str()); break;
   }
  }
  if (sklevel[sk_unarmed] >= 4)
   dam += rng(1, sklevel[sk_unarmed] / 2);
  else
   dam++;
  practice(sk_unarmed, 2);
 }
// Melee skill bonus
 dam += rng(0, sklevel[sk_melee]);
// Bashing damage bonus
 int bash_dam = weapon.damage_bash(),
     bash_cap = 5 + str_cur + sklevel[sk_bashing];
 if (bash_dam > bash_cap)// Cap for weak characters
  bash_dam = (bash_cap * 3 + bash_dam) / 4;
 if (bashing)
  bash_dam += rng(0, sklevel[sk_bashing] + sqrt(double(str_cur)));
 if (z->has_flag(MF_PLASTIC))
  bash_dam /= rng(2, 4);
 int bash_min = bash_dam / 4;
 if (bash_min < sklevel[sk_bashing] )
  bash_min = sklevel[sk_bashing];
 dam += rng(bash_min, bash_dam);
// Take some moves away from the target; at this point it's skill & bash damage
 z->moves -= rng(0, dam * 2);
// Spears treat cutting damage specially.
 if (weapon.has_flag(IF_SPEAR) &&
     weapon.damage_cut() > z->type->armor - int(sklevel[sk_stabbing])) {
  int z_armor = z->type->armor - int(sklevel[sk_stabbing]);
  dam += int(weapon.damage_cut() / 5);
  int minstab = sklevel[sk_stabbing] *  5 + weapon.volume() * 2,
      maxstab = sklevel[sk_stabbing] * 15 + weapon.volume() * 4;
  int monster_penalty = rng(minstab, maxstab);
  if (monster_penalty >= 150)
   g->add_msg("You force the %s to the ground!", z->name().c_str());
  else if (monster_penalty >= 50)
   g->add_msg("The %s is skewered and flinches!", z->name().c_str());
  z->moves -= monster_penalty;
  cutting_penalty = weapon.damage_cut() * 4 + z_armor * 8 -
                    dice(sklevel[sk_stabbing], 10);
  practice(sk_stabbing, 2);
// Cutting damage bonus
 } else if (weapon.damage_cut() >
            z->type->armor - int(sklevel[sk_cutting] / 2)) {
  int z_armor = z->type->armor - int(sklevel[sk_cutting] / 2);
  if (z_armor < 0)
   z_armor = 0;
  dam += weapon.damage_cut() - z_armor;
  cutting_penalty = weapon.damage_cut() * 3 + z_armor * 8 -
                    dice(sklevel[sk_cutting], 10);
 }
 if (weapon.has_flag(IF_MESSY)) { // e.g. chainsaws
  cutting_penalty /= 6; // Harder to get stuck
  for (int x = z->posx - 1; x <= z->posx + 1; x++) {
   for (int y = z->posy - 1; y <= z->posy + 1; y++) {
    if (!one_in(3)) {
     if (g->m.field_at(x, y).type == fd_blood &&
         g->m.field_at(x, y).density < 3)
      g->m.field_at(x, y).density++;
     else
      g->m.add_field(g, x, y, fd_blood, 1);
    }
   }
  }
 }

// Bonus attacks!

 bool shock_them = (has_bionic(bio_shock) && power_level >= 2 && unarmed &&
                    one_in(3));
 bool drain_them = (has_bionic(bio_heat_absorb) && power_level >= 1 &&
                    !is_armed() && z->has_flag(MF_WARM));
 bool  bite_them = (has_trait(PF_FANGS) && z->armor() < 18 &&
                    one_in(20 - dex_cur - sklevel[sk_unarmed]));
 bool  peck_them = (has_trait(PF_BEAK)  && z->armor() < 16 &&
                    one_in(15 - dex_cur - sklevel[sk_unarmed]));
 if (drain_them)
  power_level--;
 drain_them &= one_in(2);	// Only works half the time

// Critical hit effects
 if (critical_hit) {
  bool headshot = (!z->has_flag(MF_NOHEAD) && !one_in(3));
// Second chance for shock_them, drain_them, bite_them and peck_them
  shock_them = (shock_them || (has_bionic(bio_shock)&& power_level >= 2 &&
                               unarmed && !one_in(3)));
  drain_them = (drain_them || (has_bionic(bio_heat_absorb) && !is_armed() &&
                               power_level >= 1 && z->has_flag(MF_WARM) &&
                               !one_in(3)));
  bite_them  = ( bite_them || (has_trait(PF_FANGS) && z->armor() < 18 &&
                               one_in(5)));
  peck_them  = ( peck_them || (has_trait(PF_BEAK)  && z->armor() < 16 &&
                               one_in(4)));

  if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) {
   dam += weapon.damage_cut();
   dam += weapon.damage_cut() * double(sklevel[sk_stabbing] / 10);
   practice(sk_stabbing, 5);
  }

  if (unarmed) {
   dam += rng(1, 4) * sklevel[sk_unarmed];
   if (sklevel[sk_unarmed] > 5)
    dam += 4 * (sklevel[sk_unarmed] - 5);
   z->moves -= dam;	// Stunning blow
   if (weapon.type->id == itm_bio_claws) {
    if (sklevel[sk_cutting] >= 3)
     dam += 5;
    headshot &= z->hp < dam && one_in(2);
    if (headshot && can_see)
     g->add_msg("%s claws pierce the %s's skull!", Your.c_str(),
                z->name().c_str());
    else if (can_see)
     g->add_msg("%s claws stab straight through the %s!", Your.c_str(),
                z->name().c_str());
   } else if (has_trait(PF_TALONS)) {
    dam += 2;
    headshot &= z->hp < dam && one_in(2);
    if (headshot && can_see)
     g->add_msg("%s talons tear the %s's head open!", Your.c_str(),
                z->name().c_str());
    else if (can_see)
     g->add_msg("%s bur%s %s talons into the %s!", You.c_str(),(is_u?"y":"ies"),
                your.c_str(), z->name().c_str());
   } else {
    headshot &= z->hp < dam && one_in(2);
    if (headshot && can_see)
     g->add_msg("%s crush%s the %s's skull in a single blow!", 
                You.c_str(), (is_u ? "" : "es"), z->name().c_str());
    else if (can_see)
     g->add_msg("%s deliver%s a crushing punch!",You.c_str(),(is_u ? "" : "s"));
   }
   if (z->hp > 0 && rng(1, 5) < sklevel[sk_unarmed])
    z->add_effect(ME_STUNNED, 1 + sklevel[sk_unarmed]);
  } else {	// Not unarmed
   if (bashing) {
    dam += 8 + (str_cur / 2);
    int turns_stunned = int(dam / 20) + int(sklevel[sk_bashing] / 2);
    if (turns_stunned > 6)
     turns_stunned = 6;
    z->add_effect(ME_STUNNED, turns_stunned);
   }
   if (cutting || stabbing) {
    double cut_multiplier = double(sklevel[sk_cutting] / 12);
    if (cut_multiplier > 1.5)
     cut_multiplier = 1.5;
    dam += cut_multiplier * weapon.damage_cut();
    headshot &= z->hp < dam;
    if (stabbing) {
     if (headshot && can_see)
      g->add_msg("%s %s stabs through the %s's skull!", Your.c_str(),
                 weapon.tname(g).c_str(), z->name().c_str());
     else if (can_see)
      g->add_msg("%s stab %s %s through the %s!", You.c_str(), your.c_str(),
                 weapon.tname(g).c_str(), z->name().c_str());
    } else {
     if (headshot && can_see)
      g->add_msg("%s %s slices the %s's head off!", Your.c_str(),
                 weapon.tname(g).c_str(), z->name().c_str());
     else
      g->add_msg("%s %s cuts the %s deeply!", Your.c_str(),
                 weapon.tname(g).c_str(), z->name().c_str());
    }
   } else if (bashing) {
    headshot &= z->hp < dam;
    if (headshot && can_see)
     g->add_msg("%s crush%s the %s's skull!", You.c_str(), (is_u ? "" : "es"),
                z->name().c_str());
    else if (can_see)
     g->add_msg("%s crush%s the %s's body!", You.c_str(), (is_u ? "" : "es"),
                z->name().c_str());
   }
  }	// End of not-unarmed
 }	// End of critical hit

 if (shock_them) {
  power_level -= 2;
  if (can_see)
   g->add_msg("%s shock%s the %s!", You.c_str(), (is_u ? "" : "s"),
              z->name().c_str());
  int shock = rng(2, 5);
  dam += shock * rng(1, 3);
  z->moves -= shock * 180;
 }
 if (drain_them) {
  charge_power(rng(0, 4));
  if (can_see)
   g->add_msg("%s drain%s the %s's body heat!", You.c_str(), (is_u ? "" : "s"),
              z->name().c_str());
  dam += rng(4, 10);
  z->moves -= rng(80, 120);
 }
 if (bite_them) {
  if (can_see)
   g->add_msg("%s sink %s fangs into the %s!", You.c_str(), your.c_str(),
              z->name().c_str());
  dam += 18 - z->armor();
 }
 if (peck_them) {
  if (can_see)
   g->add_msg("%s peck%s the %s viciously!", You.c_str(), (is_u ? "" : "s"),
              z->name().c_str());
  dam += 16 - z->armor();
 }

// Make a rather quiet sound, to alert any nearby monsters
 g->sound(posx, posy, 8, "");

// Glass weapons shatter sometimes
 if (weapon.made_of(GLASS) &&
     rng(0, weapon.volume() + 8) < weapon.volume() + str_cur) {
  if (can_see)
   g->add_msg("%s %s shatters!", Your.c_str(), weapon.tname(g).c_str());
  g->sound(posx, posy, 16, "");
// Dump its contents on the ground
  for (int i = 0; i < weapon.contents.size(); i++)
   g->m.add_item(posx, posy, weapon.contents[i]);
  hit(g, bp_arms, 1, 0, rng(0, weapon.volume() * 2));// Take damage
  if (weapon.is_two_handed(this))// Hurt left arm too, if it was big
   hit(g, bp_arms, 0, 0, rng(0, weapon.volume()));
  dam += rng(0, 5 + int(weapon.volume() * 1.5));// Hurt the monster extra
  remove_weapon();
 }

 if (dam <= 0) {
  if (is_u)
   g->add_msg("You hit the %s, but do no damage.", z->name().c_str());
  else if (can_see)
   g->add_msg("%s's %s hits the %s, but does no damage.", You.c_str(),
              weapon.tname(g).c_str(), z->name().c_str());
  practice(sk_melee, rng(2, 5));
  if (unarmed)
   practice(sk_unarmed, 2);
  if (bashing)
   practice(sk_bashing, 2);
  if (cutting)
   practice(sk_cutting, 2);
  if (stabbing)
   practice(sk_stabbing, 2);
  return 0;
 }
 if (is_u)
  g->add_msg("You hit the %s for %d damage.", z->name().c_str(), dam);
 else if (can_see)
  g->add_msg("%s hits the %s with %s %s.", You.c_str(), z->name().c_str(),
             (male ? "his" : "her"),
             (weapon.type->id == 0 ? "fists" : weapon.tname(g).c_str()));
 practice(sk_melee, rng(5, 10));
 if (unarmed)
  practice(sk_unarmed, rng(5, 10));
 if (bashing)
  practice(sk_bashing, rng(5, 10));
 if (cutting)
  practice(sk_cutting, rng(5, 10));
 if (stabbing)
  practice(sk_stabbing, rng(5, 10));

// Penalize the player if their cutting weapon got stuck
 if (!unarmed && dam < z->hp && cutting_penalty > dice(str_cur * 2, 20)) {
  if (is_u)
   g->add_msg("Your %s gets stuck in the %s, pulling it out of your hands!",
              weapon.tname().c_str(), z->type->name.c_str());
  z->add_item(remove_weapon());
  if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB))
   z->speed *= .7;
  else
   z->speed *= .85;
 } else {
  if (dam >= z->hp) {
   cutting_penalty /= 2;
   cutting_penalty -= rng(sklevel[sk_cutting], sklevel[sk_cutting] * 2 + 2);
  }
  if (cutting_penalty > 0)
   moves -= cutting_penalty;
  if (cutting_penalty >= 50 && is_u)
   g->add_msg("Your %s gets stuck in the %s, but you yank it free.",
              weapon.tname().c_str(), z->type->name.c_str());
  if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB))
   z->speed *= .9;
 }

 return dam;
}
Example #17
0
void trapfuncm::snake(game *g, monster *z, int x, int y)
{
 g->sound(x, y, 10, "ssssssss");
 if (one_in(6))
  g->m.tr_at(x, y) = tr_null;
}
Example #18
0
/**
 * Attempts to harm a creature with a projectile.
 *
 * @param source Pointer to the creature who shot the projectile.
 * @param attack A structure describing the attack and its results.
 */
void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack &attack )
{
    const double missed_by = attack.missed_by;
    if( missed_by >= 1.0 ) {
        // Total miss
        return;
    }

    const projectile &proj = attack.proj;
    dealt_damage_instance &dealt_dam = attack.dealt_dam;
    const auto &proj_effects = proj.proj_effects;

    const bool u_see_this = g->u.sees(*this);

    const int avoid_roll = dodge_roll();
    // Do dice(10, speed) instead of dice(speed, 10) because speed could potentially be > 10000
    const int diff_roll = dice( 10, proj.speed );
    // Partial dodge, capped at [0.0, 1.0], added to missed_by
    const double dodge_rescaled = avoid_roll / static_cast<double>( diff_roll );
    const double goodhit = missed_by + std::max( 0.0, std::min( 1.0, dodge_rescaled ) ) ;

    if( goodhit >= 1.0 ) {
        // "Avoid" rather than "dodge", because it includes removing self from the line of fire
        //  rather than just Matrix-style bullet dodging
        if( source != nullptr && g->u.sees( *source ) ) {
            add_msg_player_or_npc(
                m_warning,
                _("You avoid %s projectile!"),
                _("<npcname> avoids %s projectile."),
                source->disp_name(true).c_str() );
        } else {
            add_msg_player_or_npc(
                m_warning,
                _("You avoid an incoming projectile!"),
                _("<npcname> avoids an incoming projectile.") );
        }

        attack.missed_by = 1.0; // Arbitrary value
        return;
    }

    // Bounce applies whether it does damage or not.
    if( proj.proj_effects.count( "BOUNCE" ) ) {
        add_effect( effect_bounced, 1_turns );
    }

    body_part bp_hit;
    double hit_value = missed_by + rng_float(-0.5, 0.5);
    // Headshots considered elsewhere
    if( hit_value <= 0.4 ) {
        bp_hit = bp_torso;
    } else if (one_in(4)) {
        if( one_in(2)) {
            bp_hit = bp_leg_l;
        } else {
            bp_hit = bp_leg_r;
        }
    } else {
        if( one_in(2)) {
            bp_hit = bp_arm_l;
        } else {
            bp_hit = bp_arm_r;
        }
    }

    double damage_mult = 1.0;

    std::string message = "";
    game_message_type gmtSCTcolor = m_neutral;

    if( goodhit < accuracy_headshot ) {
        message = _("Headshot!");
        gmtSCTcolor = m_headshot;
        damage_mult *= rng_float(1.95, 2.05);
        bp_hit = bp_head; // headshot hits the head, of course

    } else if( goodhit < accuracy_critical ) {
        message = _("Critical!");
        gmtSCTcolor = m_critical;
        damage_mult *= rng_float(1.5, 2.0);

    } else if( goodhit < accuracy_goodhit ) {
        message = _("Good hit!");
        gmtSCTcolor = m_good;
        damage_mult *= rng_float(1, 1.5);

    } else if( goodhit < accuracy_standard ) {
        damage_mult *= rng_float(0.5, 1);

    } else if( goodhit < accuracy_grazing ) {
        message = _("Grazing hit.");
        gmtSCTcolor = m_grazing;
        damage_mult *= rng_float(0, .25);
    }

    if( source != nullptr && !message.empty() ) {
        source->add_msg_if_player(m_good, message.c_str());
    }

    attack.missed_by = goodhit;

    // copy it, since we're mutating
    damage_instance impact = proj.impact;
    if( damage_mult > 0.0f && proj_effects.count( "NO_DAMAGE_SCALING" ) ) {
        damage_mult = 1.0f;
    }

    impact.mult_damage(damage_mult);

    if( proj_effects.count( "NOGIB" ) > 0 ) {
        float dmg_ratio = (float)impact.total_damage() / get_hp_max( player::bp_to_hp( bp_hit ) );
        if( dmg_ratio > 1.25f ) {
            impact.mult_damage( 1.0f / dmg_ratio );
        }
    }

    dealt_dam = deal_damage(source, bp_hit, impact);
    dealt_dam.bp_hit = bp_hit;

    // Apply ammo effects to target.
    if (proj.proj_effects.count("FLAME")) {
        if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) ||
            made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) ||
            made_of( material_id( "wood" ) ) ) {
            add_effect( effect_onfire, rng( 8_turns, 20_turns ), bp_hit );
        } else if (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) {
            add_effect( effect_onfire, rng( 5_turns, 10_turns ), bp_hit );
        }
    } else if (proj.proj_effects.count("INCENDIARY") ) {
        if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) ||
            made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) ||
            made_of( material_id( "wood" ) ) ) {
            add_effect( effect_onfire, rng( 2_turns, 6_turns ), bp_hit );
        } else if ( (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) &&
                    one_in(4) ) {
            add_effect( effect_onfire, rng( 1_turns, 4_turns ), bp_hit );
        }
    } else if (proj.proj_effects.count("IGNITE")) {
        if (made_of( material_id( "veggy" ) ) || made_of( material_id( "cotton" ) ) ||
            made_of( material_id( "wool" ) ) || made_of( material_id( "paper" ) ) ||
            made_of( material_id( "wood" ) ) ) {
            add_effect( effect_onfire, 6_turns, bp_hit );
        } else if (made_of( material_id( "flesh" ) ) || made_of( material_id( "iflesh" ) ) ) {
            add_effect( effect_onfire, 10_turns, bp_hit );
        }
    }

    if( bp_hit == bp_head && proj_effects.count( "BLINDS_EYES" ) ) {
        // TODO: Change this to require bp_eyes
        add_env_effect( effect_blind, bp_eyes, 5, rng( 3_turns, 10_turns ) );
    }

    if( proj_effects.count( "APPLY_SAP" ) ) {
        add_effect( effect_sap, 1_turns * dealt_dam.total_damage() );
    }

    int stun_strength = 0;
    if (proj.proj_effects.count("BEANBAG")) {
        stun_strength = 4;
    }
    if (proj.proj_effects.count("LARGE_BEANBAG")) {
        stun_strength = 16;
    }
    if( stun_strength > 0 ) {
        switch( get_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;
        }
        add_effect( effect_stunned, 1_turns * rng( stun_strength / 2, stun_strength ) );
    }

    if(u_see_this) {
        if( damage_mult == 0 ) {
            if( source != nullptr ) {
                add_msg( source->is_player() ? _("You miss!") : _("The shot misses!") );
            }
        } else if( dealt_dam.total_damage() == 0 ) {
            //~ 1$ - monster name, 2$ - character's bodypart or monster's skin/armor
            add_msg( _("The shot reflects off %1$s %2$s!"), disp_name(true).c_str(),
                     is_monster() ?
                        skin_name().c_str() :
                        body_part_name_accusative(bp_hit).c_str() );
        } else if( is_player() ) {
                //monster hits player ranged
                //~ Hit message. 1$s is bodypart name in accusative. 2$d is damage value.
                add_msg_if_player(m_bad, _( "You were hit in the %1$s for %2$d damage." ),
                                  body_part_name_accusative(bp_hit).c_str(),
                                  dealt_dam.total_damage());
        } else if( source != nullptr ) {
            if( source->is_player() ) {
                //player hits monster ranged
                SCT.add(posx(), posy(),
                        direction_from(0, 0, posx() - source->posx(), posy() - source->posy()),
                        get_hp_bar(dealt_dam.total_damage(), get_hp_max(), true).first,
                        m_good, message, gmtSCTcolor);

                if (get_hp() > 0) {
                    SCT.add(posx(), posy(),
                            direction_from(0, 0, posx() - source->posx(), posy() - source->posy()),
                            get_hp_bar(get_hp(), get_hp_max(), true).first, m_good,
                            //~ "hit points", used in scrolling combat text
                            _("hp"), m_neutral, "hp");
                } else {
                    SCT.removeCreatureHP();
                }

                add_msg(m_good, _("You hit %s for %d damage."),
                        disp_name().c_str(), dealt_dam.total_damage());
            } else if( u_see_this ) {
                //~ 1$ - shooter, 2$ - target
                add_msg(_("%1$s shoots %2$s."),
                        source->disp_name().c_str(), disp_name().c_str());
            }
        }
    }

    check_dead_state();
    attack.hit_critter = this;
    attack.missed_by = goodhit;
}
Example #19
0
void apply_ammo_effects( const tripoint &p, const std::set<std::string> &effects )
{
    if( effects.count( "EXPLOSIVE_SMALL" ) > 0 ) {
        g->explosion( p, 24, 0.4 );
    }

    if( effects.count( "EXPLOSIVE" ) > 0 ) {
        g->explosion( p, 24 );
    }

    if( effects.count( "FRAG" ) > 0 ) {
        explosion_data frag;
        frag.power = 1.0f;
        frag.shrapnel.count = 50;
        frag.shrapnel.mass = 5;
        frag.shrapnel.recovery = 100;
        frag.shrapnel.drop = "shrapnel";
        g->explosion( p, frag );
    }

    if( effects.count( "NAPALM" ) > 0 ) {
        g->explosion( p, 4, 0.7, true );
        // More intense fire near the center
        for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) {
            g->m.add_field( pt, fd_fire, 1, 0 );
        }
    }

    if( effects.count( "NAPALM_BIG" ) > 0 ) {
        g->explosion( p, 24, 0.8, true );
        // More intense fire near the center
        for( auto &pt : g->m.points_in_radius( p, 3, 0 ) ) {
            g->m.add_field( pt, fd_fire, 1, 0 );
        }
    }

    if( effects.count( "MININUKE_MOD" ) > 0 ) {
        g->explosion( p, 450 );
        for( auto &pt : g->m.points_in_radius( p, 6, 0 ) ) {
            if( g->m.sees( p, pt, 3 ) &&
                g->m.passable( pt ) ) {
                g->m.add_field( pt, fd_nuke_gas, 3, 0 );
            }
        }
    }

    if( effects.count( "ACIDBOMB" ) > 0 ) {
        for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) {
            g->m.add_field( pt, fd_acid, 3, 0 );
        }
    }


    if( effects.count( "EXPLOSIVE_BIG" ) > 0 ) {
        g->explosion( p, 40 );
    }

    if( effects.count( "EXPLOSIVE_HUGE" ) > 0 ) {
        g->explosion( p, 80 );
    }

    if( effects.count( "TOXICGAS" ) > 0 ) {
        for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) {
            g->m.add_field( pt, fd_toxic_gas, 3, 0 );
        }
    }
    if( effects.count( "GAS_FUNGICIDAL" ) > 0 ) {
        for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) {
            g->m.add_field( pt, fd_fungicidal_gas, 3, 0 );
        }
    }
    if( effects.count( "SMOKE" ) > 0 ) {
        for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) {
            g->m.add_field( pt, fd_smoke, MAX_FIELD_DENSITY );
        }
    }
    if( effects.count( "SMOKE_BIG" ) > 0 ) {
        for( auto &pt : g->m.points_in_radius( p, 6, 0 ) ) {
            g->m.add_field( pt, fd_smoke, MAX_FIELD_DENSITY );
        }
    }

    if( effects.count( "FLASHBANG" ) ) {
        g->flashbang( p );
    }

    if( effects.count( "EMP" ) ) {
        g->emp_blast( p );
    }

    if( effects.count( "NO_BOOM" ) == 0 && effects.count( "FLAME" ) > 0 ) {
        for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) {
            g->m.add_field( pt, fd_fire, 1, 0 );
        }
    }

    if( effects.count( "FLARE" ) > 0 ) {
        g->m.add_field( p, fd_fire, 1, 0 );
    }

    if( effects.count( "LIGHTNING" ) > 0 ) {
        for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) {
            g->m.add_field( pt, fd_electricity, 3, 0 );
        }
    }

    if( effects.count( "PLASMA" ) > 0 ) {
        for( auto &pt : g->m.points_in_radius( p, 1, 0 ) ) {
            if( one_in( 2 ) ) {
                g->m.add_field( pt, fd_plasma, rng( 2, 3 ), 0 );
            }
        }
    }
}
Example #20
0
int monster::trigger_sum(game *g, std::vector<monster_trigger> *triggers)
{
 int ret = 0;
 bool check_terrain = false, check_meat = false, check_fire = false;
 for (int i = 0; i < triggers->size(); i++) {

  switch ((*triggers)[i]) {
  case MTRIG_TIME:
   if (one_in(20))
    ret++;
   break;

  case MTRIG_MEAT:
   check_terrain = true;
   check_meat = true;
   break;

  case MTRIG_PLAYER_CLOSE:
   if (rl_dist(posx, posy, g->u.posx, g->u.posy) <= 5)
    ret += 5;
   for (int i = 0; i < g->active_npc.size(); i++) {
    if (rl_dist(posx, posy, g->active_npc[i].posx, g->active_npc[i].posy) <= 5)
     ret += 5;
   }
   break;

  case MTRIG_FIRE:
   check_terrain = true;
   check_fire = true;
   break;

  case MTRIG_PLAYER_WEAK:
   if (g->u.hp_percentage() <= 70)
    ret += 10 - int(g->u.hp_percentage() / 10);
   break;

  default:
   break; // The rest are handled when the impetus occurs
  }
 }

 if (check_terrain) {
  for (int x = posx - 3; x <= posx + 3; x++) {
   for (int y = posy - 3; y <= posy + 3; y++) {
    if (check_meat) {
     std::vector<item> *items = &(g->m.i_at(x, y));
     for (int n = 0; n < items->size(); n++) {
      if ((*items)[n].type->id == "corpse" ||
          (*items)[n].type->id == "meat" ||
          (*items)[n].type->id == "meat_cooked" ||
          (*items)[n].type->id == "human_flesh") {
       ret += 3;
       check_meat = false;
      }
     }
    }
    if (check_fire) {
     if (g->m.field_at(x, y).type == fd_fire)
      ret += 5 * g->m.field_at(x, y).density;
    }
   }
  }
  if (check_fire) {
   if (g->u.has_amount("torch_lit", 1))
    ret += 49;
  }
 }

 return ret;
}
void activity_handlers::forage_finish( player_activity *act, player *p )
{
    int veggy_chance = rng(1, 100);
    bool found_something = false;

    if( one_in(12) ) {
        add_msg(m_good, _("You found some trash!"));
        g->m.put_items_from_loc( "trash_forest", p->pos3(), calendar::turn );
        found_something = true;
    }
    items_location loc;
    ter_id next_ter = t_null; //Just to have a default for compilers' sake
    switch (calendar::turn.get_season() ) {
    case SPRING:
        loc = "forage_spring";
        next_ter = terfind("t_underbrush_harvested_spring");
        break;
    case SUMMER:
        loc = "forage_summer";
        next_ter = terfind("t_underbrush_harvested_summer");
        break;
    case AUTUMN:
        loc = "forage_autumn";
        next_ter = terfind("t_underbrush_harvested_autumn");
        break;
    case WINTER:
        loc = "forage_winter";
        next_ter = terfind("t_underbrush_harvested_winter");
        break;
    }

    // Compromise: Survival gives a bigger boost, and Peception is leveled a bit.
    if( veggy_chance < ((p->skillLevel("survival") * 1.5) + ((p->per_cur / 2 - 4) + 3)) ) {
        // Returns zero if location has no defined items.
        int cnt = g->m.put_items_from_loc( loc, p->pos3(), calendar::turn );
        if( cnt > 0 ) {
            add_msg(m_good, _("You found something!"));
            g->m.ter_set( act->placement, next_ter );
            found_something = true;
        }
    } else {
        if( one_in(2) ) {
            g->m.ter_set( act->placement, next_ter );
        }
    }
    if( !found_something ) {
        add_msg(_("You didn't find anything."));
    }

    //Determinate maximum level of skill attained by foraging using ones intelligence score
    int max_forage_skill = 0;
    if( p->int_cur < 4 ) {
        max_forage_skill = 1;
    } else if( p->int_cur < 6 ) {
        max_forage_skill = 2;
    } else if( p->int_cur < 8 ) {
        max_forage_skill = 3;
    } else if( p->int_cur < 11 ) {
        max_forage_skill = 4;
    } else if( p->int_cur < 15 ) {
        max_forage_skill = 5;
    } else if( p->int_cur < 20 ) {
        max_forage_skill = 6;
    } else if( p->int_cur < 26 ) {
        max_forage_skill = 7;
    } else if( p->int_cur > 25 ) {
        max_forage_skill = 8;
    }

    const int max_exp {(max_forage_skill * 2) - (p->skillLevel("survival") * 2)};
    //Award experience for foraging attempt regardless of success
    p->practice("survival", rng(1, max_exp), max_forage_skill);
}
Example #22
0
void game_loop()
{
    int regen_timer = 0;

    _draw();

    while (io::window_is_open() && _game_running)
    {
        if (!io::window_is_open())
            break;

        bool do_update_action_list = false;
        int state = statestack_top();

        if (get_action_list_size() == 0)
        {
            append_action_list(&player);
            for (size_t i = 0; i < current_dungeon->creatures.size(); i++)
            {
                append_action_list(current_dungeon->creatures.at(i));
            }

            _regenerate_creatures(regen_timer);

            // Tick effects every turn.
            tick_effects();

            // Update gas clouds and gas machines.
            update_gas_machines(current_dungeon);

            // Update stealth
            check_stealth();

            // Respawn creatures if few enough.
            if (_count_enemies_in_current_dungeon() <= 3 && one_in(25))
            {
                spawn_monsters(current_branch->name, current_dungeon, current_dungeon->level, random(0, 3), true);
            }

            // Increase turn count.
            turn_count++;
        }

        if (current_actor()->identity == IDENT_PLAYER)
        {
            int old_player_ap = player.ap;

            // Do this before taking action ->
            // Draws everything from the previous turn and also
            // shows all the previous turns messages.
            _draw();

            if (state == STATE_READ_COMMAND)
            {
                _read_command();
            }
            else if (state == STATE_EXAMINE || state == STATE_CAST_SPELL || state == STATE_ZAP_WAND)
            {
                _move_targeting_cursor();
            }
            else if (state == STATE_CLOSE_DOOR || state == STATE_CHAT)
            {
                _select_direction();
            }
            else if (state == STATE_TRAVEL)
            {
                _travel();
            }

            do_update_action_list = old_player_ap != player.ap;

            if (do_update_action_list)
            {
                check_cloud_collisions(current_dungeon);

                // Check if player has gained a level.
                if (can_gain_level())
                {
                    gain_level();
                }
            }
        }
        else
        {
            creature_t* actor = current_actor();
            actor->update();

            do_update_action_list = true;
        }

        if (do_update_action_list)
        {
            if (current_actor() && current_actor()->ap <= 0)
            {
                if (current_actor()->identity == IDENT_PLAYER)
                {
                    tick_food_clock();
                }

                // TODO: Regenerate creature here.

                current_actor()->ap += 100;
                remove_from_action_list(current_actor());
            }

            sort_action_list();
        }
    }
}
void activity_handlers::pulp_do_turn( player_activity *act, player *p )
{
    const tripoint &pos = act->placement;
    static const int full_pulp_threshold = 4;
    const int move_cost = int(p->weapon.is_null() ? 80 : p->weapon.attack_time() * 0.8);

    // numbers logic: a str 8 character with a butcher knife (4 bash, 18 cut)
    // should have at least a 50% chance of damaging an intact zombie corpse (75 volume).
    // a str 8 character with a baseball bat (28 bash, 0 cut) should have around a 25% chance.

    int cut_power = p->weapon.type->melee_cut;
    // stabbing weapons are a lot less effective at pulping
    if( p->weapon.has_flag("STAB") || p->weapon.has_flag("SPEAR") ) {
        cut_power /= 2;
    }
    double pulp_power = sqrt((double)(p->str_cur + p->weapon.type->melee_dam)) *
        std::min(1.0, sqrt((double)(cut_power + 1)));
    pulp_power = std::min(pulp_power, (double)p->str_cur);
    pulp_power *= 20; // constant multiplier to get the chance right
    int moves = 0;
    int &num_corpses = act->index; // use this to collect how many corpse are pulped
    auto corpse_pile = g->m.i_at(pos);
    for( auto corpse = corpse_pile.begin(); corpse != corpse_pile.end(); ++corpse ) {
        if( !(corpse->is_corpse() && corpse->damage < full_pulp_threshold) ) {
            continue; // no corpse or already pulped
        }
        int damage = pulp_power / corpse->volume();
        //Determine corpse's blood type.
        field_id type_blood = corpse->get_mtype()->bloodType();
        do {
            moves += move_cost;
            const int mod_sta = ( (p->weapon.weight() / 100 ) + 20) * -1;
            p->mod_stat("stamina", mod_sta);
            // Increase damage as we keep smashing,
            // to insure that we eventually smash the target.
            if( x_in_y(pulp_power, corpse->volume())  ) {
                corpse->damage++;
                p->handle_melee_wear();
            }
            // Splatter some blood around
            tripoint tmp = pos;
            if( type_blood != fd_null ) {
                for( tmp.x = pos.x - 1; tmp.x <= pos.x + 1; tmp.x++ ) {
                    for( tmp.y = pos.y - 1; tmp.y <= pos.y + 1; tmp.y++ ) {
                        if( !one_in(damage + 1) && type_blood != fd_null ) {
                            g->m.add_field( tmp, type_blood, 1, 0 );
                        }
                    }
                }
            }
            if( corpse->damage >= full_pulp_threshold ) {
                corpse->damage = full_pulp_threshold;
                corpse->active = false;
                num_corpses++;
            }
            if( moves >= p->moves ) {
                // enough for this turn;
                p->moves -= moves;
                return;
            }
        } while( corpse->damage < full_pulp_threshold );
    }
    // If we reach this, all corpses have been pulped, finish the activity
    act->moves_left = 0;
    // TODO: Factor in how long it took to do the smashing.
    add_msg(ngettext("The corpse is thoroughly pulped.",
                     "The corpses are thoroughly pulped.", num_corpses));
}
dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tripoint &source,
        const tripoint &target_arg, dispersion_sources dispersion,
        Creature *origin, const vehicle *in_veh )
{
    const bool do_animation = get_option<bool>( "ANIMATIONS" );

    double range = rl_dist( source, target_arg );

    Creature *target_critter = g->critter_at( target_arg );
    double target_size = target_critter != nullptr ?
                         target_critter->ranged_target_size() :
                         g->m.ranged_target_size( target_arg );
    projectile_attack_aim aim = projectile_attack_roll( dispersion, range, target_size );

    // TODO: move to-hit roll back in here

    dealt_projectile_attack attack {
        proj_arg, nullptr, dealt_damage_instance(), source, aim.missed_by
    };

    if( source == target_arg ) { // No suicidal shots
        debugmsg( "Projectile_attack targeted own square." );
        return attack;
    }

    projectile &proj = attack.proj;
    const auto &proj_effects = proj.proj_effects;

    const bool stream = proj_effects.count( "STREAM" ) > 0 ||
                        proj_effects.count( "STREAM_BIG" ) > 0 ||
                        proj_effects.count( "JET" ) > 0;
    const char bullet = stream ? '#' : '*';
    const bool no_item_damage = proj_effects.count( "NO_ITEM_DAMAGE" ) > 0;
    const bool do_draw_line = proj_effects.count( "DRAW_AS_LINE" ) > 0;
    const bool null_source = proj_effects.count( "NULL_SOURCE" ) > 0;
    // Determines whether it can penetrate obstacles
    const bool is_bullet = proj_arg.speed >= 200 && std::any_of( proj_arg.impact.damage_units.begin(),
                           proj_arg.impact.damage_units.end(),
    []( const damage_unit & dam ) {
        return dam.type == DT_CUT;
    } );

    // If we were targetting a tile rather than a monster, don't overshoot
    // Unless the target was a wall, then we are aiming high enough to overshoot
    const bool no_overshoot = proj_effects.count( "NO_OVERSHOOT" ) ||
                              ( g->critter_at( target_arg ) == nullptr && g->m.passable( target_arg ) );

    double extend_to_range = no_overshoot ? range : proj_arg.range;

    tripoint target = target_arg;
    std::vector<tripoint> trajectory;
    if( aim.missed_by_tiles >= 1.0 ) {
        // We missed enough to target a different tile
        double dx = target_arg.x - source.x;
        double dy = target_arg.y - source.y;
        double rad = atan2( dy, dx );

        // cap wild misses at +/- 30 degrees
        rad += ( one_in( 2 ) ? 1 : -1 ) * std::min( ARCMIN( aim.dispersion ), DEGREES( 30 ) );

        // @todo: This should also represent the miss on z axis
        const int offset = std::min<int>( range, sqrtf( aim.missed_by_tiles ) );
        int new_range = no_overshoot ?
                        range + rng( -offset, offset ) :
                        rng( range - offset, proj_arg.range );
        new_range = std::max( new_range, 1 );

        target.x = source.x + roll_remainder( new_range * cos( rad ) );
        target.y = source.y + roll_remainder( new_range * sin( rad ) );

        if( target == source ) {
            target.x = source.x + sgn( dx );
            target.y = source.y + sgn( dy );
        }

        // Don't extend range further, miss here can mean hitting the ground near the target
        range = rl_dist( source, target );
        extend_to_range = range;

        sfx::play_variant_sound( "bullet_hit", "hit_wall", sfx::get_heard_volume( target ),
                                 sfx::get_heard_angle( target ) );
        // TODO: Z dispersion
        // If we missed, just draw a straight line.
        trajectory = line_to( source, target );
    } else {
        // Go around obstacles a little if we're on target.
        trajectory = g->m.find_clear_path( source, target );
    }

    add_msg( m_debug, "missed_by_tiles: %.2f; missed_by: %.2f; target (orig/hit): %d,%d,%d/%d,%d,%d",
             aim.missed_by_tiles, aim.missed_by,
             target_arg.x, target_arg.y, target_arg.z,
             target.x, target.y, target.z );

    // Trace the trajectory, doing damage in order
    tripoint &tp = attack.end_point;
    tripoint prev_point = source;

    trajectory.insert( trajectory.begin(), source ); // Add the first point to the trajectory

    static emit_id muzzle_smoke( "emit_smoke_plume" );
    if( proj_effects.count( "MUZZLE_SMOKE" ) ) {
        g->m.emit_field( trajectory.front(), muzzle_smoke );
    }

    if( !no_overshoot && range < extend_to_range ) {
        // Continue line is very "stiff" when the original range is short
        // @todo: Make it use a more distant point for more realistic extended lines
        std::vector<tripoint> trajectory_extension = continue_line( trajectory,
                extend_to_range - range );
        trajectory.reserve( trajectory.size() + trajectory_extension.size() );
        trajectory.insert( trajectory.end(), trajectory_extension.begin(), trajectory_extension.end() );
    }
    // Range can be 0
    size_t traj_len = trajectory.size();
    while( traj_len > 0 && rl_dist( source, trajectory[traj_len - 1] ) > proj_arg.range ) {
        --traj_len;
    }

    const float projectile_skip_multiplier = 0.1;
    // Randomize the skip so that bursts look nicer
    int projectile_skip_calculation = range * projectile_skip_multiplier;
    int projectile_skip_current_frame = rng( 0, projectile_skip_calculation );
    bool has_momentum = true;

    for( size_t i = 1; i < traj_len && ( has_momentum || stream ); ++i ) {
        prev_point = tp;
        tp = trajectory[i];

        if( ( tp.z > prev_point.z && g->m.has_floor( tp ) ) ||
            ( tp.z < prev_point.z && g->m.has_floor( prev_point ) ) ) {
            // Currently strictly no shooting through floor
            // TODO: Bash the floor
            tp = prev_point;
            traj_len = --i;
            break;
        }
        // Drawing the bullet uses player g->u, and not player p, because it's drawn
        // relative to YOUR position, which may not be the gunman's position.
        if( do_animation && !do_draw_line ) {
            // TODO: Make this draw thrown item/launched grenade/arrow
            if( projectile_skip_current_frame >= projectile_skip_calculation ) {
                g->draw_bullet( tp, ( int )i, trajectory, bullet );
                projectile_skip_current_frame = 0;
                // If we missed recalculate the skip factor so they spread out.
                projectile_skip_calculation = std::max( ( size_t )range, i ) * projectile_skip_multiplier;
            } else {
                projectile_skip_current_frame++;
            }
        }

        if( in_veh != nullptr ) {
            int part;
            vehicle *other = g->m.veh_at( tp, part );
            if( in_veh == other && other->is_inside( part ) ) {
                continue; // Turret is on the roof and can't hit anything inside
            }
        }

        Creature *critter = g->critter_at( tp );
        if( origin == critter ) {
            // No hitting self with "weird" attacks.
            critter = nullptr;
        }

        monster *mon = dynamic_cast<monster *>( critter );
        // ignore non-point-blank digging targets (since they are underground)
        if( mon != nullptr && mon->digging() &&
            rl_dist( source, tp ) > 1 ) {
            critter = nullptr;
            mon = nullptr;
        }

        // Reset hit critter from the last iteration
        attack.hit_critter = nullptr;

        // If we shot us a monster...
        // TODO: add size effects to accuracy
        // If there's a monster in the path of our bullet, and either our aim was true,
        //  OR it's not the monster we were aiming at and we were lucky enough to hit it
        double cur_missed_by = aim.missed_by;

        // unintentional hit on something other than our actual target
        // don't re-roll for the actual target, we already decided on a missed_by value for that
        // at the start, misses should stay as misses
        if( critter != nullptr && tp != target_arg ) {
            // Unintentional hit
            cur_missed_by = std::max( rng_float( 0.1, 1.5 - aim.missed_by ) / critter->ranged_target_size(),
                                      0.4 );
        }

        if( critter != nullptr && cur_missed_by < 1.0 ) {
            if( in_veh != nullptr && g->m.veh_at( tp ) == in_veh && critter->is_player() ) {
                // Turret either was aimed by the player (who is now ducking) and shoots from above
                // Or was just IFFing, giving lots of warnings and time to get out of the line of fire
                continue;
            }
            attack.missed_by = cur_missed_by;
            critter->deal_projectile_attack( null_source ? nullptr : origin, attack );
            // Critter can still dodge the projectile
            // In this case hit_critter won't be set
            if( attack.hit_critter != nullptr ) {
                const size_t bt_len = blood_trail_len( attack.dealt_dam.total_damage() );
                if( bt_len > 0 ) {
                    const tripoint &dest = move_along_line( tp, trajectory, bt_len );
                    g->m.add_splatter_trail( critter->bloodType(), tp, dest );
                }
                sfx::do_projectile_hit( *attack.hit_critter );
                has_momentum = false;
            } else {
                attack.missed_by = aim.missed_by;
            }
        } else if( in_veh != nullptr && g->m.veh_at( tp ) == in_veh ) {
            // Don't do anything, especially don't call map::shoot as this would damage the vehicle
        } else {
            g->m.shoot( tp, proj, !no_item_damage && tp == target );
            has_momentum = proj.impact.total_damage() > 0;
        }

        if( ( !has_momentum || !is_bullet ) && g->m.impassable( tp ) ) {
            // Don't let flamethrowers go through walls
            // TODO: Let them go through bars
            traj_len = i;
            break;
        }
    } // Done with the trajectory!

    if( do_animation && do_draw_line && traj_len > 2 ) {
        trajectory.erase( trajectory.begin() );
        trajectory.resize( traj_len-- );
        g->draw_line( tp, trajectory );
        g->draw_bullet( tp, int( traj_len-- ), trajectory, bullet );
    }

    if( g->m.impassable( tp ) ) {
        tp = prev_point;
    }

    drop_or_embed_projectile( attack );

    apply_ammo_effects( tp, proj.proj_effects );
    const auto &expl = proj.get_custom_explosion();
    if( expl.power > 0.0f ) {
        g->explosion( tp, proj.get_custom_explosion() );
    }

    // TODO: Move this outside now that we have hit point in return values?
    if( proj.proj_effects.count( "BOUNCE" ) ) {
        // Add effect so the shooter is not targeted itself.
        if( origin && !origin->has_effect( effect_bounced ) ) {
            origin->add_effect( effect_bounced, 1 );
        }
        Creature *mon_ptr = g->get_creature_if( [&]( const Creature & z ) {
            // search for creatures in radius 4 around impact site
            if( rl_dist( z.pos(), tp ) <= 4 &&
                g->m.sees( z.pos(), tp, -1 ) ) {
                // don't hit targets that have already been hit
                if( !z.has_effect( effect_bounced ) ) {
                    return true;
                }
            }
            return false;
        } );
        if( mon_ptr ) {
            Creature &z = *mon_ptr;
            add_msg( _( "The attack bounced to %s!" ), z.get_name().c_str() );
            z.add_effect( effect_bounced, 1 );
            projectile_attack( proj, tp, z.pos(), dispersion, origin, in_veh );
            sfx::play_variant_sound( "fire_gun", "bio_lightning_tail",
                                     sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) );
        }
    }

    return attack;
}
// General movement.
// Currently, priority goes:
// 1) Special Attack
// 2) Sight-based tracking
// 3) Scent-based tracking
// 4) Sound-based tracking
void monster::move()
{
    // We decrement wandf no matter what.  We'll save our wander_to plans until
    // after we finish out set_dest plans, UNLESS they time out first.
    if( wandf > 0 ) {
        wandf--;
    }

    //Hallucinations have a chance of disappearing each turn
    if( is_hallucination() && one_in( 25 ) ) {
        die( nullptr );
        return;
    }

    //The monster can consume objects it stands on. Check if there are any.
    //If there are. Consume them.
    if( !is_hallucination() && ( has_flag( MF_ABSORBS ) || has_flag( MF_ABSORBS_SPLITS ) ) &&
        !g->m.has_flag( TFLAG_SEALED, pos() ) && g->m.has_items( pos() ) ) {
        if( g->u.sees( *this ) ) {
            add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ),
                     name().c_str() );
        }
        static const auto volume_per_hp = units::from_milliliter( 250 );
        for( auto &elem : g->m.i_at( pos() ) ) {
            hp += elem.volume() / volume_per_hp; // Yeah this means it can get more HP than normal.
            if( has_flag( MF_ABSORBS_SPLITS ) && hp * 2 > type->hp ) {
                for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) {
                    if( g->is_empty( dest ) && hp * 2 > type->hp ) {
                        if( monster *const  spawn = g->summon_mon( type->id, dest ) ) {
                            hp -= type->hp;
                            //this is a new copy of the monster. Ideally we should copy the stats/effects that affect the parent
                            spawn->make_ally( *this );
                            if( g->u.sees( *this ) ) {
                                add_msg( _( "The %s splits in two!" ),
                                         name().c_str() );
                            }
                        }
                    }
                }
            }
        }
        g->m.i_clear( pos() );
    }

    const bool pacified = has_effect( effect_pacified );

    // First, use the special attack, if we can!
    // The attack may change `monster::special_attacks` (e.g. by transforming
    // this into another monster type). Therefore we can not iterate over it
    // directly and instead iterate over the map from the monster type
    // (properties of monster types should never change).
    for( const auto &sp_type : type->special_attacks ) {
        const std::string &special_name = sp_type.first;
        const auto local_iter = special_attacks.find( special_name );
        if( local_iter == special_attacks.end() ) {
            continue;
        }
        mon_special_attack &local_attack_data = local_iter->second;
        if( !local_attack_data.enabled ) {
            continue;
        }

        // Cooldowns are decremented in monster::process_turn

        if( local_attack_data.cooldown == 0 && !pacified && !is_hallucination() ) {
            if( !sp_type.second->call( *this ) ) {
                continue;
            }

            // `special_attacks` might have changed at this point. Sadly `reset_special`
            // doesn't check the attack name, so we need to do it here.
            if( special_attacks.count( special_name ) == 0 ) {
                continue;
            }
            reset_special( special_name );
        }
    }

    // The monster can sometimes hang in air due to last fall being blocked
    const bool can_fly = has_flag( MF_FLIES );
    if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) {
        g->m.creature_on_trap( *this, false );
    }

    if( moves < 0 ) {
        return;
    }

    // TODO: Move this to attack_at/move_to/etc. functions
    bool attacking = false;
    if( !move_effects( attacking ) ) {
        moves = 0;
        return;
    }
    if( has_flag( MF_IMMOBILE ) ) {
        moves = 0;
        return;
    }
    if( has_effect( effect_stunned ) ) {
        stumble();
        moves = 0;
        return;
    }
    if( friendly > 0 ) {
        --friendly;
    }

    // Set attitude to attitude to our current target
    monster_attitude current_attitude = attitude( nullptr );
    if( !wander() ) {
        if( goal == g->u.pos() ) {
            current_attitude = attitude( &( g->u ) );
        } else {
            for( const npc &guy : g->all_npcs() ) {
                if( goal == guy.pos() ) {
                    current_attitude = attitude( &guy );
                }
            }
        }
    }

    if( current_attitude == MATT_IGNORE ||
        ( current_attitude == MATT_FOLLOW && rl_dist( pos(), goal ) <= MONSTER_FOLLOW_DIST ) ) {
        moves -= 100;
        stumble();
        return;
    }

    bool moved = false;
    tripoint destination;

    // If true, don't try to greedily avoid locally bad paths
    bool pathed = false;
    if( !wander() ) {
        while( !path.empty() && path.front() == pos() ) {
            path.erase( path.begin() );
        }

        const auto &pf_settings = get_pathfinding_settings();
        if( pf_settings.max_dist >= rl_dist( pos(), goal ) &&
            ( path.empty() || rl_dist( pos(), path.front() ) >= 2 || path.back() != goal ) ) {
            // We need a new path
            path = g->m.route( pos(), goal, pf_settings, get_path_avoid() );
        }

        // Try to respect old paths, even if we can't pathfind at the moment
        if( !path.empty() && path.back() == goal ) {
            destination = path.front();
            moved = true;
            pathed = true;
        } else {
            // Straight line forward, probably because we can't pathfind (well enough)
            destination = goal;
            moved = true;
        }
    }
    if( !moved && has_flag( MF_SMELLS ) ) {
        // No sight... or our plans are invalid (e.g. moving through a transparent, but
        //  solid, square of terrain).  Fall back to smell if we have it.
        unset_dest();
        tripoint tmp = scent_move();
        if( tmp.x != -1 ) {
            destination = tmp;
            moved = true;
        }
    }
    if( wandf > 0 && !moved && friendly == 0 ) { // No LOS, no scent, so as a fall-back follow sound
        unset_dest();
        if( wander_pos != pos() ) {
            destination = wander_pos;
            moved = true;
        }
    }

    if( !g->m.has_zlevels() ) {
        // Otherwise weird things happen
        destination.z = posz();
    }

    tripoint next_step;
    const bool staggers = has_flag( MF_STUMBLES );
    const bool can_climb = has_flag( MF_CLIMBS );
    if( moved ) {
        // Implement both avoiding obstacles and staggering.
        moved = false;
        float switch_chance = 0.0;
        const bool can_bash = bash_skill() > 0;
        // This is a float and using trig_dist() because that Does the Right Thing(tm)
        // in both circular and roguelike distance modes.
        const float distance_to_target = trig_dist( pos(), destination );
        for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) {
            if( candidate.z != posz() ) {
                bool can_z_move = true;
                if( !g->m.valid_move( pos(), candidate, false, true ) ) {
                    // Can't phase through floor
                    can_z_move = false;
                }

                // If we're trying to go up but can't fly, check if we can climb. If we can't, then don't
                // This prevents non-climb/fly enemies running up walls
                if( candidate.z > posz() && !can_fly ) {
                    if( !can_climb || !g->m.has_floor_or_support( candidate ) ) {
                        // Can't "jump" up a whole z-level
                        can_z_move = false;
                    }
                }

                // Last chance - we can still do the z-level stair teleport bullshit that isn't removed yet
                // @todo: Remove z-level stair bullshit teleport after aligning all stairs
                if( !can_z_move &&
                    posx() / ( SEEX * 2 ) == candidate.x / ( SEEX * 2 ) &&
                    posy() / ( SEEY * 2 ) == candidate.y / ( SEEY * 2 ) ) {
                    const tripoint &upper = candidate.z > posz() ? candidate : pos();
                    const tripoint &lower = candidate.z > posz() ? pos() : candidate;
                    if( g->m.has_flag( TFLAG_GOES_DOWN, upper ) && g->m.has_flag( TFLAG_GOES_UP, lower ) ) {
                        can_z_move = true;
                    }
                }

                if( !can_z_move ) {
                    continue;
                }
            }

            // A flag to allow non-stumbling critters to stumble when the most direct choice is bad.
            bool bad_choice = false;

            const Creature *target = g->critter_at( candidate, is_hallucination() );
            if( target != nullptr ) {
                const Creature::Attitude att = attitude_to( *target );
                if( att == A_HOSTILE ) {
                    // When attacking an adjacent enemy, we're direct.
                    moved = true;
                    next_step = candidate;
                    break;
                } else if( att == A_FRIENDLY && ( target->is_player() || target->is_npc() ) ) {
                    continue; // Friendly firing the player or an NPC is illegal for gameplay reasons
                } else if( !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) {
                    // Bail out if there's a non-hostile monster in the way and we're not pushy.
                    continue;
                }
                // Friendly fire and pushing are always bad choices - they take a lot of time
                bad_choice = true;
            }

            // Bail out if we can't move there and we can't bash.
            if( !pathed && !can_move_to( candidate ) ) {
                if( !can_bash ) {
                    continue;
                }

                const int estimate = g->m.bash_rating( bash_estimate(), candidate );
                if( estimate <= 0 ) {
                    continue;
                }

                if( estimate < 5 ) {
                    bad_choice = true;
                }
            }

            const float progress = distance_to_target - trig_dist( candidate, destination );
            // The x2 makes the first (and most direct) path twice as likely,
            // since the chance of switching is 1/1, 1/4, 1/6, 1/8
            switch_chance += progress * 2;
            // Randomly pick one of the viable squares to move to weighted by distance.
            if( !moved || x_in_y( progress, switch_chance ) ) {
                moved = true;
                next_step = candidate;
                // If we stumble, pick a random square, otherwise take the first one,
                // which is the most direct path.
                // Except if the direct path is bad, then check others
                // Or if the path is given by pathfinder
                if( !staggers && ( !bad_choice || pathed ) ) {
                    break;
                }
            }
        }
    }
    // Finished logic section.  By this point, we should have chosen a square to
    //  move to (moved = true).
    if( moved ) { // Actual effects of moving to the square we've chosen
        const bool did_something =
            ( !pacified && attack_at( next_step ) ) ||
            ( !pacified && bash_at( next_step ) ) ||
            ( !pacified && push_to( next_step, 0, 0 ) ) ||
            move_to( next_step, false, get_stagger_adjust( pos(), destination, next_step ) );

        if( !did_something ) {
            moves -= 100; // If we don't do this, we'll get infinite loops.
        }
    } else {
        moves -= 100;
        stumble();
        path.clear();
    }
}
Example #26
0
int monster::trigger_sum(std::set<monster_trigger> *triggers) const
{
    int ret = 0;
    bool check_terrain = false, check_meat = false, check_fire = false;
    for (auto trig = triggers->begin(); trig != triggers->end(); ++trig){
        switch (*trig) {
            case MTRIG_STALK:
                if (anger > 0 && one_in(20)) {
                    ret++;
                }
                break;

            case MTRIG_MEAT:
                check_terrain = true;
                check_meat = true;
                break;

            case MTRIG_PLAYER_CLOSE:
                if (rl_dist(_posx, _posy, g->u.posx, g->u.posy) <= 5) {
                    ret += 5;
                }
                for (auto &i : g->active_npc) {
                    if (rl_dist(_posx, _posy, i->posx, i->posy) <= 5) {
                        ret += 5;
                    }
                }
                break;

            case MTRIG_FIRE:
                check_terrain = true;
                check_fire = true;
                break;

            case MTRIG_PLAYER_WEAK:
                if (g->u.hp_percentage() <= 70) {
                    ret += 10 - int(g->u.hp_percentage() / 10);
                }
                break;

            default:
                break; // The rest are handled when the impetus occurs
        }
    }

    if (check_terrain) {
        for (int x = _posx - 3; x <= _posx + 3; x++) {
            for (int y = _posy - 3; y <= _posy + 3; y++) {
                if (check_meat) {
                    std::vector<item> *items = &(g->m.i_at(x, y));
                    for (size_t n = 0; n < items->size(); n++) {
                        if ((*items)[n].type->id == "corpse" || (*items)[n].type->id == "meat" ||
                              (*items)[n].type->id == "meat_cooked" ||
                              (*items)[n].type->id == "human_flesh") {
                            ret += 3;
                            check_meat = false;
                        }
                    }
                }
                if (check_fire) {
                    ret += ( 5 * g->m.get_field_strength( point(x, y), fd_fire) );
                }
            }
        }
        if (check_fire) {
            if (g->u.has_amount("torch_lit", 1)) {
                ret += 49;
            }
        }
    }

    return ret;
}
Example #27
0
// General movement.
// Currently, priority goes:
// 1) Special Attack
// 2) Sight-based tracking
// 3) Scent-based tracking
// 4) Sound-based tracking
void monster::move()
{
    // We decrement wandf no matter what.  We'll save our wander_to plans until
    // after we finish out set_dest plans, UNLESS they time out first.
    if( wandf > 0 ) {
        wandf--;
    }

    //Hallucinations have a chance of disappearing each turn
    if( is_hallucination() && one_in( 25 ) ) {
        die( nullptr );
        return;
    }

    //The monster can consume objects it stands on. Check if there are any.
    //If there are. Consume them.
    if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( TFLAG_SEALED, pos() ) ) {
        if( !g->m.i_at( pos3() ).empty() ) {
            add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ),
                     name().c_str() );
            for( auto &elem : g->m.i_at( pos3() ) ) {
                hp += elem.volume(); // Yeah this means it can get more HP than normal.
            }
            g->m.i_clear( pos3() );
        }
    }

    static const std::string pacified_string = "pacified";
    const bool pacified = has_effect( pacified_string );

    // First, use the special attack, if we can!
    for( size_t i = 0; i < sp_timeout.size(); ++i ) {
        if( sp_timeout[i] > 0 ) {
            sp_timeout[i]--;
        }

        if( sp_timeout[i] == 0 && !pacified && !is_hallucination() ) {
            type->sp_attack[i]( this, i );
        }
    }

    // The monster can sometimes hang in air due to last fall being blocked
    const bool can_fly = has_flag( MF_FLIES );
    if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) {
        g->m.creature_on_trap( *this, false );
    }

    if( moves < 0 ) {
        return;
    }

    // TODO: Move this to attack_at/move_to/etc. functions
    bool attacking = false;
    if( !move_effects(attacking) ) {
        moves = 0;
        return;
    }
    if( has_flag( MF_IMMOBILE ) ) {
        moves = 0;
        return;
    }
    static const std::string stun_string = "stunned";
    if( has_effect( stun_string ) ) {
        stumble();
        moves = 0;
        return;
    }
    if( friendly > 0 ) {
        --friendly;
    }

    // Set attitude to attitude to our current target
    monster_attitude current_attitude = attitude( nullptr );
    if( !wander() ) {
        if( goal == g->u.pos3() ) {
            current_attitude = attitude( &( g->u ) );
        } else {
            for( auto &i : g->active_npc ) {
                if( goal == i->pos3() ) {
                    current_attitude = attitude( i );
                }
            }
        }
    }

    if( current_attitude == MATT_IGNORE ||
        ( current_attitude == MATT_FOLLOW && rl_dist(pos(), goal) <= MONSTER_FOLLOW_DIST ) ) {
        moves -= 100;
        stumble();
        return;
    }

    bool moved = false;
    tripoint destination;

    // CONCRETE PLANS - Most likely based on sight
    if( !wander() ) {
        destination = goal;
        moved = true;
    }
    if( !moved && has_flag( MF_SMELLS ) ) {
        // No sight... or our plans are invalid (e.g. moving through a transparent, but
        //  solid, square of terrain).  Fall back to smell if we have it.
        unset_dest();
        tripoint tmp = scent_move();
        if( tmp.x != -1 ) {
            destination = tmp;
            moved = true;
        }
    }
    if( wandf > 0 && !moved ) { // No LOS, no scent, so as a fall-back follow sound
        unset_dest();
        tripoint tmp = wander_next();
        if( tmp != pos() ) {
            destination = tmp;
            moved = true;
        }
    }

    tripoint next_step;
    if( moved ) {
        // Implement both avoiding obstacles and staggering.
        moved = false;
        float switch_chance = 0.0;
        const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES );
        // This is a float and using trig_dist() because that Does the Right Thing(tm)
        // in both circular and roguelike distance modes.
        const float distance_to_target = trig_dist( pos(), destination );
        for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) {
            const Creature *target = g->critter_at( candidate, is_hallucination() );
            // When attacking an adjacent enemy, we're direct.
            if( target != nullptr && attitude_to( *target ) == A_HOSTILE ) {
                moved = true;
                next_step = candidate;
                break;
            }
            // Bail out if we can't move there and we can't bash.
            if( !can_move_to( candidate ) &&
                !(can_bash && g->m.bash_rating( bash_estimate(), candidate ) >= 0 ) ) {
                continue;
            }
            // Bail out if there's a non-hostile monster in the way and we're not pushy.
            if( target != nullptr && attitude_to( *target ) != A_HOSTILE &&
                !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) {
                continue;
            }
            float progress = distance_to_target - trig_dist( candidate, destination );
            switch_chance += progress;
            // Randomly pick one of the viable squares to move to weighted by distance.
            if( x_in_y( progress, switch_chance ) ) {
                moved = true;
                next_step = candidate;
                // If we stumble, pick a random square, otherwise take the first one,
                // which is the most direct path.
                if( !has_flag( MF_STUMBLES ) ) {
                    break;
                }
            }
        }
    }
    // Finished logic section.  By this point, we should have chosen a square to
    //  move to (moved = true).
    if( moved ) { // Actual effects of moving to the square we've chosen
        // move_to() uses the slope to determine some move speed scaling.
        const float slope = (destination.x > destination.y) ?
            (float)destination.y / (float)destination.x :
            (float)destination.x / (float)destination.y;
        const bool did_something =
            ( !pacified && attack_at( next_step ) ) ||
            ( !pacified && bash_at( next_step ) ) ||
            ( !pacified && push_to( next_step, 0, 0 ) ) ||
            move_to( next_step, false, slope );
        if( !did_something ) {
            moves -= 100; // If we don't do this, we'll get infinite loops.
        }
    } else {
        moves -= 100;
        stumble();
    }
}
Example #28
0
void monster::melee_attack(Creature &target, bool, matec_id) {
    mod_moves(-100);
    if (type->melee_dice == 0) { // We don't attack, so just return
        return;
    }
    add_effect("hit_by_player", 3); // Make us a valid target for a few turns

    if (has_flag(MF_HIT_AND_RUN)) {
        add_effect("run", 4);
    }

    bool u_see_me = g->u_see(this);

    body_part bp_hit;
    //int highest_hit = 0;

    damage_instance damage;
    if(!is_hallucination()) {
        if (type->melee_dice > 0) {
            damage.add_damage(DT_BASH,
                    dice(type->melee_dice,type->melee_sides));
        }
        if (type->melee_cut > 0) {
            damage.add_damage(DT_CUT, type->melee_cut);
        }
    }

    dealt_damage_instance dealt_dam;
    int hitspread = target.deal_melee_attack(this, hit_roll());
    if (hitspread >= 0) {
        target.deal_melee_hit(this, hitspread, false, damage, dealt_dam);
    }
    bp_hit = dealt_dam.bp_hit;

    if (hitspread < 0) { // a miss
        // TODO: characters practice dodge when a hit misses 'em
        if (target.is_player()) {
            if (u_see_me) {
                add_msg(_("You dodge %s."), disp_name().c_str());
            } else {
                add_msg(_("You dodge an attack from an unseen source."));
            }
        } else {
            if (u_see_me) {
                add_msg(_("The %1$s dodges %2$s attack."), name().c_str(),
                            target.disp_name(true).c_str());
            }
        }
    //Hallucinations always produce messages but never actually deal damage
    } else if (is_hallucination() || dealt_dam.total_damage() > 0) {
        if (target.is_player()) {
            if (u_see_me) {
                //~ 1$s is attacker name, 2$s is bodypart name in accusative.
                add_msg(m_bad, _("The %1$s hits your %2$s."), name().c_str(),
                        body_part_name_accusative(bp_hit).c_str());
            } else {
                //~ %s is bodypart name in accusative.
                add_msg(m_bad, _("Something hits your %s."),
                        body_part_name_accusative(bp_hit).c_str());
            }
        } else {
            if (u_see_me) {
                //~ 1$s is attacker name, 2$s is target name, 3$s is bodypart name in accusative.
                add_msg(_("The %1$s hits %2$s %3$s."), name().c_str(),
                            target.disp_name(true).c_str(),
                            body_part_name_accusative(bp_hit).c_str());
            }
        }
    } else {
        if (target.is_player()) {
            if (u_see_me) {
                //~ 1$s is attacker name, 2$s is bodypart name in accusative, 3$s is armor name
                add_msg(_("The %1$s hits your %2$s, but your %3$s protects you."), name().c_str(),
                        body_part_name_accusative(bp_hit).c_str(), target.skin_name().c_str());
            } else {
                //~ 1$s is bodypart name in accusative, 2$s is armor name.
                add_msg(_("Something hits your %1$s, but your %2$s protects you."),
                        body_part_name_accusative(bp_hit).c_str(), target.skin_name().c_str());
            }
        } else {
            if (u_see_me) {
                //~ $1s is monster name, %2$s is that monster target name,
                //~ $3s is target bodypart name in accusative, 4$s is target armor name.
                add_msg(_("The %1$s hits %2$s %3$s but is stopped by %2$s %4$s."), name().c_str(),
                            target.disp_name(true).c_str(),
                            body_part_name_accusative(bp_hit).c_str(),
                            target.skin_name().c_str());
            }
        }
    }

    if (is_hallucination()) {
        if(one_in(7)) {
            die( nullptr );
        }
        return;
    }

    // Adjust anger/morale of same-species monsters, if appropriate
    int anger_adjust = 0, morale_adjust = 0;
    if (type->has_anger_trigger(MTRIG_FRIEND_ATTACKED)){
        anger_adjust += 15;
    }
    if (type->has_fear_trigger(MTRIG_FRIEND_ATTACKED)){
        morale_adjust -= 15;
    }
    if (type->has_placate_trigger(MTRIG_FRIEND_ATTACKED)){
        anger_adjust -= 15;
    }

    if (anger_adjust != 0 && morale_adjust != 0)
    {
        for (size_t i = 0; i < g->num_zombies(); i++)
        {
            g->zombie(i).morale += morale_adjust;
            g->zombie(i).anger += anger_adjust;
        }
    }
}
Example #29
0
// General movement.
// Currently, priority goes:
// 1) Special Attack
// 2) Sight-based tracking
// 3) Scent-based tracking
// 4) Sound-based tracking
void monster::move(game *g)
{
// We decrement wandf no matter what.  We'll save our wander_to plans until
// after we finish out set_dest plans, UNLESS they time out first.
 if (wandf > 0)
  wandf--;

// First, use the special attack, if we can!
 if (sp_timeout > 0)
  sp_timeout--;
 if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) {
  mattack ma;
  (ma.*type->sp_attack)(g, this);
 }
 if (moves < 0)
  return;
 if (has_flag(MF_IMMOBILE)) {
  moves = 0;
  return;
 }
 if (has_effect(ME_STUNNED)) {
  stumble(g, false);
  moves = 0;
  return;
 }
 if (has_effect(ME_DOWNED)) {
  moves = 0;
  return;
 }
 if (friendly != 0) {
  if (friendly > 0)
   friendly--;
  friendly_move(g);
  return;
 }

 moves -= 100;
 bool moved = false;
 point next;
 int mondex = (plans.size() > 0 ? g->mon_at(plans[0].x, plans[0].y) : -1);

 monster_attitude current_attitude = attitude();
 if (friendly == 0)
  current_attitude = attitude(&(g->u));
// If our plans end in a player, set our attitude to consider that player
 if (plans.size() > 0) {
  if (plans.back().x == g->u.posx && plans.back().y == g->u.posy)
   current_attitude = attitude(&(g->u));
  else {
   for (int i = 0; i < g->active_npc.size(); i++) {
    if (plans.back().x == g->active_npc[i].posx &&
        plans.back().y == g->active_npc[i].posy)
     current_attitude = attitude(&(g->active_npc[i]));
   }
  }
 }

 if (current_attitude == MATT_IGNORE ||
     (current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST)) {
  stumble(g, false);
  return;
 }

 if (plans.size() > 0 && !is_fleeing(g->u) &&
     (mondex == -1 || g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON)) &&
     (can_move_to(g->m, plans[0].x, plans[0].y) ||
      (plans[0].x == g->u.posx && plans[0].y == g->u.posy) || 
     (g->m.has_flag(bashable, plans[0].x, plans[0].y) && has_flag(MF_BASHES)))){
  // CONCRETE PLANS - Most likely based on sight
  next = plans[0];
  moved = true;
 } else if (has_flag(MF_SMELLS)) {
// No sight... or our plans are invalid (e.g. moving through a transparent, but
//  solid, square of terrain).  Fall back to smell if we have it.
  point tmp = scent_move(g);
  if (tmp.x != -1) {
   next = tmp;
   moved = true;
  }
 }
 if (wandf > 0 && !moved) { // No LOS, no scent, so as a fall-back follow sound
  point tmp = sound_move(g);
  if (tmp.x != posx || tmp.y != posy) {
   next = tmp;
   moved = true;
  }
 }

// Finished logic section.  By this point, we should have chosen a square to
//  move to (moved = true).
 if (moved) {	// Actual effects of moving to the square we've chosen
  mondex = g->mon_at(next.x, next.y);
  int npcdex = g->npc_at(next.x, next.y);
  if (next.x == g->u.posx && next.y == g->u.posy && type->melee_dice > 0)
   hit_player(g, g->u);
  else if (mondex != -1 && type->melee_dice > 0 &&
           (g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON)))
   hit_monster(g, mondex);
  else if (npcdex != -1 && type->melee_dice > 0)
   hit_player(g, g->active_npc[npcdex]);
  else if ((!can_move_to(g->m, next.x, next.y) || one_in(3)) &&
             g->m.has_flag(bashable, next.x, next.y) && has_flag(MF_BASHES)) {
   std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug!
   int bashskill = int(type->melee_dice * type->melee_sides);
   g->m.bash(next.x, next.y, bashskill, bashsound);
   g->sound(next.x, next.y, 18, bashsound);
  } else if (g->m.move_cost(next.x, next.y) == 0 && has_flag(MF_DESTROYS)) {
   g->m.destroy(g, next.x, next.y, true);
   moves -= 250;
  } else if (can_move_to(g->m, next.x, next.y) && g->is_empty(next.x, next.y))
   move_to(g, next.x, next.y);
  else
   moves -= 100;
 }

// If we're close to our target, we get focused and don't stumble
 if ((has_flag(MF_STUMBLES) && (plans.size() > 3 || plans.size() == 0)) ||
     !moved)
  stumble(g, moved);
}
Example #30
0
void map::mon_in_field(int x, int y, game *g, monster *z)
{
 if (z->has_flag(MF_DIGS))
  return;	// Digging monsters are immune to fields
 field *cur = &field_at(x, y);
 int dam = 0, j;
 switch (cur->type) {
  case fd_null:
  case fd_blood:	// It doesn't actually do anything
  case fd_bile:		// Ditto
   break;

  case fd_acid:
   if (!z->has_flag(MF_DIGS) && !z->has_flag(MF_ACIDPROOF)) {
    if (cur->density == 3)
     dam = rng(4, 10) + rng(2, 8);
    else
     dam = rng(cur->density, cur->density * 4);
   }
   break;

  case fd_fire:
   if (z->made_of(FLESH))
    dam = 3;
   if (z->made_of(VEGGY))
    dam = 12;
   if (z->made_of(PAPER) || z->made_of(LIQUID) || z->made_of(POWDER) ||
       z->made_of(WOOD)  || z->made_of(COTTON) || z->made_of(WOOL))
    dam = 50;
   if (z->made_of(STONE) || z->made_of(KEVLAR) || z->made_of(STEEL))
    dam = -25;
   if (z->has_flag(MF_FLIES))
    dam -= 20;

   if (cur->density == 1)
    dam += rng(0, 8);
   else if (cur->density == 2) {
    dam += rng(3, 12);
    if (!z->has_flag(MF_FLIES)) {
     z->moves -= 20;
     if (!z->made_of(LIQUID) && !z->made_of(STONE) && !z->made_of(KEVLAR) &&
         !z->made_of(STEEL) && !z->has_flag(MF_FIREY))
      z->add_effect(ME_ONFIRE, rng(3, 8));
    }
   } else if (cur->density == 3) {
    dam += rng(5, 18);
    if (!z->has_flag(MF_FLIES) || one_in(3)) {
     z->moves -= 40;
     if (!z->made_of(LIQUID) && !z->made_of(STONE) && !z->made_of(KEVLAR) &&
         !z->made_of(STEEL) && !z->has_flag(MF_FIREY))
      z->add_effect(ME_ONFIRE, rng(8, 12));
    }
   }
   break;

  case fd_smoke:
   if (cur->density == 3)
    z->speed -= rng(10, 20);
   if (z->made_of(VEGGY))	// Plants suffer from smoke even worse
    z->speed -= rng(1, cur->density * 12);
   break;

  case fd_tear_gas:
   if (cur->density == 3) {
    z->speed -= rng(30, 60);
    dam = rng(8, 20);
   } else if (cur->density == 2) {
    z->speed -= rng(10, 25);
    dam = rng(4, 10);
   } else
    z->speed -= rng(0, 6);
   if (z->made_of(VEGGY)) {
    z->speed -= rng(cur->density * 5, cur->density * 12);
    dam += cur->density * 10;
   }
   break;
  case fd_nuke_gas:
   if (cur->density == 3) {
    z->speed -= rng(60, 120);
    dam = rng(30, 50);
   } else if (cur->density == 2) {
    z->speed -= rng(20, 50);
    dam = rng(10, 25);
   } else {
    z->speed -= rng(0, 15);
    dam = rng(0, 12);
   }
   if (z->made_of(VEGGY)) {
    z->speed -= rng(cur->density * 5, cur->density * 12);
    dam *= cur->density;
   }
   break;

  case fd_electricity:
   dam = rng(1, cur->density);
   if (one_in(8 - cur->density) && one_in(z->armor()))
    z->moves -= cur->density * 150;
   break;

  case fd_fatigue:
   if (rng(0, 2) < cur->density) {
    dam = cur->density;
    int tries = 0;
    int newposx, newposy;
    do {
     newposx = rng(z->posx - SEEX, z->posx + SEEX);
     newposy = rng(z->posy - SEEY, z->posy + SEEY);
     tries++;
    } while (g->m.move_cost(newposx, newposy) == 0 && tries != 10);

    if (tries == 10)
     g->explode_mon(g->mon_at(z->posx, z->posy));
    else {
     int mon_hit = g->mon_at(newposx, newposy), t;
     if (mon_hit != -1) {
      if (g->u_see(z, t))
       g->add_msg("The %s teleports into a %s, killing them both!",
                  z->name().c_str(), g->z[mon_hit].name().c_str());
      g->explode_mon(mon_hit);
     } else {
      z->posx = newposx;
      z->posy = newposy;
     }
    }
   }
   break;
     
 }
 z->hurt(dam);
}