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; }
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; }
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() ); }
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 ); }
/* 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(); } } }
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()); } } }
/* 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(); } }
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; }
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(), '@'); }
/* 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; }
bool Creature::sees( const int tx, const int ty ) const { return sees( tripoint( tx, ty, posz() ) ); }
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; }
bool Creature::sees( const tripoint &t ) const { // TODO: FoV update int junk1, junk2; return sees( t, junk1, junk2 ); }
bool Creature::sees( const int tx, const int ty, int &bresenham_slope ) const { int junk; return sees( tripoint( tx, ty, posz() ), bresenham_slope, junk ); }
bool Creature::sees( const point t ) const { int bresen1, bresen2; return sees( tripoint( t, posz() ), bresen1, bresen2 ); }
bool Creature::sees( const int tx, const int ty ) const { int bresen1, bresen2; return sees( tripoint( tx, ty, posz() ), bresen1, bresen2 ); }
bool Creature::sees( const Creature &critter ) const { int junk1, junk2; return sees( critter, junk1, junk2 ); }
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; }
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; } }
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(); } }
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(); } } }
bool Creature::sees( const point t ) const { return sees( tripoint( t, posz() ) ); }
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; }
bool Creature::sees( const Creature &critter, int &bresenham_slope ) const { int bresen2; return sees( critter, bresenham_slope, bresen2 ); }