Ejemplo n.º 1
0
float monster::rate_target( Creature &c, float best, bool smart ) const
{
    const int d = rl_dist( pos(), c.pos() );
    if( d <= 0 ) {
        return INT_MAX;
    }

    // Check a very common and cheap case first
    if( !smart && d >= best ) {
        return INT_MAX;
    }

    if( !sees( c ) ) {
        return INT_MAX;
    }

    if( !smart ) {
        return d;
    }

    float power = c.power_rating();
    monster *mon = dynamic_cast< monster * >( &c );
    // Their attitude to us and not ours to them, so that bobcats won't get gunned down
    if( mon != nullptr && mon->attitude_to( *this ) == Attitude::A_HOSTILE ) {
        power += 2;
    }

    if( power > 0 ) {
        return d / power;
    }

    return INT_MAX;
}
Ejemplo n.º 2
0
void creature_t::step_toward(int px, int py, creature_t* exclude)
{
  TCODMap* map = new TCODMap(current_dungeon->width, current_dungeon->height);

  for (int y = 0; y < current_dungeon->height; y++)
  {
    for (int x = 0; x < current_dungeon->width; x++)
    {
      tile_t* tile = get_tile_at(current_dungeon, x, y);
      bool walkable = tile_is_walkable_by(*tile, this);
      if (flag & CF_INTELLIGENT)
      {
        // Intelligent monsters know how to open doors.
        if (tile->id == TILE_DOOR_CLOSED)
        {
          walkable = true;
        }
      }
      map->setProperties(x, y, tile->properties & TILE_PROP_TRANSPARENT, walkable);
    }
  }

  for (size_t i = 0; i < current_dungeon->creatures.size(); i++)
  {
    creature_t* c = current_dungeon->creatures.at(i);
    if (c == this || c == exclude)
      continue;

    if (sees(c))
    {
      // If this creature can see another creature, then consider that
      // square as unwalkable.
      map->setProperties(c->pos.x, c->pos.y, 1, 0);
    }
  }

  TCODPath* path = new TCODPath(map, 1.0f);
  if (path->compute(pos.x, pos.y, px, py))
  {
    if (path->size() > 0)
    {
      int move_x, move_y;
      path->get(0, &move_x, &move_y);
      try_move(move_x, move_y);
    }
    else
    {
      // No path found, walk around confused.
      random_walk();
    }
  }
  else
  {
    // No path found, walk around confused.
    random_walk();
  }

  delete map;
  delete path;
}
Ejemplo n.º 3
0
bool Creature::sees( const Creature &critter ) const
{
    if( critter.is_hallucination() ) {
        // hallucinations are imaginations of the player character, npcs or monsters don't hallucinate.
        // Invisible hallucinations would be pretty useless (nobody would see them at all), therefor
        // the player will see them always.
        return is_player();
    }

    const auto p = dynamic_cast< const player* >( &critter );
    if( p != nullptr && p->is_invisible() ) {
        // Let invisible players see themselves (simplifies drawing)
        return p == this;
    }

    if( !fov_3d && !debug_mode && posz() != critter.posz() ) {
        return false;
    }

    const int wanted_range = rl_dist( pos(), critter.pos() );
    if( wanted_range <= 1 &&
        ( posz() == critter.posz() || g->m.valid_move( pos(), critter.pos(), false, true ) ) ) {
        return true;
    } else if( ( wanted_range > 1 && critter.digging() ) ||
        (critter.has_flag(MF_NIGHT_INVISIBILITY) && g->m.light_at(critter.pos()) <= LL_LOW ) ||
        ( critter.is_underwater() && !is_underwater() && g->m.is_divable( critter.pos() ) ) ) {
        return false;
    }

    return sees( critter.pos(), critter.is_player() );
}
Ejemplo n.º 4
0
bool Creature::sees( const Creature &critter, int &bresen1, int &bresen2 ) const
{
    if( critter.is_hallucination() ) {
        // hallucinations are imaginations of the player character, npcs or monsters don't hallucinate.
        // Invisible hallucinations would be pretty useless (nobody would see them at all), therefor
        // the player will see them always.
        return is_player();
    }

    const auto p = dynamic_cast< const player* >( &critter );
    if( p != nullptr && p->is_invisible() ) {
        // Let invisible players see themselves (simplifies drawing)
        return p == this;
    }

    if( posz() != critter.posz() && !debug_mode ) {
        return false; // TODO: Remove this
    }

    const int wanted_range = rl_dist( pos3(), critter.pos3() );
    if( wanted_range <= 1 ) {
        return true;
    } else if( ( wanted_range > 1 && critter.digging() ) ||
        ( g->m.is_divable( critter.pos3() ) && critter.is_underwater() && !is_underwater() ) ) {
        return false;
    }

    return sees( critter.pos3(), bresen1, bresen2 );
}
Ejemplo n.º 5
0
/* Random walking even when we've moved
 * To simulate zombie stumbling and ineffective movement
 * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed.
 * Most of the time (out in the open) this effect is insignificant compared to
 * the negative effects, but in a hallway it's perfectly even
 */
void monster::stumble( bool moved )
{
    // don't stumble every turn. every 3rd turn, or 8th when walking.
    if( ( moved && !one_in( 8 ) ) || !one_in( 3 ) ) {
        return;
    }

    std::vector<tripoint> valid_stumbles;
    const bool avoid_water = has_flag( MF_NO_BREATHE ) && !has_flag( MF_SWIMS ) && !has_flag( MF_AQUATIC );
    for( int i = -1; i <= 1; i++ ) {
        for( int j = -1; j <= 1; j++ ) {
            tripoint dest( posx() + i, posy() + j, posz() );
            if( ( i || j ) && can_move_to( dest ) &&
                //Stop zombies and other non-breathing monsters wandering INTO water
                //(Unless they can swim/are aquatic)
                //But let them wander OUT of water if they are there.
                !( avoid_water &&
                   g->m.has_flag( "SWIMMABLE", dest ) &&
                   !g->m.has_flag( "SWIMMABLE", pos3() ) ) &&
                g->critter_at( dest ) == nullptr ) {
                valid_stumbles.push_back( dest );
            }
        }
    }

    if( g->m.has_zlevels() ) {
        tripoint below( posx(), posy(), posz() - 1 );
        tripoint above( posx(), posy(), posz() + 1 );
        if( g->m.valid_move( pos(), below, false, true ) && can_move_to( below ) ) {
            valid_stumbles.push_back( below );
        }
        // More restrictions for moving up
        // It should happen during "shambling around", but not as actual stumbling
        if( !moved && one_in( 5 ) && has_flag( MF_FLIES ) &&
            g->m.valid_move( pos(), above, false, true ) && can_move_to( above ) ) {
            valid_stumbles.push_back( above );
        }
    }

    if( valid_stumbles.empty() ) { //nowhere to stumble?
        return;
    }

    move_to( random_entry( valid_stumbles ), false );

    // Here we have to fix our plans[] list,
    // acquiring a new path to the previous target.
    // target == either end of current plan, or the player.
    int bresenham_slope, junk;
    if( !plans.empty() ) {
        if( g->m.sees( pos3(), plans.back(), -1, bresenham_slope, junk ) ) {
            set_dest( plans.back(), bresenham_slope );
        } else if( sees( g->u, bresenham_slope ) ) {
            set_dest( g->u.pos(), bresenham_slope );
        } else { //durr, i'm suddenly calm. what was i doing?
            plans.clear();
        }
    }
}
Ejemplo n.º 6
0
void creature_t::blink()
{
  std::vector<coord_t> valid_coords;

  for (int y = pos.y - FOV_RADIUS; y <= pos.y + FOV_RADIUS; y++)
  {
    for (int x = pos.x - FOV_RADIUS; x <= pos.x + FOV_RADIUS; x++)
    {
      if (sees(x, y) && place_free(current_dungeon, x, y, this))
      {
        valid_coords.push_back(coord_t {x, y});
      }
    }
  }

  if (valid_coords.size())
  {
    std::random_shuffle(valid_coords.begin(), valid_coords.end());

    if (identity == IDENT_PLAYER)
    {
      append_msg_log("You feel a wrenching sensation!");
    }

    cloud_create_translocation_effect(pos.x, pos.y);

    coord_t target = valid_coords.front();
    pos = target;

    if (identity != IDENT_PLAYER)
    {
      if (player.sees(this))
      {
        append_msg_log("%s blinks!", capitalize(get_full_name()).c_str());
      }
      else
      {
        append_msg_log("%s suddenly disappears!", capitalize(get_full_name()).c_str());
      }
    }
  }
  else
  {
    if (identity == IDENT_PLAYER)
    {
      append_msg_log("You shudder for a moment.");
    }
    else if (player.sees(this))
    {
      append_msg_log("%s shudders for a moment.", capitalize(get_full_name()).c_str());
    }
  }
}
Ejemplo n.º 7
0
/* Random walking even when we've moved
 * To simulate zombie stumbling and ineffective movement
 * Note that this is sub-optimal; stumbling may INCREASE a zombie's speed.
 * Most of the time (out in the open) this effect is insignificant compared to
 * the negative effects, but in a hallway it's perfectly even
 */
void monster::stumble(bool moved)
{
 // don't stumble every turn. every 3rd turn, or 8th when walking.
 if((moved && !one_in(8)) || !one_in(3))
 {
     return;
 }

 std::vector <point> valid_stumbles;
 for (int i = -1; i <= 1; i++) {
  for (int j = -1; j <= 1; j++) {
   const int nx = posx() + i;
   const int ny = posy() + j;
   if ((i || j) && can_move_to(nx, ny) &&
       //Stop zombies and other non-breathing monsters wandering INTO water
       //(Unless they can swim/are aquatic)
       //But let them wander OUT of water if they are there.
       !(has_flag(MF_NO_BREATHE) && !has_flag(MF_SWIMS) && !has_flag(MF_AQUATIC)
           && g->m.has_flag("SWIMMABLE", nx, ny)
           && !g->m.has_flag("SWIMMABLE", posx(), posy())) &&
       (g->u.posx() != nx || g->u.posy() != ny) &&
       (g->mon_at(nx, ny) == -1) &&
       (g->npc_at(nx, ny) == -1) ) {
    point tmp(nx, ny);
    valid_stumbles.push_back(tmp);
   }
  }
 }
 if (valid_stumbles.empty()) //nowhere to stumble?
 {
     return;
 }

 int choice = rng(0, valid_stumbles.size() - 1);
 int cx = valid_stumbles[choice].x;
 int cy = valid_stumbles[choice].y;

 move_to( cx, cy, false );

 // Here we have to fix our plans[] list,
 // acquiring a new path to the previous target.
 // target == either end of current plan, or the player.
 int bresenham_slope;
 if (!plans.empty()) {
  if (g->m.sees( pos(), plans.back(), -1, bresenham_slope))
   set_dest(plans.back().x, plans.back().y, bresenham_slope);
  else if (sees( g->u, bresenham_slope ))
   set_dest(g->u.posx(), g->u.posy(), bresenham_slope);
  else //durr, i'm suddenly calm. what was i doing?
   plans.clear();
 }
}
Ejemplo n.º 8
0
Creature *monster::attack_target()
{
    if( plans.empty() ) {
        return nullptr;
    }

    point target_point = move_target();
    Creature *target = g->critter_at( target_point.x, target_point.y );
    if( target == nullptr || attitude_to( *target ) == Creature::A_FRIENDLY ||
        !sees(*target) ) {
        return nullptr;
    }
    return target;
}
Ejemplo n.º 9
0
void map::draw(game *g, WINDOW* w)
{
    int t = 0;
    int light = g->u.sight_range(g->light_level());
    for  (int realx = g->u.posx - SEEX; realx <= g->u.posx + SEEX; realx++) {
        for (int realy = g->u.posy - SEEY; realy <= g->u.posy + SEEY; realy++) {
            if (rl_dist(g->u.posx, g->u.posy, realx, realy) > light) {
                if (g->u.has_disease(DI_BOOMERED))
                    mvwputch(w, realx+SEEX - g->u.posx, realy+SEEY - g->u.posy, c_magenta,'#');
                else
                    mvwputch(w, realx+SEEX - g->u.posx, realy+SEEY - g->u.posy, c_dkgray, '#');
            } else if (sees(g->u.posx, g->u.posy, realx, realy, light, t))
                drawsq(w, g->u, realx, realy, false, true);
        }
    }
    mvwputch(w, SEEY, SEEX, g->u.color(), '@');
}
Ejemplo n.º 10
0
/* will_reach() is used for determining whether we'll get to stairs (and
 * potentially other locations of interest).  It is generally permissive.
 * TODO: Pathfinding;
         Make sure that non-smashing monsters won't "teleport" through windows
         Injure monsters if they're gonna be walking through pits or whatevs
 */
bool monster::will_reach( int x, int y )
{
    monster_attitude att = attitude( &( g->u ) );
    if( att != MATT_FOLLOW && att != MATT_ATTACK && att != MATT_FRIEND && att != MATT_ZLAVE ) {
        return false;
    }

    if( has_flag( MF_DIGS ) || has_flag( MF_AQUATIC ) ) {
        return false;
    }

    if( has_flag( MF_IMMOBILE ) && ( posx() != x || posy() != y ) ) {
        return false;
    }

    auto path = g->m.route( pos(), tripoint( x, y, posz() ), get_pathfinding_settings() );
    if( path.empty() ) {
        return false;
    }

    if( has_flag( MF_SMELLS ) && g->scent.get( pos() ) > 0 &&
        g->scent.get( { x, y, posz() } ) > g->scent.get( pos() ) ) {
        return true;
    }

    if( can_hear() && wandf > 0 && rl_dist( wander_pos.x, wander_pos.y, x, y ) <= 2 &&
        rl_dist( posx(), posy(), wander_pos.x, wander_pos.y ) <= wandf ) {
        return true;
    }

    if( can_see() && sees( tripoint( x, y, posz() ) ) ) {
        return true;
    }

    return false;
}
Ejemplo n.º 11
0
bool Creature::sees( const int tx, const int ty ) const
{
    return sees( tripoint( tx, ty, posz() ) );
}
Ejemplo n.º 12
0
std::vector<tripoint> map::route( const tripoint &f, const tripoint &t,
                                  const int bash, const int maxdist ) const
{
    /* TODO: If the origin or destination is out of bound, figure out the closest
     * in-bounds point and go to that, then to the real origin/destination.
     */

    int linet1 = 0, linet2 = 0;
    if( !inbounds( f ) || !inbounds( t ) ) {
        // Note: The creature needs to understand not-moving upwards
        // or else the plans can cause it to do so.
        if( sees( f, t, -1, linet1, linet2 ) ) {
            return line_to( f, t, linet1, linet2 );
        } else {
            std::vector<tripoint> empty;
            return empty;
        }
    }
    // First, check for a simple straight line on flat ground
    // Except when the player is on the line - we need to do regular pathing then
    const tripoint &pl_pos = g->u.pos();
    if( f.z == t.z && clear_path( f, t, -1, 2, 2, linet1, linet2 ) ) {
        const auto line_path = line_to( f, t, linet1, linet2 );
        if( pl_pos.z != f.z ) {
            // Player on different z-level, certainly not on the line
            return line_path;
        }

        if( std::find( line_path.begin(), line_path.end(), pl_pos ) == line_path.end() ) {
            return line_path;
        }
    }

    const int pad = 8;  // Should be much bigger - low value makes pathfinders dumb!
    int minx = std::min( f.x, t.x ) - pad;
    int miny = std::min( f.y, t.y ) - pad;
    int minz = std::min( f.z, t.z ); // TODO: Make this way bigger
    int maxx = std::max( f.x, t.x ) + pad;
    int maxy = std::max( f.y, t.y ) + pad;
    int maxz = std::max( f.z, t.z ); // Same TODO as above
    clip_to_bounds( minx, miny, minz );
    clip_to_bounds( maxx, maxy, maxz );

    pathfinder pf( minx, miny, maxx, maxy );
    pf.add_point( 0, 0, f, f );
    // Make NPCs not want to path through player
    // But don't make player pathing stop working
    if( f != pl_pos && t != pl_pos ) {
        pf.close_point( pl_pos );
    }

    bool done = false;

    do {
        auto cur = pf.get_next();

        const int parent_index = flat_index( cur.x, cur.y );
        auto &layer = pf.get_layer( cur.z );
        auto &cur_state = layer.state[parent_index];
        if( cur_state == ASL_CLOSED ) {
            continue;
        }

        if( layer.gscore[parent_index] > maxdist ) {
            // Shortest path would be too long, return empty vector
            return std::vector<tripoint>();
        }

        if( cur == t ) {
            done = true;
            break;
        }

        cur_state = ASL_CLOSED;
        std::vector<tripoint> neighbors = closest_tripoints_first( 1, cur );

        for( const auto &p : neighbors ) {
            const int index = flat_index( p.x, p.y );

            // TODO: Remove this and instead have sentinels at the edges
            if( p.x < minx || p.x >= maxx || p.y < miny || p.y >= maxy ) {
                continue;
            }

            if( layer.state[index] == ASL_CLOSED ) {
                continue;
            }

            int part = -1;
            const maptile &tile = maptile_at_internal( p );
            const auto &terrain = tile.get_ter_t();
            const auto &furniture = tile.get_furn_t();
            const vehicle *veh = veh_at_internal( p, part );

            const int cost = move_cost_internal( furniture, terrain, veh, part );
            // Don't calculate bash rating unless we intend to actually use it
            const int rating = ( bash == 0 || cost != 0 ) ? -1 :
                                 bash_rating_internal( bash, furniture, terrain, false, veh, part );

            if( cost == 0 && rating <= 0 && terrain.open.empty() ) {
                layer.state[index] = ASL_CLOSED; // Close it so that next time we won't try to calc costs
                continue;
            }

            int newg = layer.gscore[parent_index] + cost + ( (cur.x != p.x && cur.y != p.y ) ? 1 : 0);
            if( cost == 0 ) {
                // Handle all kinds of doors
                // Only try to open INSIDE doors from the inside

                if( !terrain.open.empty() &&
                    ( !terrain.has_flag( "OPENCLOSE_INSIDE" ) || !is_outside( cur ) ) ) {
                    newg += 4; // To open and then move onto the tile
                } else if( veh != nullptr ) {
                    part = veh->obstacle_at_part( part );
                    int dummy = -1;
                    if( !veh->part_flag( part, "OPENCLOSE_INSIDE" ) || veh_at_internal( cur, dummy ) == veh ) {
                        // Handle car doors, but don't try to path through curtains
                        newg += 10; // One turn to open, 4 to move there
                    } else {
                        // Car obstacle that isn't a door
                        newg += veh->parts[part].hp / bash + 8 + 4;
                    }
                } else if( rating > 1 ) {
                    // Expected number of turns to bash it down, 1 turn to move there
                    // and 2 turns of penalty not to trash everything just because we can
                    newg += ( 20 / rating ) + 2 + 4;
                } else if( rating == 1 ) {
                    // Desperate measures, avoid whenever possible
                    newg += 500;
                } else {
                    continue; // Unbashable and unopenable from here
                }
            }

            // If not visited, add as open
            // If visited, add it only if we can do so with better score
            if( layer.state[index] == ASL_NONE || newg < layer.gscore[index] ) {
                pf.add_point( newg, newg + 2 * rl_dist( p, t ), cur, p );
            }
        }

        if( !has_zlevels() ) {
            // The part below is only for z-level pathing
            continue;
        }

        const maptile &parent_tile = maptile_at_internal( cur );
        const auto &parent_terrain = parent_tile.get_ter_t();
        if( cur.z > minz && parent_terrain.has_flag( TFLAG_GOES_DOWN ) ) {
            tripoint dest( cur.x, cur.y, cur.z - 1 );
            dest = vertical_move_destination<TFLAG_GOES_UP>( *this, dest );
            if( inbounds( dest ) ) {
                auto &layer = pf.get_layer( dest.z );
                pf.add_point( layer.gscore[parent_index] + 2,
                              layer.score[parent_index] + 2 * rl_dist( dest, t ),
                              cur, dest );
            }
        }
        if( cur.z < maxz && parent_terrain.has_flag( TFLAG_GOES_UP ) ) {
            tripoint dest( cur.x, cur.y, cur.z + 1 );
            dest = vertical_move_destination<TFLAG_GOES_DOWN>( *this, dest );
            if( inbounds( dest ) ) {
                auto &layer = pf.get_layer( dest.z );
                pf.add_point( layer.gscore[parent_index] + 2,
                              layer.score[parent_index] + 2 * rl_dist( dest, t ),
                              cur, dest );
            }
        }
    } while( !done && !pf.empty() );

    std::vector<tripoint> ret;
    ret.reserve( rl_dist( f, t ) * 2 );
    if( done ) {
        tripoint cur = t;
        // Just to limit max distance, in case something weird happens
        for( int fdist = maxdist; fdist != 0; fdist-- ) {
            const int cur_index = flat_index( cur.x, cur.y );
            const auto &layer = pf.get_layer( cur.z );
            const tripoint &par = layer.parent[cur_index];
            if( cur == f ) {
                break;
            }

            ret.push_back( cur );
            // Jumps are acceptable on 1 z-level changes
            // This is because stairs teleport the player too
            if( rl_dist( cur, par ) > 1 && abs( cur.z - par.z ) != 1 ) {
                debugmsg( "Jump in our route! %d:%d:%d->%d:%d:%d",
                          cur.x, cur.y, cur.z, par.x, par.y, par.z );
                return ret;
            }

            cur = par;
        }

        std::reverse( ret.begin(), ret.end() );
    }

    return ret;
}
Ejemplo n.º 13
0
bool Creature::sees( const tripoint &t ) const
{
    // TODO: FoV update
    int junk1, junk2;
    return sees( t, junk1, junk2 );
}
Ejemplo n.º 14
0
bool Creature::sees( const int tx, const int ty, int &bresenham_slope ) const
{
    int junk;
    return sees( tripoint( tx, ty, posz() ), bresenham_slope, junk );
}
Ejemplo n.º 15
0
bool Creature::sees( const point t ) const
{
    int bresen1, bresen2;
    return sees( tripoint( t, posz() ), bresen1, bresen2 );
}
Ejemplo n.º 16
0
bool Creature::sees( const int tx, const int ty ) const
{
    int bresen1, bresen2;
    return sees( tripoint( tx, ty, posz() ), bresen1, bresen2 );
}
Ejemplo n.º 17
0
bool Creature::sees( const Creature &critter ) const
{
    int junk1, junk2;
    return sees( critter, junk1, junk2 );
}
Ejemplo n.º 18
0
void creature_t::update()
{

  if (flag & CF_SPELLCASTER)
  {
    if (spell_timer > 0)
      spell_timer--;
  }

  if (get_active_effects_for(this) & EF_CONFUSE)
  {
    random_walk();
  }
  else if (identity == IDENT_SHOPKEEPER)
  {
    update_shopkeeper(this);
  }
  else if (flag & CF_NEUTRAL)
  {
    random_walk();
  }
  else if ((flag & CF_HIDES) && (!(get_active_effects_for(this) & EF_HIDDEN)) && !player.sees(this))
  {
    hide_creature(this);
  }
  else if (flag & CF_PLAYER_ALLY)
  {
    creature_t* closest_enemy = get_closest_enemy();
    if (closest_enemy && sees(closest_enemy))
    {
      if ((flag & CF_SPELLCASTER) && spell_timer == 0)
      {
        creature_cast_spell(this, closest_enemy);
      }
      else
      {
        step_toward(closest_enemy->pos.x, closest_enemy->pos.y, closest_enemy);
      }
    }
    else
    {
      _regular_walk_around(this);
    }
  }
  else if (sees(&player))
  {
    if (seen_player == 0)
    {
      // Shout should depend on if the creature is intelligent etc.
      if ((flag & CF_SPEAKS)&& !(flag & CF_SPELLCASTER))
      {
        shout("");
      }
      seen_player = 1;
    }
    forget_player_timer = FORGETFULNESS;

    if ((flag & CF_SPELLCASTER) && spell_timer == 0)
    {
      creature_cast_spell(this, &player);
    }
    else
    {
      bool move_towards = true;

      if (flag & CF_INTELLIGENT)
      {
        if (_try_zap_wand(this))
        {
          move_towards = false;
        }
      }

      if (move_towards)
      {
        step_toward(player.pos.x, player.pos.y, nullptr);
      }
    }

    // Stop being interested in whatever noise it heard.
    heard_noise = 0;
  }
  else
  {
    // If the spell timer has reached 0 while the creature is outside player view, reset
    // it for a random value to make things more varied when a caster enters view.
    if (spell_timer == 0)
    {
      spell_timer = random(0, spell_timer_duration);
    }

    if (seen_player && forget_player_timer > 0)
    {
      forget_player_timer--;
      step_toward(player.pos.x, player.pos.y, nullptr);
    }
    else
    {
      seen_player = 0;
      if (heard_noise)
      {
        step_toward(noise_location.x, noise_location.y, nullptr);
        if (pos.x == noise_location.x && pos.y == noise_location.y)
        {
          // Found the position of the noise.
          heard_noise = 0;
        }
      }
      else
      {
        // If the player has allies and I can see one, attack it.
        creature_t* closest_enemy = get_closest_enemy();
        if (closest_enemy && sees(closest_enemy))
        {
          step_toward(closest_enemy->pos.x, closest_enemy->pos.y, closest_enemy);
        }
        else
        {
          _regular_walk_around(this);
        }
      }
    }
  }

  ap -= speed;
}
Ejemplo n.º 19
0
void player::fire_gun(int tarx, int tary, bool burst) {
    item ammotmp;
    item* gunmod = weapon.active_gunmod();
    it_ammo *curammo = NULL;
    item *used_weapon = NULL;

    if (weapon.has_flag("CHARGE")) { // It's a charger gun, so make up a type
        // Charges maxes out at 8.
        int charges = weapon.num_charges();
        it_ammo *tmpammo = dynamic_cast<it_ammo*>(itypes["charge_shot"]);

        tmpammo->damage = charges * charges;
        tmpammo->pierce = (charges >= 4 ? (charges - 3) * 2.5 : 0);
        if (charges <= 4)
            tmpammo->dispersion = 14 - charges * 2;
        else // 5, 12, 21, 32
            tmpammo->dispersion = charges * (charges - 4);
        tmpammo->recoil = tmpammo->dispersion * .8;
        tmpammo->ammo_effects.clear(); // Reset effects.
        if (charges == 8) { tmpammo->ammo_effects.insert("EXPLOSIVE_BIG"); }
        else if (charges >= 6) { tmpammo->ammo_effects.insert("EXPLOSIVE"); }

        if (charges >= 5){ tmpammo->ammo_effects.insert("FLAME"); }
        else if (charges >= 4) { tmpammo->ammo_effects.insert("INCENDIARY"); }

        if (gunmod != NULL) { // TODO: range calculation in case of active gunmod.
            used_weapon = gunmod;
        } else {
            used_weapon = &weapon;
        }

        curammo = tmpammo;
        used_weapon->curammo = tmpammo;
    } else if (gunmod != NULL) {
        used_weapon = gunmod;
        curammo = used_weapon->curammo;
    } else {// Just a normal gun. If we're here, we know curammo is valid.
        curammo = weapon.curammo;
        used_weapon = &weapon;
    }

    ammotmp = item(curammo, 0);
    ammotmp.charges = 1;

    if (!used_weapon->is_gun() && !used_weapon->is_gunmod()) {
        debugmsg("%s tried to fire a non-gun (%s).", name.c_str(),
                                                    used_weapon->tname().c_str());
        return;
    }

    projectile proj; // damage will be set later
    proj.aoe_size = 0;
    proj.ammo = curammo;
    proj.speed = 1000;

    std::set<std::string> *curammo_effects = &curammo->ammo_effects;
    if(gunmod == NULL){
        std::set<std::string> *gun_effects = &dynamic_cast<it_gun*>(used_weapon->type)->ammo_effects;
        proj.proj_effects.insert(gun_effects->begin(),gun_effects->end());
    }
    proj.proj_effects.insert(curammo_effects->begin(),curammo_effects->end());

    proj.wide = (curammo->phase == LIQUID ||
            proj.proj_effects.count("SHOT") || proj.proj_effects.count("BOUNCE"));
    proj.drops = (curammo->type == "bolt" || curammo->type == "arrow");

    //int x = xpos(), y = ypos();
    // Have to use the gun, gunmods don't have a type
    it_gun* firing = dynamic_cast<it_gun*>(weapon.type);
    if (has_trait("TRIGGERHAPPY") && one_in(30))
        burst = true;
    if (burst && used_weapon->burst_size() < 2)
        burst = false; // Can't burst fire a semi-auto

    // Use different amounts of time depending on the type of gun and our skill
    if (!proj.proj_effects.count("BOUNCE")) {
        moves -= time_to_fire(*this, firing);
    }
    // Decide how many shots to fire
    int num_shots = 1;
    if (burst)
        num_shots = used_weapon->burst_size();
    if (num_shots > used_weapon->num_charges() && !used_weapon->has_flag("CHARGE") && !used_weapon->has_flag("NO_AMMO"))
        num_shots = used_weapon->num_charges();

    if (num_shots == 0)
        debugmsg("game::fire() - num_shots = 0!");

    int ups_drain = 0;
    int adv_ups_drain = 0;
    if (weapon.has_flag("USE_UPS")) {
        ups_drain = 5;
        adv_ups_drain = 3;
    } else if (weapon.has_flag("USE_UPS_20")) {
        ups_drain = 20;
        adv_ups_drain = 12;
    } else if (weapon.has_flag("USE_UPS_40")) {
        ups_drain = 40;
        adv_ups_drain = 24;
    }

    // cap our maximum burst size by the amount of UPS power left
    if (ups_drain > 0 || adv_ups_drain > 0)
    while (!(has_charges("UPS_off", ups_drain*num_shots) ||
                has_charges("UPS_on", ups_drain*num_shots) ||
                has_charges("adv_UPS_off", adv_ups_drain*num_shots) ||
                has_charges("adv_UPS_on", adv_ups_drain*num_shots))) {
        num_shots--;
    }

    const bool debug_retarget = false;  // this will inevitably be needed
    //const bool wildly_spraying = false; // stub for now. later, rng based on stress/skill/etc at the start,
    int weaponrange = weapon.range(); // this is expensive, let's cache. todo: figure out if we need weapon.range(&p);

    for (int curshot = 0; curshot < num_shots; curshot++) {
        // Burst-fire weapons allow us to pick a new target after killing the first
        int zid = g->mon_at(tarx, tary);
        if ( curshot > 0 && (zid == -1 || g->zombie(zid).hp <= 0) ) {
            std::vector<point> new_targets;
            new_targets.clear();

            if ( debug_retarget == true ) {
                mvprintz(curshot,5,c_red,"[%d] %s: retarget: mon_at(%d,%d)",curshot,name.c_str(),tarx,tary);
                if(zid == -1) {
                    printz(c_red, " = -1");
                } else {
                    printz(c_red, ".hp=%d", g->zombie(zid).hp);
                }
            }

            for (unsigned long int i = 0; i < g->num_zombies(); i++) {
                monster &z = g->zombie(i);
                int dummy;
                // search for monsters in radius
                if (rl_dist(z.posx(), z.posy(), tarx, tary) <= std::min(2 + skillLevel("gun"), weaponrange) &&
                        rl_dist(xpos(),ypos(),z.xpos(),z.ypos()) <= weaponrange &&
                        sees(&z, dummy) ) {
                    if (!z.is_dead_state())
                        new_targets.push_back(point(z.xpos(), z.ypos())); // oh you're not dead and I don't like you. Hello!
                }
            }

            if ( new_targets.empty() == false ) {    /* new victim! or last victim moved */
                int target_picked = rng(0, new_targets.size() - 1); /* 1 victim list unless wildly spraying */
                tarx = new_targets[target_picked].x;
                tary = new_targets[target_picked].y;
                zid = g->mon_at(tarx, tary);

                /* debug */ if (debug_retarget) printz(c_ltgreen, " NEW:(%d:%d,%d) %d,%d (%s)[%d] hp: %d",
                    target_picked, new_targets[target_picked].x, new_targets[target_picked].y,
                    tarx, tary, g->zombie(zid).name().c_str(), zid, g->zombie(zid).hp);

            } else if (
                (
                    !has_trait("TRIGGERHAPPY") ||   /* double ta TRIPLE TAP! wait, no... */
                    one_in(3)                          /* on second though...everyone double-taps at times. */
                ) && (
                    skillLevel("gun") >= 7 ||        /* unless trained */
                    one_in(7 - skillLevel("gun"))    /* ...sometimes */
                ) ) {
                return;                               // No targets, so return
            } else if (debug_retarget) {
                printz(c_red, " new targets.empty()!");
            }
        } else if (debug_retarget) {
            const int zid = g->mon_at(tarx, tary);
            mvprintz(curshot,5,c_red,"[%d] %s: target == mon_at(%d,%d)[%d] %s hp %d",curshot, name.c_str(), tarx ,tary,
            zid,
            g->zombie(zid).name().c_str(),
            g->zombie(zid).hp);
        }

        // Drop a shell casing if appropriate.
        itype_id casing_type = curammo->casing;
        if (casing_type != "NULL" && !casing_type.empty()) {
            item casing;
            casing.make(itypes[casing_type]);
            // Casing needs a charges of 1 to stack properly with other casings.
            casing.charges = 1;
            if( used_weapon->has_gunmod("brass_catcher") != -1 ) {
                i_add( casing );
            } else {
                int x = 0;
                int y = 0;
                int count = 0;
                do {
                    x = xpos() - 1 + rng(0, 2);
                    y = ypos() - 1 + rng(0, 2);
                    count++;
                    // Try not to drop the casing on a wall if at all possible.
                } while( g->m.move_cost( x, y ) == 0 && count < 10 );
                g->m.add_item_or_charges(x, y, casing);
            }
        }

        // Use up a round (or 100)
        if (used_weapon->has_flag("FIRE_100")) {
            used_weapon->charges -= 100;
        } else if (used_weapon->has_flag("FIRE_50")) {
            used_weapon->charges -= 50;
        } else if (used_weapon->has_flag("CHARGE")) {
            used_weapon->active = false;
            used_weapon->charges = 0;
        } else if (!used_weapon->has_flag("NO_AMMO")) {
            used_weapon->charges--;
        }

        // Drain UPS power
        if (has_charges("adv_UPS_off", adv_ups_drain)) {
            use_charges("adv_UPS_off", adv_ups_drain);
        } else if (has_charges("adv_UPS_on", adv_ups_drain)) {
            use_charges("adv_UPS_on", adv_ups_drain);
        } else if (has_charges("UPS_off", ups_drain)) {
            use_charges("UPS_off", ups_drain);
        } else if (has_charges("UPS_on", ups_drain)) {
            use_charges("UPS_on", ups_drain);
        }

        if (firing->skill_used != Skill::skill("archery") &&
            firing->skill_used != Skill::skill("throw")) {
            // Current guns have a durability between 5 and 9.
            // Misfire chance is between 1/64 and 1/1024.
            if (is_underwater() && !weapon.has_flag("WATERPROOF_GUN") && one_in(firing->durability)) {
                g->add_msg_player_or_npc(this, _("Your weapon misfires with a wet click!"),
                                         _("<npcname>'s weapon misfires with a wet click!") );
                return;
            } else if (one_in(2 << firing->durability)) {
                g->add_msg_player_or_npc(this, _("Your weapon misfires!"),
                                         _("<npcname>'s weapon misfires!") );
                return;
            }
        }

        make_gun_sound_effect(*this, burst, used_weapon);

        double total_dispersion = get_weapon_dispersion(used_weapon);
        //debugmsg("%f",total_dispersion);
        int range = rl_dist(xpos(), ypos(), tarx, tary);
        // penalties for point-blank
        if (range < (firing->volume/3) && firing->ammo != "shot")
            total_dispersion *= double(firing->volume/3) / double(range);

        // rifle has less range penalty past LONG_RANGE
        if (firing->skill_used == Skill::skill("rifle") && range > LONG_RANGE)
            total_dispersion *= 1 - 0.4*double(range - LONG_RANGE) / double(range);

        if (curshot > 0) {
            if (recoil_add(*this) % 2 == 1) {
                recoil++;
            }
            recoil += recoil_add(*this) / 2;
        } else {
            recoil += recoil_add(*this);
        }

        int mtarx = tarx;
        int mtary = tary;

        int adjusted_damage = used_weapon->gun_damage();

        proj.impact = damage_instance::physical(0,adjusted_damage,0);

        double missed_by = projectile_attack(proj, mtarx, mtary, total_dispersion);
        if (missed_by <= .1) { // TODO: check head existence for headshot
            practice(g->turn, firing->skill_used, 5);
            lifetime_stats()->headshots++;
        } else if (missed_by <= .2) {
            practice(g->turn, firing->skill_used, 3);
        } else if (missed_by <= .4) {
            practice(g->turn, firing->skill_used, 2);
        } else if (missed_by <= .6) {
            practice(g->turn, firing->skill_used, 1);
        }

    }

    if (used_weapon->num_charges() == 0) {
        used_weapon->curammo = NULL;
    }

}
Ejemplo n.º 20
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;
    int bresenham_slope = 0;
    int selected_slope = 0;
    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, bresenham_slope ) ) {
        dist = rate_target( g->u, bresenham_slope, dist, electronic );
        fleeing = fleeing || is_fleeing( g->u );
        target = &g->u;
        selected_slope = bresenham_slope;
        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, bresenham_slope, dist, electronic );
                if( rating < dist ) {
                    target = &tmp;
                    dist = rating;
                    selected_slope = bresenham_slope;
                }
            }
        }
    }

    if( !docile ) {
        for( size_t i = 0; i < g->active_npc.size(); i++ ) {
            npc *me = g->active_npc[i];
            float rating = rate_target( *me, bresenham_slope, 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;
                    selected_slope = bresenham_slope;
            }
            fleeing = fleeing || fleeing_from;
            if( rating <= 5 ) {
                anger += angers_hostile_near;
                morale -= fears_hostile_near;
            }
        }
    }

    fleeing = fleeing || ( mood == MATT_FLEE );
    if( friendly == 0 && !docile ) {
        for( const auto &fac : factions ) {
            auto faction_att = faction->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, bresenham_slope, dist, electronic );
                if( rating < dist ) {
                    target = &mon;
                    dist = rating;
                    selected_slope = bresenham_slope;
                }
                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 monfaction *actual_faction = friendly == 0 ? faction : GetMFact( "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 " << 
            ( friendly == 0 ? faction->name : "player" ) << " 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 ) {
        auto const &myfaction = myfaction_iter->second;
        for( int i : myfaction ) {
            monster &mon = g->zombie( i );
            float rating = rate_target( mon, bresenham_slope, dist, electronic );
            if( group_morale && rating <= 10 ) {
                morale += 10 - rating;
            }
            if( swarms ) {
                if( rating < 5 ) { // Too crowded here
                    wandx = posx() * rng( 1, 3 ) - mon.posx();
                    wandy = 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;
                    selected_slope = bresenham_slope;
                }
            }
        }
    }

    if( target != nullptr ) {
        if( one_in( 2 ) ) { // Random for the diversity of the trajectory
            ++selected_slope;
        } else {
            --selected_slope;
        }

        point dest = target->pos();
        auto att_to_target = attitude_to( *target );
        if( att_to_target == Attitude::A_HOSTILE && !fleeing ) {
            set_dest( dest.x, dest.y, selected_slope );
        } else if( fleeing ) {
            set_dest( posx() * 2 - dest.x, posy() * 2 - dest.y, selected_slope );
        }
        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, bresenham_slope ) ) {
        if( rl_dist( pos(), g->u.pos() ) > 2 ) {
            set_dest(g->u.posx(), g->u.posy(), bresenham_slope);
        } else {
            plans.clear();
        }
    }
    // If we're not adjacent to the start of our plan path, don't act on it.
    // This is to catch when we had pre-existing invalid plans and
    // made it through the function without changing them.
    if( !plans.empty() && square_dist(pos().x, pos().y,
                                      plans.front().x, plans.front().y ) > 1 ) {
        plans.clear();
    }
}
Ejemplo n.º 21
0
void monster::plan( const mfactions &factions )
{
    // Bots are more intelligent than most living stuff
    bool smart_planning = has_flag( MF_PRIORITIZE_TARGETS );
    Creature *target = nullptr;
    // 8.6f is rating for tank drone 60 tiles away, moose 16 or boomer 33
    float dist = !smart_planning ? 1000 : 8.6f;
    bool fleeing = false;
    bool docile = friendly != 0 && has_effect( 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, smart_planning );
        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, smart_planning );
                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 &who = *g->active_npc[i];
        auto faction_att = faction.obj().attitude( who.get_monster_faction() );
        if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) {
            continue;
        }

        float rating = rate_target( who, dist, smart_planning );
        bool fleeing_from = is_fleeing( who );
        // Switch targets if closer and hostile or scarier than current target
        if( ( rating < dist && fleeing ) ||
            ( rating < dist && attitude( &who ) == MATT_ATTACK ) ||
            ( !fleeing && fleeing_from ) ) {
            target = &who;
            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, smart_planning );
                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, smart_planning );
            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( pos(), g->u.pos() ) > 2 ) {
            set_dest( g->u.pos() );
        } else {
            unset_dest();
        }
    }
}
Ejemplo n.º 22
0
bool Creature::sees( const point t ) const
{
    return sees( tripoint( t, posz() ) );
}
Ejemplo n.º 23
0
Creature *Creature::auto_find_hostile_target( int range, int &boo_hoo, int area )
{
    Creature *target = nullptr;
    player &u = g->u; // Could easily protect something that isn't the player
    constexpr int hostile_adj = 2; // Priority bonus for hostile targets
    const int iff_dist = ( range + area ) * 3 / 2 + 6; // iff check triggers at this distance
    int iff_hangle = 15 + area; // iff safety margin (degrees). less accuracy, more paranoia
    float best_target_rating = -1.0f; // bigger is better
    int u_angle = 0;         // player angle relative to turret
    boo_hoo = 0;         // how many targets were passed due to IFF. Tragically.
    bool self_area_iff = false; // Need to check if the target is near the vehicle we're a part of
    bool area_iff = false;      // Need to check distance from target to player
    bool angle_iff = true;      // Need to check if player is in a cone between us and target
    int pldist = rl_dist( pos(), g->u.pos() );
    vehicle *in_veh = is_fake() ? veh_pointer_or_null( g->m.veh_at( pos() ) ) : nullptr;
    if( pldist < iff_dist && sees( g->u ) ) {
        area_iff = area > 0;
        angle_iff = true;
        // Player inside vehicle won't be hit by shots from the roof,
        // so we can fire "through" them just fine.
        const optional_vpart_position vp = g->m.veh_at( u.pos() );
        if( in_veh && veh_pointer_or_null( vp ) == in_veh && vp->is_inside() ) {
            angle_iff = false; // No angle IFF, but possibly area IFF
        } else if( pldist < 3 ) {
            iff_hangle = (pldist == 2 ? 30 : 60);    // granularity increases with proximity
        }
        u_angle = g->m.coord_to_angle(posx(), posy(), u.posx(), u.posy());
    }

    if( area > 0 && in_veh != nullptr ) {
        self_area_iff = true;
    }

    std::vector<Creature*> targets = g->get_creatures_if( [&]( const Creature &critter ) {
        if( const monster *const mon_ptr = dynamic_cast<const monster*>( &critter ) ) {
            // friendly to the player, not a target for us
            return mon_ptr->friendly == 0;
        }
        if( const npc *const npc_ptr = dynamic_cast<const npc*>( &critter ) ) {
            // friendly to the player, not a target for us
            return npc_ptr->get_attitude() == NPCATT_KILL;
        }
        //@todo: what about g->u?
        return false;
    } );
    for( auto &m : targets ) {
        if( !sees( *m ) ) {
            // can't see nor sense it
            continue;
        }
        int dist = rl_dist( pos(), m->pos() ) + 1; // rl_dist can be 0
        if( dist > range + 1 || dist < area ) {
            // Too near or too far
            continue;
        }
        // Prioritize big, armed and hostile stuff
        float mon_rating = m->power_rating();
        float target_rating = mon_rating / dist;
        if( mon_rating + hostile_adj <= 0 ) {
            // We wouldn't attack it even if it was hostile
            continue;
        }

        if( in_veh != nullptr && veh_pointer_or_null( g->m.veh_at( m->pos() ) ) == in_veh ) {
            // No shooting stuff on vehicle we're a part of
            continue;
        }
        if( area_iff && rl_dist( u.pos(), m->pos() ) <= area ) {
            // Player in AoE
            boo_hoo++;
            continue;
        }
        // Hostility check can be expensive, but we need to inform the player of boo_hoo
        // only when the target is actually "hostile enough"
        bool maybe_boo = false;
        if( angle_iff ) {
            int tangle = g->m.coord_to_angle(posx(), posy(), m->posx(), m->posy());
            int diff = abs(u_angle - tangle);
            // Player is in the angle and not too far behind the target
            if( ( diff + iff_hangle > 360 || diff < iff_hangle ) &&
                ( dist * 3 / 2 + 6 > pldist ) ) {
                maybe_boo = true;
            }
        }
        if( !maybe_boo && ( ( mon_rating + hostile_adj ) / dist <= best_target_rating ) ) {
            // "Would we skip the target even if it was hostile?"
            // Helps avoid (possibly expensive) attitude calculation
            continue;
        }
        if( m->attitude_to( u ) == A_HOSTILE ) {
            target_rating = ( mon_rating + hostile_adj ) / dist;
            if( maybe_boo ) {
                boo_hoo++;
                continue;
            }
        }
        if( target_rating <= best_target_rating || target_rating <= 0 ) {
            continue; // Handle this late so that boo_hoo++ can happen
        }
        // Expensive check for proximity to vehicle
        if( self_area_iff && overlaps_vehicle( in_veh->get_points(), m->pos(), area ) ) {
            continue;
        }

        target = m;
        best_target_rating = target_rating;
    }
    return target;
}
Ejemplo n.º 24
0
bool Creature::sees( const Creature &critter, int &bresenham_slope ) const
{
    int bresen2;
    return sees( critter, bresenham_slope, bresen2 );
}