// firing is the item that is fired. It may be the wielded gun, but it can also be an attached // gunmod. p is the character that is firing, this may be a pseudo-character (used by monattack/ // vehicle turrets) or a NPC. void sfx::generate_gun_sound( const player &p, const item &firing ) { end_sfx_timestamp = std::chrono::high_resolution_clock::now(); sfx_time = end_sfx_timestamp - start_sfx_timestamp; if( std::chrono::duration_cast<std::chrono::milliseconds> ( sfx_time ).count() < 80 ) { return; } const tripoint source = p.pos(); int heard_volume = get_heard_volume( source ); if( heard_volume <= 30 ) { heard_volume = 30; } itype_id weapon_id = firing.typeId(); int angle; int distance; std::string selected_sound; // this does not mean p == g->u (it could be a vehicle turret) if( g->u.pos() == source ) { angle = 0; distance = 0; selected_sound = "fire_gun"; const auto mods = firing.gunmods(); if( std::any_of( mods.begin(), mods.end(), []( const item *e ) { return e->type->gunmod->loudness < 0; } ) ) { weapon_id = "weapon_fire_suppressed"; } } else { angle = get_heard_angle( source ); distance = rl_dist( g->u.pos(), source ); if( distance <= 17 ) { selected_sound = "fire_gun"; } else { selected_sound = "fire_gun_distant"; } } play_variant_sound( selected_sound, weapon_id, heard_volume, angle, 0.8, 1.2 ); start_sfx_timestamp = std::chrono::high_resolution_clock::now(); }
int monster::group_bash_skill( point target ) { if( !has_flag(MF_GROUP_BASH) ) { return bash_skill(); } int bashskill = 0; // pileup = more bashskill, but only help bashing mob directly infront of target const int max_helper_depth = 5; const std::vector<point> bzone = get_bashing_zone( target, pos(), max_helper_depth ); for( point candidate : bzone ) { // Drawing this line backwards excludes the target and includes the candidate. std::vector<point> path_to_target = line_to( target, candidate, 0 ); bool connected = true; int mondex = -1; for( point in_path : path_to_target ) { // If any point in the line from zombie to target is not a cooperating zombie, // it can't contribute. mondex = g->mon_at( in_path ); if( mondex == -1 ) { connected = false; break; } monster &helpermon = g->zombie( mondex ); if( !helpermon.has_flag(MF_GROUP_BASH) || helpermon.is_hallucination() ) { connected = false; break; } } if( !connected ) { continue; } // If we made it here, the last monster checked was the candidate. monster &helpermon = g->zombie( mondex ); // Contribution falls off rapidly with distance from target. bashskill += helpermon.bash_skill() / rl_dist( candidate, target ); } return bashskill; }
void mdeath::jabberwock( monster &z ) { player *ch = dynamic_cast<player *>( z.get_killer() ); bool vorpal = ch && ch->is_player() && rl_dist( z.pos(), ch->pos() ) <= 1 && ch->weapon.has_flag( "DIAMOND" ) && ch->weapon.volume() > units::from_milliliter( 750 ); if( vorpal && !ch->weapon.has_technique( matec_id( "VORPAL" ) ) ) { if( ch->sees( z ) ) { //~ %s is the possessive form of the monster's name ch->add_msg_if_player( m_info, _( "As the flames in %s eyes die out, your weapon seems to shine slightly brighter." ), z.disp_name( true ) ); } ch->weapon.add_technique( matec_id( "VORPAL" ) ); } mdeath::normal( z ); }
void mdefense::zapback(monster *m, const projectile *proj) { int j; if (rl_dist(m->posx(), m->posy(), g->u.posx, g->u.posy) > 1 || !g->sees_u(m->posx(), m->posy(), j)) { return; // Out of range } if (proj != NULL) { return; // Not a melee attack } if ((!g->u.has_active_bionic("bio_faraday") && !g->u.worn_with_flag("ELECTRIC_IMMUNE") && !g->u.has_artifact_with(AEP_RESIST_ELECTRICITY)) && (g->u.weapon.conductive() || g->u.unarmed_attack()) && (rng(0, 100) <= m->def_chance)) { damage_instance shock; shock.add_damage(DT_ELECTRIC, rng(1, 5)); g->u.deal_damage(m, bp_arm_l, shock); g->u.deal_damage(m, bp_arm_r, shock); add_msg(m_bad, _("Striking the %s shocks you!"), m->name().c_str()); } return; }
void mdeath::guilt(game *g, monster *z) { if (g->u.has_trait(PF_CANNIBAL)) return; // We don't give a shit! if (rl_dist(z->posx, z->posy, g->u.posx, g->u.posy) > 5) return; // Too far away, we can deal with it if (z->hp >= 0) return; // It probably didn't die from damage g->add_msg("You feel terrible for killing %s!", z->name().c_str()); if(z->type->id == mon_hallu_mom) { g->u.add_morale(MORALE_KILLED_MONSTER, -50, -250); } else if(z->type->id == mon_zombie_child) { g->u.add_morale(MORALE_KILLED_MONSTER, -5, -250); } else { return; } }
void mdeath::boomer(game *g, monster *z) { std::string tmp; g->sound(z->posx, z->posy, 24, "a boomer explode!"); for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { g->m.bash(z->posx + i, z->posy + j, 10, tmp); if (g->m.field_at(z->posx + i, z->posy + j).type == fd_bile && g->m.field_at(z->posx + i, z->posy + j).density < 3) g->m.field_at(z->posx + i, z->posy + j).density++; else g->m.add_field(g, z->posx + i, z->posy + j, fd_bile, 1); int mondex = g->mon_at(z->posx + i, z->posy +j); if (mondex != -1) { g->z[mondex].stumble(g, false); g->z[mondex].moves -= 250; } } } if (rl_dist(z->posx, z->posy, g->u.posx, g->u.posy) == 1) g->u.infect(DI_BOOMERED, bp_eyes, 2, 24, g); }
void talk_function::give_all_aid( npc &p ) { p.add_effect( effect_currently_busy, 30_minutes ); give_aid( p ); for( npc &guy : g->all_npcs() ) { if( rl_dist( guy.pos(), g->u.pos() ) < PICKUP_RANGE && guy.is_friend() ) { for( int i = 0; i < num_hp_parts; i++ ) { const body_part bp_healed = player::hp_to_bp( hp_part( i ) ); guy.heal( hp_part( i ), 5 * rng( 2, 5 ) ); if( guy.has_effect( effect_bite, bp_healed ) ) { guy.remove_effect( effect_bite, bp_healed ); } if( guy.has_effect( effect_bleed, bp_healed ) ) { guy.remove_effect( effect_bleed, bp_healed ); } if( guy.has_effect( effect_infected, bp_healed ) ) { guy.remove_effect( effect_infected, bp_healed ); } } } } }
bool gun_actor::call( monster &z ) const { Creature *target; if( z.friendly ) { int max_range = 0; for( const auto &e : ranges ) { max_range = std::max( std::max( max_range, e.first.first ), e.first.second ); } int hostiles; // hostiles which cannot be engaged without risking friendly fire target = z.auto_find_hostile_target( max_range, hostiles ); if( !target ) { if( hostiles > 0 && g->u.sees( z ) ) { add_msg( m_warning, ngettext( "Pointed in your direction, the %s emits an IFF warning beep.", "Pointed in your direction, the %s emits %d annoyed sounding beeps.", hostiles ), z.name(), hostiles ); } return false; } } else { target = z.attack_target(); if( !target || !z.sees( *target ) ) { return false; } } int dist = rl_dist( z.pos(), target->pos() ); for( const auto &e : ranges ) { if( dist >= e.first.first && dist <= e.first.second ) { shoot( z, *target, e.second ); return true; } } return false; }
city_reference overmapbuffer::closest_city( const tripoint ¢er ) { // a whole overmap (because it's in submap coordinates, OMAPX is overmap terrain coordinates) auto const radius = OMAPX * 2; // Starting with distance = INT_MAX, so the first city is already closer city_reference result{ nullptr, nullptr, tripoint( 0, 0, 0 ), INT_MAX }; for( auto &om : get_overmaps_near( center, radius ) ) { const auto abs_pos_om = om_to_sm_copy( om->pos() ); for( auto &city : om->cities ) { const auto rel_pos_city = omt_to_sm_copy( point( city.x, city.y ) ); // TODO: Z-level cities. This 0 has to be here until mapgen understands non-0 zlev cities const auto abs_pos_city = tripoint( abs_pos_om + rel_pos_city, 0 ); const auto distance = rl_dist( abs_pos_city, center ); const city_reference cr{ om, &city, abs_pos_city, distance }; if( distance < result.distance ) { result = cr; } else if( distance == result.distance && result.city->s < city.s ) { result = cr; } } } return result; }
bool trap::detect_trap(const player &p, int x, int y) const { // Some decisions are based around: // * Starting, and thus average perception, is 8. // * Buried landmines, the silent killer, has a visibility of 10. // * There will always be a distance malus of 1 unless you're on top of the trap. // * ...and an average character should at least have a minor chance of // noticing a buried landmine if standing right next to it. // Effective Perception... return (p.per_cur - const_cast<player&>(p).encumb(bp_eyes)) + // ...small bonus from stimulants... (p.stim > 10 ? rng(1, 2) : 0) + // ...bonus from trap skill... (const_cast<player&>(p).skillLevel("traps") * 2) + // ...luck, might be good, might be bad... rng(-4, 4) - // ...malus if we are tired... (p.has_disease("lack_sleep") ? rng(1, 5) : 0) - // ...malus farther we are from trap... rl_dist(p.posx, p.posy, x, y) > // ...must all be greater than the trap visibility. visibility; }
std::vector<city_reference> overmapbuffer::get_cities_near( const tripoint &location, int radius ) { std::vector<city_reference> result; for( const auto om : get_overmaps_near( location, radius ) ) { const auto abs_pos_om = om_to_sm_copy( om->pos() ); result.reserve( result.size() + om->cities.size() ); std::transform( om->cities.begin(), om->cities.end(), std::back_inserter( result ), [&]( city & element ) { const auto rel_pos_city = omt_to_sm_copy( element.pos ); const auto abs_pos_city = tripoint( rel_pos_city + abs_pos_om, 0 ); const auto distance = rl_dist( abs_pos_city, location ); return city_reference{ &element, abs_pos_city, distance }; } ); } std::sort( result.begin(), result.end(), []( const city_reference & lhs, const city_reference & rhs ) { return lhs.get_distance_from_bounds() < rhs.get_distance_from_bounds(); } ); return result; }
bool Creature::sees( const tripoint &t, int &bresen1, int &bresen2 ) const { // TODO: FoV update bresen2 = 0; if( posz() != t.z ) { return false; } const int range_cur = sight_range( g->m.ambient_light_at(t) ); const int range_day = sight_range( DAYLIGHT_LEVEL ); const int range_min = std::min( range_cur, range_day ); const int wanted_range = rl_dist( pos3(), t ); if( wanted_range <= range_min || ( wanted_range <= range_day && g->m.ambient_light_at( t ) > g->natural_light_level() ) ) { if( g->m.ambient_light_at( t ) > g->natural_light_level() ) { return g->m.sees( pos3(), t, wanted_range, bresen1, bresen2 ); } else { return g->m.sees( pos3(), t, range_min, bresen1, bresen2 ); } } else { return false; } }
void event::actualize() { switch( type ) { case EVENT_HELP: debugmsg("Currently disabled while NPC and monster factions are being rewritten."); /* { int num = 1; if( faction_id >= 0 ) { num = rng( 1, 6 ); } for( int i = 0; i < num; i++ ) { npc *temp = new npc(); temp->normalize(); if( faction_id != -1 ) { faction *fac = g->faction_by_id( faction_id ); if( fac ) { temp->randomize_from_faction( fac ); } else { debugmsg( "EVENT_HELP run with invalid faction_id" ); temp->randomize(); } } else { temp->randomize(); } temp->attitude = NPCATT_DEFEND; // important: npc::spawn_at must be called to put the npc into the overmap temp->spawn_at( g->get_abs_levx(), g->get_abs_levy(), g->get_abs_levz() ); // spawn at the border of the reality bubble, outside of the players view if( one_in( 2 ) ) { temp->posx = rng( 0, SEEX * MAPSIZE - 1 ); temp->posy = rng( 0, 1 ) * SEEY * MAPSIZE; } else { temp->posx = rng( 0, 1 ) * SEEX * MAPSIZE; temp->posy = rng( 0, SEEY * MAPSIZE - 1 ); } // And tell the npc to go to the player. temp->goal.x = g->om_global_location().x; temp->goal.y = g->om_global_location().y; // The npcs will be loaded later by game::load_npcs() } } */ break; case EVENT_ROBOT_ATTACK: { if (rl_dist(g->get_abs_levx(), g->get_abs_levy(), map_point.x, map_point.y) <= 4) { mtype *robot_type = GetMType("mon_tripod"); if (faction_id == 0) { // The cops! if (one_in(2)) { robot_type = GetMType("mon_copbot"); } else { robot_type = GetMType("mon_riotbot"); } g->u.add_memorial_log(pgettext("memorial_male", "Became wanted by the police!"), pgettext("memorial_female", "Became wanted by the police!")); } monster robot(robot_type); int robx = (g->get_abs_levx() > map_point.x ? 0 - SEEX * 2 : SEEX * 4), roby = (g->get_abs_levy() > map_point.y ? 0 - SEEY * 2 : SEEY * 4); robot.spawn(robx, roby); g->add_zombie(robot); } } break; case EVENT_SPAWN_WYRMS: { if (g->levz >= 0) return; g->u.add_memorial_log(pgettext("memorial_male", "Awoke a group of dark wyrms!"), pgettext("memorial_female", "Awoke a group of dark wyrms!")); monster wyrm(GetMType("mon_dark_wyrm")); int num_wyrms = rng(1, 4); for (int i = 0; i < num_wyrms; i++) { int tries = 0; int monx = -1, mony = -1; do { monx = rng(0, SEEX * MAPSIZE); mony = rng(0, SEEY * MAPSIZE); tries++; } while (tries < 10 && !g->is_empty(monx, mony) && rl_dist(g->u.posx, g->u.posy, monx, mony) <= 2); if (tries < 10) { wyrm.spawn(monx, mony); g->add_zombie(wyrm); } } if (!one_in(25)) // They just keep coming! g->add_event(EVENT_SPAWN_WYRMS, int(calendar::turn) + rng(15, 25)); } break; case EVENT_AMIGARA: { g->u.add_memorial_log(pgettext("memorial_male", "Angered a group of amigara horrors!"), pgettext("memorial_female", "Angered a group of amigara horrors!")); int num_horrors = rng(3, 5); int faultx = -1, faulty = -1; bool horizontal = false; for (int x = 0; x < SEEX * MAPSIZE && faultx == -1; x++) { for (int y = 0; y < SEEY * MAPSIZE && faulty == -1; y++) { if (g->m.ter(x, y) == t_fault) { faultx = x; faulty = y; if (g->m.ter(x - 1, y) == t_fault || g->m.ter(x + 1, y) == t_fault) horizontal = true; else horizontal = false; } } } monster horror(GetMType("mon_amigara_horror")); for (int i = 0; i < num_horrors; i++) { int tries = 0; int monx = -1, mony = -1; do { if (horizontal) { monx = rng(faultx, faultx + 2 * SEEX - 8); for (int n = -1; n <= 1; n++) { if (g->m.ter(monx, faulty + n) == t_rock_floor) mony = faulty + n; } } else { // Vertical fault mony = rng(faulty, faulty + 2 * SEEY - 8); for (int n = -1; n <= 1; n++) { if (g->m.ter(faultx + n, mony) == t_rock_floor) monx = faultx + n; } } tries++; } while ((monx == -1 || mony == -1 || g->is_empty(monx, mony)) && tries < 10); if (tries < 10) { horror.spawn(monx, mony); g->add_zombie(horror); } } } break; case EVENT_ROOTS_DIE: g->u.add_memorial_log(pgettext("memorial_male", "Destroyed a triffid grove."), pgettext("memorial_female", "Destroyed a triffid grove.")); for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_root_wall && one_in(3)) g->m.ter_set(x, y, t_underbrush); } } break; case EVENT_TEMPLE_OPEN: { g->u.add_memorial_log(pgettext("memorial_male", "Opened a strange temple."), pgettext("memorial_female", "Opened a strange temple.")); bool saw_grate = false; for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_grate) { g->m.ter_set(x, y, t_stairs_down); if (!saw_grate && g->u_see(x, y)) saw_grate = true; } } } if (saw_grate) add_msg(_("The nearby grates open to reveal a staircase!")); } break; case EVENT_TEMPLE_FLOOD: { bool flooded = false; ter_id flood_buf[SEEX*MAPSIZE][SEEY*MAPSIZE]; for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) flood_buf[x][y] = g->m.ter(x, y); } for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_water_sh) { bool deepen = false; for (int wx = x - 1; wx <= x + 1 && !deepen; wx++) { for (int wy = y - 1; wy <= y + 1 && !deepen; wy++) { if (g->m.ter(wx, wy) == t_water_dp) deepen = true; } } if (deepen) { flood_buf[x][y] = t_water_dp; flooded = true; } } else if (g->m.ter(x, y) == t_rock_floor) { bool flood = false; for (int wx = x - 1; wx <= x + 1 && !flood; wx++) { for (int wy = y - 1; wy <= y + 1 && !flood; wy++) { if (g->m.ter(wx, wy) == t_water_dp || g->m.ter(wx, wy) == t_water_sh) flood = true; } } if (flood) { flood_buf[x][y] = t_water_sh; flooded = true; } } } } if (!flooded) return; // We finished flooding the entire chamber! // Check if we should print a message if (flood_buf[g->u.posx][g->u.posy] != g->m.ter(g->u.posx, g->u.posy)) { if (flood_buf[g->u.posx][g->u.posy] == t_water_sh) { add_msg(m_warning, _("Water quickly floods up to your knees.")); g->u.add_memorial_log(pgettext("memorial_male", "Water level reached knees."), pgettext("memorial_female", "Water level reached knees.")); } else { // Must be deep water! add_msg(m_warning, _("Water fills nearly to the ceiling!")); g->u.add_memorial_log(pgettext("memorial_male", "Water level reached the ceiling."), pgettext("memorial_female", "Water level reached the ceiling.")); g->plswim(g->u.posx, g->u.posy); } } // flood_buf is filled with correct tiles; now copy them back to g->m for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) g->m.ter_set(x, y, flood_buf[x][y]); } g->add_event(EVENT_TEMPLE_FLOOD, int(calendar::turn) + rng(2, 3)); } break; case EVENT_TEMPLE_SPAWN: { std::string montype = "mon_null"; switch (rng(1, 4)) { case 1: montype = "mon_sewer_snake"; break; case 2: montype = "mon_centipede"; break; case 3: montype = "mon_dermatik"; break; case 4: montype = "mon_spider_widow_giant"; break; } monster spawned( GetMType(montype) ); int tries = 0, x, y; do { x = rng(g->u.posx - 5, g->u.posx + 5); y = rng(g->u.posy - 5, g->u.posy + 5); tries++; } while (tries < 20 && !g->is_empty(x, y) && rl_dist(x, y, g->u.posx, g->u.posy) <= 2); if (tries < 20) { spawned.spawn(x, y); g->add_zombie(spawned); } } break; default: break; // Nothing happens for other events } }
bool leap_actor::call( monster &z ) const { if( !z.can_act() ) { return false; } std::vector<tripoint> options; tripoint target = z.move_target(); float best_float = trig_dist( z.pos(), target ); if( best_float < min_consider_range || best_float > max_consider_range ) { return false; } // We wanted the float for range check // int here will make the jumps more random int best = ( int )best_float; if( !allow_no_target && z.attack_target() == nullptr ) { return false; } for( const tripoint &dest : g->m.points_in_radius( z.pos(), max_range ) ) { if( dest == z.pos() ) { continue; } if( !z.sees( dest ) ) { continue; } if( !g->is_empty( dest ) ) { continue; } int cur_dist = rl_dist( target, dest ); if( cur_dist > best ) { continue; } if( trig_dist( z.pos(), dest ) < min_range ) { continue; } bool blocked_path = false; // check if monster has a clear path to the proposed point std::vector<tripoint> line = g->m.find_clear_path( z.pos(), dest ); for( auto &i : line ) { if( g->m.impassable( i ) ) { blocked_path = true; break; } } if( blocked_path ) { continue; } if( cur_dist < best ) { // Better than any earlier one options.clear(); } options.push_back( dest ); best = cur_dist; } if( options.empty() ) { return false; // Nowhere to leap! } z.moves -= move_cost; const tripoint chosen = random_entry( options ); bool seen = g->u.sees( z ); // We can see them jump... z.setpos( chosen ); seen |= g->u.sees( z ); // ... or we can see them land if( seen ) { add_msg( _( "The %s leaps!" ), z.name().c_str() ); } return true; }
void gun_actor::shoot( monster &z, Creature &target ) const { // Make sure our ammo isn't weird. if( z.ammo[ammo_type] > max_ammo ) { debugmsg( "Generated too much ammo (%d) of type %s for %s in gun_actor::shoot", z.ammo[ammo_type], ammo_type.c_str(), z.name().c_str() ); z.ammo[ammo_type] = max_ammo; } const bool require_targeting = ( require_targeting_player && target.is_player() ) || ( require_targeting_npc && target.is_npc() ) || ( require_targeting_monster && target.is_monster() ); const bool not_targeted = require_targeting && !z.has_effect( effect_targeted ); const bool not_laser_locked = require_targeting && laser_lock && !target.has_effect( effect_was_laserlocked ); if( not_targeted || not_laser_locked ) { if( !targeting_sound.empty() ) { sounds::sound( z.pos(), targeting_volume, _( targeting_sound.c_str() ) ); } if( not_targeted ) { z.add_effect( effect_targeted, targeting_timeout ); } if( not_laser_locked ) { target.add_effect( effect_laserlocked, 5 ); target.add_effect( effect_was_laserlocked, 5 ); target.add_msg_if_player( m_warning, _( "You're not sure why you've got a laser dot on you..." ) ); } z.moves -= targeting_cost; return; } // It takes a while z.moves -= move_cost; if( z.ammo[ammo_type] <= 0 && !no_ammo_sound.empty() ) { sounds::sound( z.pos(), 10, _( no_ammo_sound.c_str() ) ); return; } if( g->u.sees( z ) ) { add_msg( m_warning, _( description.c_str() ) ); } npc tmp; tmp.name = _( "The " ) + z.name(); tmp.set_fake( true ); tmp.recoil = 0; tmp.driving_recoil = 0; tmp.setpos( z.pos() ); tmp.str_max = fake_str; tmp.dex_max = fake_dex; tmp.int_max = fake_int; tmp.per_max = fake_per; tmp.str_cur = fake_str; tmp.dex_cur = fake_dex; tmp.int_cur = fake_int; tmp.per_cur = fake_per; tmp.weapon = item( gun_type, 0 ); tmp.weapon.set_curammo( ammo_type ); tmp.weapon.charges = z.ammo[ammo_type]; if( z.friendly != 0 ) { tmp.attitude = NPCATT_DEFEND; } else { tmp.attitude = NPCATT_KILL; } for( const auto &pr : fake_skills ) { tmp.skillLevel( pr.first ).level( pr.second ); } const auto distance = rl_dist( z.pos(), target.pos() ); int burst_size = std::min( burst_limit, tmp.weapon.burst_size() ); if( distance > range_no_burst || burst_size < 1 ) { burst_size = 1; } tmp.fire_gun( target.pos(), burst_size ); z.ammo[ammo_type] = tmp.weapon.charges; if( require_targeting ) { z.add_effect( effect_targeted, targeting_timeout_extend ); } if( laser_lock ) { // To prevent spamming laser locks when the player can tank that stuff somehow target.add_effect( effect_was_laserlocked, 5 ); } }
void monster::plan(const std::vector<int> &friendlies) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc = 0; int stc = 0; bool fleeing = false; if (friendly != 0) { // Target monsters, not the player! for (int i = 0, numz = g->num_zombies(); i < numz; i++) { monster *tmp = &(g->zombie(i)); if (tmp->friendly == 0) { int d = rl_dist(posx(), posy(), tmp->posx(), tmp->posy()); if (d < dist && g->m.sees(posx(), posy(), tmp->posx(), tmp->posy(), sightrange, tc)) { closest = i; dist = d; stc = tc; } } } if (has_effect("docile")) { closest = -1; } if (closest >= 0) { set_dest(g->zombie(closest).posx(), g->zombie(closest).posy(), stc); } else if (friendly > 0 && one_in(3)) { // Grow restless with no targets friendly--; } else if (friendly < 0 && sees_player( tc ) ) { if (rl_dist(posx(), posy(), g->u.posx, g->u.posy) > 2) { set_dest(g->u.posx, g->u.posy, tc); } else { plans.clear(); } } return; } // If we can see, and we can see a character, move toward them or flee. if (can_see() && sees_player( tc ) ) { dist = rl_dist(posx(), posy(), g->u.posx, g->u.posy); if (is_fleeing(g->u)) { // Wander away. fleeing = true; set_dest(posx() * 2 - g->u.posx, posy() * 2 - g->u.posy, tc); } else { // Chase the player. closest = -2; stc = tc; } } for (int i = 0; i < g->active_npc.size(); i++) { npc *me = (g->active_npc[i]); int medist = rl_dist(posx(), posy(), me->posx, me->posy); if ((medist < dist || (!fleeing && is_fleeing(*me))) && (can_see() && g->m.sees(posx(), posy(), me->posx, me->posy, sightrange, tc))) { if (is_fleeing(*me)) { fleeing = true; set_dest(posx() * 2 - me->posx, posy() * 2 - me->posy, tc); \ } else { closest = i; stc = tc; } dist = medist; } } if (!fleeing) { fleeing = attitude() == MATT_FLEE; if (can_see()) { for (int f = 0, numf = friendlies.size(); f < numf; f++) { const int i = friendlies[f]; monster *mon = &(g->zombie(i)); int mondist = rl_dist(posx(), posy(), mon->posx(), mon->posy()); if (mondist < dist && g->m.sees(posx(), posy(), mon->posx(), mon->posy(), sightrange, tc)) { dist = mondist; if (fleeing) { wandx = posx() * 2 - mon->posx(); wandy = posy() * 2 - mon->posy(); wandf = 40; } else { closest = -3 - i; stc = tc; } } } } if (closest == -2) { if (one_in(2)) {//random for the diversity of the trajectory ++stc; } else { --stc; } set_dest(g->u.posx, g->u.posy, stc); } else if (closest <= -3) set_dest(g->zombie(-3 - closest).posx(), g->zombie(-3 - closest).posy(), stc); else if (closest >= 0) set_dest(g->active_npc[closest]->posx, g->active_npc[closest]->posy, stc); } }
void monster::plan(game *g) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc, stc; bool fleeing = false; if (friendly != 0) { // Target monsters, not the player! for (int i = 0; i < g->z.size(); i++) { monster *tmp = &(g->z[i]); if (tmp->friendly == 0 && rl_dist(posx, posy, tmp->posx, tmp->posy) < dist && g->m.sees(posx, posy, tmp->posx, tmp->posy, sightrange, tc)) { closest = i; dist = rl_dist(posx, posy, tmp->posx, tmp->posy); stc = tc; } } if (has_effect(ME_DOCILE)) closest = -1; if (closest >= 0) set_dest(g->z[closest].posx, g->z[closest].posy, stc); else if (friendly > 0 && one_in(3)) // Grow restless with no targets friendly--; else if (friendly < 0 && g->sees_u(posx, posy, tc)) { if (rl_dist(posx, posy, g->u.posx, g->u.posy) > 2) set_dest(g->u.posx, g->u.posy, tc); else plans.clear(); } return; } if (is_fleeing(g->u) && can_see() && g->sees_u(posx, posy, tc)) { fleeing = true; wandx = posx * 2 - g->u.posx; wandy = posy * 2 - g->u.posy; wandf = 40; dist = rl_dist(posx, posy, g->u.posx, g->u.posy); } // If we can see, and we can see a character, start moving towards them if (!is_fleeing(g->u) && can_see() && g->sees_u(posx, posy, tc)) { dist = rl_dist(posx, posy, g->u.posx, g->u.posy); closest = -2; stc = tc; } for (int i = 0; i < g->active_npc.size(); i++) { npc *me = (g->active_npc[i]); int medist = rl_dist(posx, posy, me->posx, me->posy); if ((medist < dist || (!fleeing && is_fleeing(*me))) && (can_see() && g->m.sees(posx, posy, me->posx, me->posy, sightrange, tc))) { if (is_fleeing(*me)) { fleeing = true; wandx = posx * 2 - me->posx; wandy = posy * 2 - me->posy; wandf = 40; dist = medist; } else if (can_see() && g->m.sees(posx, posy, me->posx, me->posy, sightrange, tc)) { dist = rl_dist(posx, posy, me->posx, me->posy); closest = i; stc = tc; } } } if (!fleeing) { fleeing = attitude() == MATT_FLEE; for (int i = 0; i < g->z.size(); i++) { monster *mon = &(g->z[i]); int mondist = rl_dist(posx, posy, mon->posx, mon->posy); if (mon->friendly != 0 && mondist < dist && can_see() && g->m.sees(posx, posy, mon->posx, mon->posy, sightrange, tc)) { dist = mondist; if (fleeing) { wandx = posx * 2 - mon->posx; wandy = posy * 2 - mon->posy; wandf = 40; } else { closest = -3 - i; stc = tc; } } } } if (!fleeing) { if (closest == -2) set_dest(g->u.posx, g->u.posy, stc); else if (closest <= -3) set_dest(g->z[-3 - closest].posx, g->z[-3 - closest].posy, stc); else if (closest >= 0) set_dest(g->active_npc[closest]->posx, g->active_npc[closest]->posy, stc); } }
bool Creature::compare_by_dist_to_point::operator()( const Creature* const a, const Creature* const b ) const { return rl_dist( a->pos(), center ) < rl_dist( b->pos(), center ); }
void sounds::process_sound_markers( player *p ) { bool is_deaf = p->is_deaf(); const float volume_multiplier = p->hearing_ability(); const int safe_volume = p->worn_with_flag("PARTIAL_DEAF") ? 100 : 9999; const int weather_vol = weather_data( g->weather ).sound_attn; for( const auto &sound_event_pair : sounds_since_last_turn ) { const int volume = std::min(safe_volume, (int)(sound_event_pair.second.volume * volume_multiplier)); const std::string& sfx_id = sound_event_pair.second.id; const std::string& sfx_variant = sound_event_pair.second.variant; const int max_volume = std::max( volume, sound_event_pair.second.volume ); // For deafness checks int dist = rl_dist( p->pos(), sound_event_pair.first ); bool ambient = sound_event_pair.second.ambient; // Too far away, we didn't hear it! if( dist > volume ) { continue; } if( is_deaf ) { // Has to be here as well to work for stacking deafness (loud noises prolong deafness) if( !p->is_immune_effect( effect_deaf ) && rng( ( max_volume - dist ) / 2, ( max_volume - dist ) ) >= 150 ) { // Prolong deafness, but not as much as if it was freshly applied int duration = std::min( 40, ( max_volume - dist - 130 ) / 8 ); p->add_effect( effect_deaf, duration ); if( !p->has_trait( "DEADENED" ) ) { p->add_msg_if_player( m_bad, _( "Your eardrums suddenly ache!" ) ); if( p->get_pain() < 10 ) { p->mod_pain( rng( 0, 2 ) ); } } } // We're deaf, skip rest of processing. continue; } // Player volume meter includes all sounds from their tile and adjacent tiles // TODO: Add noises from vehicle player is in. if( dist <= 1 ) { p->volume = std::max( p->volume, volume ); } // Check for deafness if( !p->is_immune_effect( effect_deaf ) && rng((max_volume - dist) / 2, (max_volume - dist)) >= 150 ) { int duration = (max_volume - dist - 130) / 4; p->add_effect( effect_deaf, duration ); if( p->is_deaf() ) { // Need to check for actual deafness is_deaf = true; sfx::do_hearing_loss( duration ); continue; } } // At this point we are dealing with attention (as opposed to physical effects) // so reduce volume by the amount of ambient noise from the weather. const int mod_vol = ( sound_event_pair.second.volume - weather_vol ) * volume_multiplier; // The noise was drowned out by the surroundings. if( mod_vol - dist < 0 ) { continue; } // See if we need to wake someone up if( p->has_effect( effect_sleep ) ) { if( ( !( p->has_trait( "HEAVYSLEEPER" ) || p->has_trait( "HEAVYSLEEPER2" ) ) && dice( 2, 15 ) < mod_vol - dist ) || ( p->has_trait( "HEAVYSLEEPER" ) && dice( 3, 15 ) < mod_vol - dist ) || ( p->has_trait( "HEAVYSLEEPER2" ) && dice( 6, 15 ) < mod_vol - dist ) ) { //Not kidding about sleep-thru-firefight p->wake_up(); add_msg( m_warning, _( "Something is making noise." ) ); } else { continue; } } const tripoint &pos = sound_event_pair.first; const std::string &description = sound_event_pair.second.description; if( !ambient && ( pos != p->pos() ) && !g->m.pl_sees( pos, dist ) ) { if( p->activity.ignore_trivial != true ) { std::string query; if( description.empty() ) { query = _( "Heard a noise!" ); } else { query = string_format( _( "Heard %s!" ), sound_event_pair.second.description.c_str() ); } if( g->cancel_activity_or_ignore_query( query.c_str() ) ) { p->activity.ignore_trivial = true; for( auto activity : p->backlog ) { activity.ignore_trivial = true; } } } } // Only print a description if it exists if( !description.empty() ) { // If it came from us, don't print a direction if( pos == p->pos() ) { add_msg( _( "You hear %s" ), description.c_str() ); } else { // Else print a direction as well std::string direction = direction_name( direction_from( p->pos(), pos ) ); add_msg( m_warning, _( "From the %s you hear %s" ), direction.c_str(), description.c_str() ); } } // Play the sound effect, if any. if( !sfx_id.empty() ) { // for our sfx API, 100 is "normal" volume, so scale accordingly int heard_volume = sfx::get_heard_volume( pos ); sfx::play_variant_sound( sfx_id, sfx_variant, heard_volume ); //add_msg("Playing sound effect %s, %s, %d", sfx_id.c_str(), sfx_variant.c_str(), heard_volume); } // If Z coord is different, draw even when you can see the source const bool diff_z = pos.z != p->posz(); // Place footstep markers. if( pos == p->pos() || p->sees( pos ) ) { // If we are or can see the source, don't draw a marker. continue; } int err_offset; if( mod_vol / dist < 2 ) { err_offset = 3; } else if( mod_vol / dist < 3 ) { err_offset = 2; } else { err_offset = 1; } // Enumerate the valid points the player *cannot* see. // Unless the source is on a different z-level, then any point is fine std::vector<tripoint> unseen_points; tripoint newp = pos; int &newx = newp.x; int &newy = newp.y; for( newx = pos.x - err_offset; newx <= pos.x + err_offset; newx++ ) { for( newy = pos.y - err_offset; newy <= pos.y + err_offset; newy++ ) { if( diff_z || !p->sees( newp ) ) { unseen_points.emplace_back( newp ); } } } // Then place the sound marker in a random one. if( !unseen_points.empty() ) { sound_markers.emplace( random_entry( unseen_points ), sound_event_pair.second ); } } sounds_since_last_turn.clear(); }
void map::generate_lightmap() { memset(lm, 0, sizeof(lm)); memset(sm, 0, sizeof(sm)); /* Bulk light sources wastefully cast rays into neighbors; a burning hospital can produce significant slowdown, so for stuff like fire and lava: * Step 1: Store the position and luminance in buffer via add_light_source, for efficient checking of neighbors. * Step 2: After everything else, iterate buffer and apply_light_source only in non-redundant directions * Step 3: Profit! */ memset(light_source_buffer, 0, sizeof(light_source_buffer)); constexpr int dir_x[] = { 0, -1 , 1, 0 }; // [0] constexpr int dir_y[] = { -1, 0 , 0, 1 }; // [1][X][2] constexpr int dir_d[] = { 180, 270, 0, 90 }; // [3] const bool u_is_inside = !is_outside(g->u.posx(), g->u.posy()); const float natural_light = g->natural_light_level(); const float hl = natural_light / 2; if (natural_light > LIGHT_SOURCE_BRIGHT) { // Apply sunlight, first light source so just assign for (int sx = DAYLIGHT_LEVEL - hl; sx < LIGHTMAP_CACHE_X - hl; ++sx) { for (int sy = DAYLIGHT_LEVEL - hl; sy < LIGHTMAP_CACHE_Y - hl; ++sy) { // In bright light indoor light exists to some degree if (!is_outside(sx, sy)) { lm[sx][sy] = LIGHT_AMBIENT_LOW; } else if (g->u.posx() == sx && g->u.posy() == sy ) { //Only apply daylight on square where player is standing to avoid flooding // the lightmap when in less than total sunlight. lm[sx][sy] = natural_light; } } } } apply_character_light( g->u ); for( auto &n : g->active_npc ) { apply_character_light( *n ); } // LIGHTMAP_CACHE_X = MAPSIZE * SEEX // LIGHTMAP_CACHE_Y = MAPSIZE * SEEY // Traverse the submaps in order for (int smx = 0; smx < my_MAPSIZE; ++smx) { for (int smy = 0; smy < my_MAPSIZE; ++smy) { auto const cur_submap = get_submap_at_grid( smx, smy ); for (int sx = 0; sx < SEEX; ++sx) { for (int sy = 0; sy < SEEY; ++sy) { const int x = sx + smx * SEEX; const int y = sy + smy * SEEY; // When underground natural_light is 0, if this changes we need to revisit // Only apply this whole thing if the player is inside, // buildings will be shadowed when outside looking in. if (natural_light > LIGHT_SOURCE_BRIGHT && u_is_inside && !is_outside(x, y)) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if (INBOUNDS(x + dir_x[i], y + dir_y[i]) && is_outside(x + dir_x[i], y + dir_y[i])) { lm[x][y] = natural_light; if (light_transparency(x, y) > LIGHT_TRANSPARENCY_SOLID) { apply_light_arc(x, y, dir_d[i], natural_light); } } } } if (cur_submap->lum[sx][sy]) { auto items = i_at(x, y); add_light_from_items(x, y, items.begin(), items.end()); } const ter_id terrain = cur_submap->ter[sx][sy]; if (terrain == t_lava) { add_light_source(x, y, 50 ); } else if (terrain == t_console) { add_light_source(x, y, 3 ); } else if (terrain == t_utility_light) { add_light_source(x, y, 35 ); } for( auto &fld : cur_submap->fld[sx][sy] ) { const field_entry *cur = &fld.second; // TODO: [lightmap] Attach light brightness to fields switch(cur->getFieldType()) { case fd_fire: if (3 == cur->getFieldDensity()) { add_light_source(x, y, 160); } else if (2 == cur->getFieldDensity()) { add_light_source(x, y, 60); } else { add_light_source(x, y, 16); } break; case fd_fire_vent: case fd_flame_burst: add_light_source(x, y, 8); break; case fd_electricity: case fd_plasma: if (3 == cur->getFieldDensity()) { add_light_source(x, y, 8); } else if (2 == cur->getFieldDensity()) { add_light_source(x, y, 1); } else { apply_light_source(x, y, LIGHT_SOURCE_LOCAL, trigdist); // kinda a hack as the square will still get marked } break; case fd_incendiary: if (3 == cur->getFieldDensity()) { add_light_source(x, y, 30); } else if (2 == cur->getFieldDensity()) { add_light_source(x, y, 16); } else { add_light_source(x, y, 8); } break; case fd_laser: apply_light_source(x, y, 1, trigdist); break; case fd_spotlight: add_light_source(x, y, 20); break; case fd_dazzling: add_light_source(x, y, 2); break; default: //Suppress warnings break; } } } } } } for (size_t i = 0; i < g->num_zombies(); ++i) { auto &critter = g->zombie(i); if(critter.is_hallucination()) { continue; } int mx = critter.posx(); int my = critter.posy(); if (INBOUNDS(mx, my)) { if (critter.has_effect("onfire")) { apply_light_source(mx, my, 3, trigdist); } // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights if (critter.type->luminance > 0) { apply_light_source(mx, my, critter.type->luminance, trigdist); } } } // Apply any vehicle light sources VehicleList vehs = get_vehicles(); for( auto &vv : vehs ) { vehicle *v = vv.v; if(v->lights_on) { int dir = v->face.dir(); float veh_luminance = 0.0; float iteration = 1.0; std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_CONE_LIGHT); for( auto &light_indice : light_indices ) { veh_luminance += ( v->part_info( light_indice ).bonus / iteration ); iteration = iteration * 1.1; } if (veh_luminance > LL_LIT) { for( auto &light_indice : light_indices ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source(px, py, SQRT_2); // Add a little surrounding light apply_light_arc( px, py, dir + v->parts[light_indice].direction, veh_luminance, 45 ); } } } } if(v->overhead_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_CIRCLE_LIGHT); for( auto &light_indice : light_indices ) { if( ( calendar::turn % 2 && v->part_info( light_indice ).has_flag( VPFLAG_ODDTURN ) ) || ( !( calendar::turn % 2 ) && v->part_info( light_indice ).has_flag( VPFLAG_EVENTURN ) ) || ( !v->part_info( light_indice ).has_flag( VPFLAG_EVENTURN ) && !v->part_info( light_indice ).has_flag( VPFLAG_ODDTURN ) ) ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source( px, py, v->part_info( light_indice ).bonus ); } } } } // why reinvent the [lightmap] wheel if(v->dome_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_DOME_LIGHT); for( auto &light_indice : light_indices ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source( px, py, v->part_info( light_indice ).bonus ); } } } if(v->aisle_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_AISLE_LIGHT); for( auto &light_indice : light_indices ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source( px, py, v->part_info( light_indice ).bonus ); } } } if(v->has_atomic_lights) { // atomic light is always on std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_ATOMIC_LIGHT); for( auto &light_indice : light_indices ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source( px, py, v->part_info( light_indice ).bonus ); } } } for( size_t p = 0; p < v->parts.size(); ++p ) { int px = vv.x + v->parts[p].precalc[0].x; int py = vv.y + v->parts[p].precalc[0].y; if( !INBOUNDS( px, py ) ) { continue; } if( v->part_flag( p, VPFLAG_CARGO ) && !v->part_flag( p, "COVERED" ) ) { add_light_from_items( px, py, v->get_items(p).begin(), v->get_items(p).end() ); } } } /* Now that we have position and intensity of all bulk light sources, apply_ them This may seem like extra work, but take a 12x12 raging inferno: unbuffered: (12^2)*(160*4) = apply_light_ray x 92160 buffered: (12*4)*(160) = apply_light_ray x 7680 */ for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { if ( light_source_buffer[sx][sy] > 0. ) { apply_light_source(sx, sy, light_source_buffer[sx][sy], ( trigdist && light_source_buffer[sx][sy] > 3. ) ); } } } if (g->u.has_active_bionic("bio_night") ) { for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { if (rl_dist(sx, sy, g->u.posx(), g->u.posy()) < 15) { lm[sx][sy] = 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; }
bool mission::is_complete( const int _npc_id ) const { if( status == mission_status::success ) { return true; } auto &u = g->u; switch( type->goal ) { case MGOAL_GO_TO: { const tripoint cur_pos = g->u.global_omt_location(); return ( rl_dist( cur_pos, target ) <= 1 ); } break; case MGOAL_GO_TO_TYPE: { const auto cur_ter = overmap_buffer.ter( g->u.global_omt_location() ); return cur_ter == type->target_id; } break; case MGOAL_FIND_ITEM: { inventory tmp_inv = u.crafting_inventory(); // TODO: check for count_by_charges and use appropriate player::has_* function if (!tmp_inv.has_amount(type->item_id, item_count)) { return tmp_inv.has_amount( type->item_id, 1 ) && tmp_inv.has_charges( type->item_id, item_count ); } if( npc_id != -1 && npc_id != _npc_id ) { return false; } } return true; case MGOAL_FIND_ANY_ITEM: return u.has_mission_item( uid ) && ( npc_id == -1 || npc_id == _npc_id ); case MGOAL_FIND_MONSTER: if( npc_id != -1 && npc_id != _npc_id ) { return false; } for( size_t i = 0; i < g->num_zombies(); i++ ) { if( g->zombie( i ).mission_id == uid ) { return true; } } return false; case MGOAL_RECRUIT_NPC: { npc *p = g->find_npc( target_npc_id ); return p != nullptr && p->attitude == NPCATT_FOLLOW; } case MGOAL_RECRUIT_NPC_CLASS: { const auto npcs = overmap_buffer.get_npcs_near_player( 100 ); for( auto & npc : npcs ) { if( npc->myclass == recruit_class && npc->attitude == NPCATT_FOLLOW ) { return true; } } return false; } case MGOAL_FIND_NPC: return npc_id == _npc_id; case MGOAL_ASSASSINATE: return step >= 1; case MGOAL_KILL_MONSTER: return step >= 1; case MGOAL_KILL_MONSTER_TYPE: return g->kill_count( mtype_id( monster_type ) ) >= monster_kill_goal; case MGOAL_COMPUTER_TOGGLE: return step >= 1; default: return false; } return false; }
void map::generate_lightmap() { memset(lm, 0, sizeof(lm)); memset(sm, 0, sizeof(sm)); /* Bulk light sources wastefully cast rays into neighbors; a burning hospital can produce significant slowdown, so for stuff like fire and lava: * Step 1: Store the position and luminance in buffer via add_light_source, for efficient checking of neighbors. * Step 2: After everything else, iterate buffer and apply_light_source only in non-redundant directions * Step 3: Profit! */ memset(light_source_buffer, 0, sizeof(light_source_buffer)); const int dir_x[] = { 1, 0 , -1, 0 }; const int dir_y[] = { 0, 1 , 0, -1 }; const int dir_d[] = { 180, 270, 0, 90 }; const float held_luminance = g->u.active_light(); const float natural_light = g->natural_light_level(); if (natural_light > LIGHT_SOURCE_BRIGHT) { // Apply sunlight, first light source so just assign for(int sx = DAYLIGHT_LEVEL - (natural_light / 2); sx < LIGHTMAP_CACHE_X - (natural_light / 2); ++sx) { for(int sy = DAYLIGHT_LEVEL - (natural_light / 2); sy < LIGHTMAP_CACHE_Y - (natural_light / 2); ++sy) { // In bright light indoor light exists to some degree if (!is_outside(sx, sy)) { lm[sx][sy] = LIGHT_AMBIENT_LOW; } else if (g->u.posx == sx && g->u.posy == sy ) { //Only apply daylight on square where player is standing to avoid flooding // the lightmap when in less than total sunlight. lm[sx][sy] = natural_light; } } } } // Apply player light sources if (held_luminance > LIGHT_AMBIENT_LOW) { apply_light_source(g->u.posx, g->u.posy, held_luminance, trigdist); } for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { const ter_id terrain = ter(sx, sy); const std::vector<item> &items = i_at(sx, sy); field ¤t_field = field_at(sx, sy); // When underground natural_light is 0, if this changes we need to revisit // Only apply this whole thing if the player is inside, // buildings will be shadowed when outside looking in. if (natural_light > LIGHT_AMBIENT_LOW && !is_outside(g->u.posx, g->u.posy) ) { if (!is_outside(sx, sy)) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if (INBOUNDS(sx + dir_x[i], sy + dir_y[i]) && is_outside(sx + dir_x[i], sy + dir_y[i])) { lm[sx][sy] = natural_light; if (light_transparency(sx, sy) > LIGHT_TRANSPARENCY_SOLID) { apply_light_arc(sx, sy, dir_d[i], natural_light); } } } } } for( std::vector<item>::const_iterator itm = items.begin(); itm != items.end(); ++itm ) { float ilum = 0.0; // brightness int iwidth = 0; // 0-360 degrees. 0 is a circular light_source int idir = 0; // otherwise, it's a light_arc pointed in this direction if ( itm->getlight(ilum, iwidth, idir ) ) { if ( iwidth > 0 ) { apply_light_arc( sx, sy, idir, ilum, iwidth ); } else { add_light_source(sx, sy, ilum); } } } if(terrain == t_lava) { add_light_source(sx, sy, 50 ); } if(terrain == t_console) { add_light_source(sx, sy, 3 ); } if(terrain == t_emergency_light) { add_light_source(sx, sy, 3 ); } if(terrain == t_utility_light) { add_light_source(sx, sy, 35 ); } field_entry *cur = NULL; for(std::map<field_id, field_entry *>::iterator field_list_it = current_field.getFieldStart(); field_list_it != current_field.getFieldEnd(); ++field_list_it) { cur = field_list_it->second; if(cur == NULL) { continue; } // TODO: [lightmap] Attach light brightness to fields switch(cur->getFieldType()) { case fd_fire: if (3 == cur->getFieldDensity()) { add_light_source(sx, sy, 160); } else if (2 == cur->getFieldDensity()) { add_light_source(sx, sy, 60); } else { add_light_source(sx, sy, 16); } break; case fd_fire_vent: case fd_flame_burst: add_light_source(sx, sy, 8); break; case fd_electricity: case fd_plasma: if (3 == cur->getFieldDensity()) { add_light_source(sx, sy, 8); } else if (2 == cur->getFieldDensity()) { add_light_source(sx, sy, 1); } else { apply_light_source(sx, sy, LIGHT_SOURCE_LOCAL, trigdist); // kinda a hack as the square will still get marked } break; case fd_incendiary: if (3 == cur->getFieldDensity()) { add_light_source(sx, sy, 30); } else if (2 == cur->getFieldDensity()) { add_light_source(sx, sy, 16); } else { add_light_source(sx, sy, 8); } break; case fd_laser: apply_light_source(sx, sy, 1, trigdist); break; case fd_spotlight: add_light_source(sx, sy, 20); break; case fd_dazzling: add_light_source(sx, sy, 2); break; default: //Suppress warnings break; } } } } for (size_t i = 0; i < g->num_zombies(); ++i) { int mx = g->zombie(i).posx(); int my = g->zombie(i).posy(); if (INBOUNDS(mx, my)) { if (g->zombie(i).has_effect("onfire")) { apply_light_source(mx, my, 3, trigdist); } // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights if (g->zombie(i).type->luminance > 0) { apply_light_source(mx, my, g->zombie(i).type->luminance, trigdist); } } } // Apply any vehicle light sources VehicleList vehs = get_vehicles(); for( size_t v = 0; v < vehs.size(); ++v ) { if(vehs[v].v->lights_on) { int dir = vehs[v].v->face.dir(); float veh_luminance = 0.0; float iteration = 1.0; std::vector<int> light_indices = vehs[v].v->all_parts_with_feature(VPFLAG_CONE_LIGHT); for (std::vector<int>::iterator part = light_indices.begin(); part != light_indices.end(); ++part) { veh_luminance += ( vehs[v].v->part_info(*part).bonus / iteration ); iteration = iteration * 1.1; } if (veh_luminance > LL_LIT) { for (std::vector<int>::iterator part = light_indices.begin(); part != light_indices.end(); ++part) { int px = vehs[v].x + vehs[v].v->parts[*part].precalc_dx[0]; int py = vehs[v].y + vehs[v].v->parts[*part].precalc_dy[0]; if(INBOUNDS(px, py)) { apply_light_arc(px, py, dir + vehs[v].v->parts[*part].direction, veh_luminance, 45); } } } } if(vehs[v].v->overhead_lights_on) { std::vector<int> light_indices = vehs[v].v->all_parts_with_feature(VPFLAG_CIRCLE_LIGHT); for (std::vector<int>::iterator part = light_indices.begin(); part != light_indices.end(); ++part) { if((calendar::turn % 2 && vehs[v].v->part_info(*part).has_flag(VPFLAG_ODDTURN)) || (!(calendar::turn % 2) && vehs[v].v->part_info(*part).has_flag(VPFLAG_EVENTURN)) || (!vehs[v].v->part_info(*part).has_flag(VPFLAG_EVENTURN) && !vehs[v].v->part_info(*part).has_flag(VPFLAG_ODDTURN))) { int px = vehs[v].x + vehs[v].v->parts[*part].precalc_dx[0]; int py = vehs[v].y + vehs[v].v->parts[*part].precalc_dy[0]; if(INBOUNDS(px, py)) { add_light_source( px, py, vehs[v].v->part_info(*part).bonus ); } } } } } /* Now that we have position and intensity of all bulk light sources, apply_ them This may seem like extra work, but take a 12x12 raging inferno: unbuffered: (12^2)*(160*4) = apply_light_ray x 92160 buffered: (12*4)*(160) = apply_light_ray x 7680 */ for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { if ( light_source_buffer[sx][sy] > 0. ) { apply_light_source(sx, sy, light_source_buffer[sx][sy], ( trigdist && light_source_buffer[sx][sy] > 3. ) ); } } } if (g->u.has_active_bionic("bio_night") ) { for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { if (rl_dist(sx, sy, g->u.posx, g->u.posy) < 15) { lm[sx][sy] = 0; } } } } }
std::vector<tripoint> map::route( const tripoint &f, const tripoint &t, const pathfinding_settings &settings, const std::set<tripoint> &pre_closed ) 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. */ std::vector<tripoint> ret; if( f == t || !inbounds( f ) ) { return ret; } if( !inbounds( t ) ) { tripoint clipped = t; clip_to_bounds( clipped ); return route( f, clipped, settings, pre_closed ); } // First, check for a simple straight line on flat ground // Except when the line contains a pre-closed tile - we need to do regular pathing then static const auto non_normal = PF_SLOW | PF_WALL | PF_VEHICLE | PF_TRAP; if( f.z == t.z ) { const auto line_path = line_to( f, t ); const auto &pf_cache = get_pathfinding_cache_ref( f.z ); // Check all points for any special case (including just hard terrain) if( std::all_of( line_path.begin(), line_path.end(), [&pf_cache]( const tripoint & p ) { return !( pf_cache.special[p.x][p.y] & non_normal ); } ) ) { const std::set<tripoint> sorted_line( line_path.begin(), line_path.end() ); if( is_disjoint( sorted_line, pre_closed ) ) { return line_path; } } } // If expected path length is greater than max distance, allow only line path, like above if( rl_dist( f, t ) > settings.max_dist ) { return ret; } int max_length = settings.max_length; int bash = settings.bash_strength; int climb_cost = settings.climb_cost; bool doors = settings.allow_open_doors; bool trapavoid = settings.avoid_traps; const int pad = 16; // 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 ); // Make NPCs not want to path through player // But don't make player pathing stop working for( const auto &p : pre_closed ) { if( p.x >= minx && p.x < maxx && p.y >= miny && p.y < maxy ) { pf.close_point( p ); } } // Start and end must not be closed pf.unclose_point( f ); pf.unclose_point( t ); pf.add_point( 0, 0, f, f ); 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] > max_length ) { // Shortest path would be too long, return empty vector return std::vector<tripoint>(); } if( cur == t ) { done = true; break; } cur_state = ASL_CLOSED; const auto &pf_cache = get_pathfinding_cache_ref( cur.z ); const auto cur_special = pf_cache.special[cur.x][cur.y]; // 7 3 5 // 1 . 2 // 6 4 8 constexpr std::array<int, 8> x_offset{{ -1, 1, 0, 0, 1, -1, -1, 1 }}; constexpr std::array<int, 8> y_offset{{ 0, 0, -1, 1, -1, 1, -1, 1 }}; for( size_t i = 0; i < 8; i++ ) { const tripoint p( cur.x + x_offset[i], cur.y + y_offset[i], cur.z ); 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; } // Penalize for diagonals or the path will look "unnatural" int newg = layer.gscore[parent_index] + ( ( cur.x != p.x && cur.y != p.y ) ? 1 : 0 ); const auto p_special = pf_cache.special[p.x][p.y]; // @todo: De-uglify, de-huge-n if( !( p_special & non_normal ) ) { // Boring flat dirt - the most common case above the ground newg += 2; } else { 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 && ( !doors || !terrain.open ) && veh == nullptr && climb_cost <= 0 ) { layer.state[index] = ASL_CLOSED; // Close it so that next time we won't try to calculate costs continue; } newg += cost; if( cost == 0 ) { if( climb_cost > 0 && p_special & PF_CLIMBABLE ) { // Climbing fences newg += climb_cost; } else if( doors && terrain.open && ( !terrain.has_flag( "OPENCLOSE_INSIDE" ) || !is_outside( cur ) ) ) { // Only try to open INSIDE doors from the inside // To open and then move onto the tile newg += 4; } else if( veh != nullptr ) { part = veh->obstacle_at_part( part ); int dummy = -1; if( doors && veh->part_flag( part, VPFLAG_OPENABLE ) && ( !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 if( part >= 0 && bash > 0 ) { // Car obstacle that isn't a door // @todo: Account for armor int hp = veh->parts[part].hp(); if( hp / 20 > bash ) { // Threshold damage thing means we just can't bash this down layer.state[index] = ASL_CLOSED; continue; } else if( hp / 10 > bash ) { // Threshold damage thing means we will fail to deal damage pretty often hp *= 2; } newg += 2 * hp / bash + 8 + 4; } else if( part >= 0 ) { if( !doors || !veh->part_flag( part, VPFLAG_OPENABLE ) ) { // Won't be openable, don't try from other sides layer.state[index] = ASL_CLOSED; } continue; } } else if( rating > 1 ) { // Expected number of turns to bash it down, 1 turn to move there // and 5 turns of penalty not to trash everything just because we can newg += ( 20 / rating ) + 2 + 10; } else if( rating == 1 ) { // Desperate measures, avoid whenever possible newg += 500; } else { // Unbashable and unopenable from here if( !doors || !terrain.open ) { // Or anywhere else for that matter layer.state[index] = ASL_CLOSED; } continue; } } if( trapavoid && p_special & PF_TRAP ) { const auto &ter_trp = terrain.trap.obj(); const auto &trp = ter_trp.is_benign() ? tile.get_trap_t() : ter_trp; if( !trp.is_benign() ) { // For now make them detect all traps if( has_zlevels() && terrain.has_flag( TFLAG_NO_FLOOR ) ) { // Special case - ledge in z-levels // Warning: really expensive, needs a cache if( valid_move( p, tripoint( p.x, p.y, p.z - 1 ), false, true ) ) { tripoint below( p.x, p.y, p.z - 1 ); if( !has_flag( TFLAG_NO_FLOOR, below ) ) { // Otherwise this would have been a huge fall auto &layer = pf.get_layer( p.z - 1 ); // From cur, not p, because we won't be walking on air pf.add_point( layer.gscore[parent_index] + 10, layer.score[parent_index] + 10 + 2 * rl_dist( below, t ), cur, below ); } // Close p, because we won't be walking on it layer.state[index] = ASL_CLOSED; continue; } } else if( trapavoid ) { // Otherwise it's walkable newg += 500; } } } } // 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() || !( cur_special & PF_UPDOWN ) || !settings.allow_climb_stairs ) { // 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( settings.allow_climb_stairs && 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( settings.allow_climb_stairs && 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 ); } } if( cur.z < maxz && parent_terrain.has_flag( TFLAG_RAMP ) && valid_move( cur, tripoint( cur.x, cur.y, cur.z + 1 ), false, true ) ) { auto &layer = pf.get_layer( cur.z + 1 ); for( size_t it = 0; it < 8; it++ ) { const tripoint above( cur.x + x_offset[it], cur.y + y_offset[it], cur.z + 1 ); pf.add_point( layer.gscore[parent_index] + 4, layer.score[parent_index] + 4 + 2 * rl_dist( above, t ), cur, above ); } } } while( !done && !pf.empty() ); if( done ) { ret.reserve( rl_dist( f, t ) * 2 ); tripoint cur = t; // Just to limit max distance, in case something weird happens for( int fdist = max_length; 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; }
void mdeath::guilt(monster *z) { const int MAX_GUILT_DISTANCE = 5; int kill_count = g->kill_count(z->type->id); int maxKills = 100; // this is when the player stop caring altogether. // different message as we kill more of the same monster std::string msg = _("You feel guilty for killing %s."); // default guilt message game_message_type msgtype = m_bad; // default guilt message type std::map<int, std::string> guilt_tresholds; guilt_tresholds[75] = _("You feel ashamed for killing %s."); guilt_tresholds[50] = _("You regret killing %s."); guilt_tresholds[25] = _("You feel remorse for killing %s."); if (g->u.has_trait("PSYCHOPATH") || g->u.has_trait("PRED3") || g->u.has_trait("PRED4") ) { return; } if (rl_dist(z->posx(), z->posy(), g->u.posx, g->u.posy) > MAX_GUILT_DISTANCE) { // Too far away, we can deal with it. return; } if (z->hp >= 0) { // We probably didn't kill it return; } if (kill_count >= maxKills) { // player no longer cares if (kill_count == maxKills) { //~ Message after killing a lot of monsters which would normally affect the morale negatively. %s is the monster name, it will be pluralized with a number of 100. add_msg(m_good, _("After killing so many bloody %s you no longer care " "about their deaths anymore."), z->name(maxKills).c_str()); } return; } else if ((g->u.has_trait("PRED1")) || (g->u.has_trait("PRED2"))) { msg = (_("Culling the weak is distasteful, but necessary.")); msgtype = m_neutral; } else { msgtype = m_bad; for (std::map<int, std::string>::iterator it = guilt_tresholds.begin(); it != guilt_tresholds.end(); it++) { if (kill_count >= it->first) { msg = it->second; break; } } } add_msg(msgtype, msg.c_str(), z->name().c_str()); int moraleMalus = -50 * (1.0 - ((float) kill_count / maxKills)); int maxMalus = -250 * (1.0 - ((float) kill_count / maxKills)); int duration = 300 * (1.0 - ((float) kill_count / maxKills)); int decayDelay = 30 * (1.0 - ((float) kill_count / maxKills)); if (z->type->in_species("ZOMBIE")) { moraleMalus /= 10; if (g->u.has_trait("PACIFIST")) { moraleMalus *= 5; } else if (g->u.has_trait("PRED1")) { moraleMalus /= 4; } else if (g->u.has_trait("PRED2")) { moraleMalus /= 5; } } g->u.add_morale(MORALE_KILLED_MONSTER, moraleMalus, maxMalus, duration, decayDelay); }
int monster::trigger_sum(std::set<monster_trigger> *triggers) { int ret = 0; bool check_terrain = false, check_meat = false, check_fire = false; for (std::set<monster_trigger>::iterator trig = triggers->begin(); trig != triggers->end(); ++trig) { switch (*trig){ case MTRIG_STALK: if (anger > 0 && one_in(20)) ret++; break; case MTRIG_MEAT: check_terrain = true; check_meat = true; break; case MTRIG_PLAYER_CLOSE: if (rl_dist(_posx, _posy, g->u.posx, g->u.posy) <= 5) ret += 5; for (int i = 0; i < g->active_npc.size(); i++) { if (rl_dist(_posx, _posy, g->active_npc[i]->posx, g->active_npc[i]->posy) <= 5) ret += 5; } break; case MTRIG_FIRE: check_terrain = true; check_fire = true; break; case MTRIG_PLAYER_WEAK: if (g->u.hp_percentage() <= 70) ret += 10 - int(g->u.hp_percentage() / 10); break; default: break; // The rest are handled when the impetus occurs } } if (check_terrain) { for (int x = _posx - 3; x <= _posx + 3; x++) { for (int y = _posy - 3; y <= _posy + 3; y++) { if (check_meat) { std::vector<item> *items = &(g->m.i_at(x, y)); for (int n = 0; n < items->size(); n++) { if ((*items)[n].type->id == "corpse" || (*items)[n].type->id == "meat" || (*items)[n].type->id == "meat_cooked" || (*items)[n].type->id == "human_flesh") { ret += 3; check_meat = false; } } } if (check_fire) { ret += ( 5 * g->m.get_field_strength( point(x, y), fd_fire) ); } } } if (check_fire) { if (g->u.has_amount("torch_lit", 1)) ret += 49; } } return ret; }
Path Pathfinder::path_a_star(Tripoint start, Tripoint end) { int x_size = map.get_size_x(); int y_size = map.get_size_y(); int z_size = map.get_size_z(); start.x -= map.x_offset; start.y -= map.y_offset; start.z -= map.z_offset; end.x -= map.x_offset; end.y -= map.y_offset; end.z -= map.z_offset; if (x_size == 0 || y_size == 0 || z_size == 0) { debugmsg("A* generated; %s => %s (size %d, %d, %d)", start.str().c_str(), end.str().c_str(), x_size, y_size, z_size); return Path(); } std::vector<Tripoint> open_points; A_star_status status[x_size][y_size][z_size]; int gscore[x_size][y_size][z_size]; int hscore[x_size][y_size][z_size]; Tripoint parent[x_size][y_size][z_size]; if (border > 0) { int x0 = (start.x < end.x ? start.x : end.x); int y0 = (start.y < end.y ? start.y : end.y); int z0 = (start.z < end.z ? start.z : end.z); int x1 = (start.x > end.x ? start.x : end.x); int y1 = (start.y > end.y ? start.y : end.y); int z1 = (start.z > end.z ? start.z : end.z); set_bounds(x0 - border, y0 - border, z0 - border, x1 + border, y1 + border, z1 + border); } // Init everything to 0 for (int x = 0; x < x_size; x++) { for (int y = 0; y < y_size; y++) { for (int z = 0; z < z_size; z++) { status[x][y][z] = ASTAR_NONE; gscore[x][y][z] = 0; hscore[x][y][z] = 0; parent[x][y][z] = Tripoint(-1, -1, -1); } } } status[start.x][start.y][start.z] = ASTAR_OPEN; open_points.push_back(start); bool done = false; while (!done && !open_points.empty()) { // 1) Find the lowest cost in open_points, and set (current) to that point // (if multiple points are tied, randomly select one) int lowest_cost = -1, point_index = -1; Tripoint current; int current_g = 0; std::vector<int> lowest_indices; for (int i = 0; i < open_points.size(); i++) { Tripoint p = open_points[i]; int score = gscore[p.x][p.y][p.z] + hscore[p.x][p.y][p.z]; if (i == 0 || score < lowest_cost) { lowest_cost = score; lowest_indices.clear(); lowest_indices.push_back(i); } else if (score == lowest_cost) { lowest_indices.push_back(i); } } if (lowest_indices.empty()) { // Should never happen point_index = 0; } else { point_index = lowest_indices[ rng(0, lowest_indices.size() - 1) ]; } current = open_points[point_index]; current_g = gscore[current.x][current.y][current.z]; // 2) Check if (current) is the endpoint if (current == end) { done = true; } else { // 3) Set (current) to be closed open_points.erase(open_points.begin() + point_index); status[current.x][current.y][current.z] = ASTAR_CLOSED; // 4) Examine all adjacent points on the same z-level for (int x = current.x - 1; x <= current.x + 1; x++) { for (int y = current.y - 1; y <= current.y + 1; y++) { if (x == current.x && y == current.y) { y++; // Skip the current tile } int z = current.z; // If it's not diagonal, or diagonals are allowed... // ...and if it's in-bounds and not blocked... if ((allow_diag || x == current.x || y == current.y) && (in_bounds(x, y, z) && !map.blocked(x, y, z))) { int g = current_g + map.get_cost(x, y, z); // If it's unexamined, make it open and set its values if (status[x][y][z] == ASTAR_NONE) { status[x][y][z] = ASTAR_OPEN; gscore[x][y][z] = g; if (allow_diag) { hscore[x][y][z] = map.get_cost(x, y, z) * rl_dist(x, y, z, end.x, end.y, end.z); } else { hscore[x][y][z] = map.get_cost(x, y, z) * manhattan_dist(x, y, z, end.x, end.y, end.z); } parent[x][y][z] = current; open_points.push_back( Tripoint(x, y, z) ); // Otherwise, if it's open and we're a better parent, make us the parent } else if (status[x][y][z] == ASTAR_OPEN && g < gscore[x][y][z]) { gscore[x][y][z] = g; parent[x][y][z] = current; } } } } // 5. Examine adjacent points on adjacent Z-levels // TODO: Allow diagonal movement across Z-levels? For flying monsters? // Examine above if (map.allow_z_up(current)) { int z = current.z + 1; int x = current.x, y = current.y; if ((in_bounds(x, y, z) && !map.blocked(x, y, z))) { int g = current_g + map.get_cost(x, y, z); // If it's unexamined, make it open and set its values if (status[x][y][z] == ASTAR_NONE) { status[x][y][z] = ASTAR_OPEN; gscore[x][y][z] = g; if (allow_diag) { hscore[x][y][z] = map.get_cost(x, y, z) * rl_dist(x, y, z, end.x, end.y, end.z); } else { hscore[x][y][z] = map.get_cost(x, y, z) * manhattan_dist(x, y, z, end.x, end.y, end.z); } parent[x][y][z] = current; open_points.push_back( Tripoint(x, y, z) ); // If it's open and we're a better parent, make us the parent } else if (status[x][y][z] == ASTAR_OPEN && g < gscore[x][y][z]) { gscore[x][y][z] = g; parent[x][y][z] = current; } } } // Examine below (code duplication, sorry mom) if (map.allow_z_down(current)) { int z = current.z - 1; int x = current.x, y = current.y; if ((in_bounds(x, y, z) && !map.blocked(x, y, z))) { int g = current_g + map.get_cost(x, y, z); // If it's unexamined, make it open and set its values if (status[x][y][z] == ASTAR_NONE) { debugmsg("A*'d over Z-level"); status[x][y][z] = ASTAR_OPEN; gscore[x][y][z] = g; if (allow_diag) { hscore[x][y][z] = map.get_cost(x, y, z) * rl_dist(x, y, z, end.x, end.y, end.z); } else { hscore[x][y][z] = map.get_cost(x, y, z) * manhattan_dist(x, y, z, end.x, end.y, end.z); } parent[x][y][z] = current; open_points.push_back( Tripoint(x, y, z) ); // If it's open and we're a better parent, make us the parent } else if (status[x][y][z] == ASTAR_OPEN && g < gscore[x][y][z]) { gscore[x][y][z] = g; parent[x][y][z] = current; } } } } } Path ret; if (open_points.empty()) { return ret; } Tripoint cur = end; ret.add_step(cur, map.get_cost(cur)); while (parent[cur.x][cur.y][cur.z] != start) { cur = parent[cur.x][cur.y][cur.z]; ret.add_step(cur, map.get_cost(cur)); } ret.reverse(); // Add the offsets back in. ret.offset(map.x_offset, map.y_offset, map.z_offset); return ret; }
/** * Calculates the Field Of View for the provided map from the given x, y * coordinates. Returns a lightmap for a result where the values represent a * percentage of fully lit. * * A value equal to or below 0 means that cell is not in the * field of view, whereas a value equal to or above 1 means that cell is * in the field of view. * * @param startx the horizontal component of the starting location * @param starty the vertical component of the starting location * @param radius the maximum distance to draw the FOV */ void map::build_seen_cache(const tripoint &origin) { memset(seen_cache, false, sizeof(seen_cache)); seen_cache[origin.x][origin.y] = true; castLight<0, 1, 1, 0>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<1, 0, 0, 1>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<0, -1, 1, 0>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<-1, 0, 0, 1>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<0, 1, -1, 0>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<1, 0, 0, -1>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<0, -1, -1, 0>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<-1, 0, 0, -1>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); int part; if ( vehicle *veh = veh_at( origin.x, origin.y, part ) ) { // We're inside a vehicle. Do mirror calcs. std::vector<int> mirrors = veh->all_parts_with_feature(VPFLAG_EXTENDS_VISION, true); // Do all the sight checks first to prevent fake multiple reflection // from happening due to mirrors becoming visible due to processing order. // Cameras are also handled here, so that we only need to get through all veh parts once int cam_control = -1; for (std::vector<int>::iterator m_it = mirrors.begin(); m_it != mirrors.end(); /* noop */) { const auto mirror_pos = veh->global_pos() + veh->parts[*m_it].precalc[0]; // We can utilize the current state of the seen cache to determine // if the player can see the mirror from their position. if( !veh->part_info( *m_it ).has_flag( "CAMERA" ) && !g->u.sees( mirror_pos )) { m_it = mirrors.erase(m_it); } else if( !veh->part_info( *m_it ).has_flag( "CAMERA_CONTROL" ) ) { ++m_it; } else { if( origin.x == mirror_pos.x && origin.y == mirror_pos.y && veh->camera_on ) { cam_control = *m_it; } m_it = mirrors.erase( m_it ); } } for( size_t i = 0; i < mirrors.size(); i++ ) { const int &mirror = mirrors[i]; bool is_camera = veh->part_info( mirror ).has_flag( "CAMERA" ); if( is_camera && cam_control < 0 ) { continue; // Player not at camera control, so cameras don't work } const auto mirror_pos = veh->global_pos() + veh->parts[mirror].precalc[0]; // Determine how far the light has already traveled so mirrors // don't cheat the light distance falloff. int offsetDistance; if( !is_camera ) { offsetDistance = rl_dist(origin.x, origin.y, mirror_pos.x, mirror_pos.y); } else { offsetDistance = 60 - veh->part_info( mirror ).bonus * veh->parts[mirror].hp / veh->part_info( mirror ).durability; seen_cache[mirror_pos.x][mirror_pos.y] = true; } // @todo: Factor in the mirror facing and only cast in the // directions the player's line of sight reflects to. // // The naive solution of making the mirrors act like a second player // at an offset appears to give reasonable results though. castLight<0, 1, 1, 0>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<1, 0, 0, 1>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<0, -1, 1, 0>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<-1, 0, 0, 1>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<0, 1, -1, 0>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<1, 0, 0, -1>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<0, -1, -1, 0>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<-1, 0, 0, -1>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); } } }
/** * Calculates the Field Of View for the provided map from the given x, y * coordinates. Returns a lightmap for a result where the values represent a * percentage of fully lit. * * A value equal to or below 0 means that cell is not in the * field of view, whereas a value equal to or above 1 means that cell is * in the field of view. * * @param startx the horizontal component of the starting location * @param starty the vertical component of the starting location * @param radius the maximum distance to draw the FOV */ void map::build_seen_cache() { memset(seen_cache, false, sizeof(seen_cache)); seen_cache[g->u.posx][g->u.posy] = true; const int offsetX = g->u.posx; const int offsetY = g->u.posy; castLight( 1, 1.0f, 0.0f, 0, 1, 1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, 1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, -1, 1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, 1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, 1, -1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, -1, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, 0, -1, -1, 0, offsetX, offsetY, 0 ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, -1, offsetX, offsetY, 0 ); if (vehicle *veh = veh_at(offsetX, offsetY)) { // We're inside a vehicle. Do mirror calcs. std::vector<int> mirrors = veh->all_parts_with_feature(VPFLAG_MIRROR, true); // Do all the sight checks first to prevent fake multiple reflection // from happening due to mirrors becoming visible due to processing order. for (std::vector<int>::iterator m_it = mirrors.begin(); m_it != mirrors.end(); /* noop */) { const int mirrorX = veh->global_x() + veh->parts[*m_it].precalc_dx[0]; const int mirrorY = veh->global_y() + veh->parts[*m_it].precalc_dy[0]; // We can utilize the current state of the seen cache to determine // if the player can see the mirror from their position. if (!g->u.sees(mirrorX, mirrorY)) { m_it = mirrors.erase(m_it); } else { ++m_it; } } for (std::vector<int>::iterator m_it = mirrors.begin(); m_it != mirrors.end(); ++m_it) { const int mirrorX = veh->global_x() + veh->parts[*m_it].precalc_dx[0]; const int mirrorY = veh->global_y() + veh->parts[*m_it].precalc_dy[0]; // Determine how far the light has already traveled so mirrors // don't cheat the light distance falloff. int offsetDistance = rl_dist(offsetX, offsetY, mirrorX, mirrorY); // @todo: Factor in the mirror facing and only cast in the // directions the player's line of sight reflects to. // // The naive solution of making the mirrors act like a second player // at an offset appears to give reasonable results though. castLight( 1, 1.0f, 0.0f, 0, 1, 1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, 1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, -1, 1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, 1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, 1, -1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 1, 0, 0, -1, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, 0, -1, -1, 0, mirrorX, mirrorY, offsetDistance ); castLight( 1, 1.0f, 0.0f, -1, 0, 0, -1, mirrorX, mirrorY, offsetDistance ); } } }
void castLight( bool (&output_cache)[MAPSIZE*SEEX][MAPSIZE*SEEY], const float (&input_array)[MAPSIZE*SEEX][MAPSIZE*SEEY], const int offsetX, const int offsetY, const int offsetDistance, const int row, float start, const float end ) { float newStart = 0.0f; float radius = 60.0f - offsetDistance; if( start < end ) { return; } // Making these static prevents them from being needlessly constructed/destructed all the time. static const tripoint origin(0, 0, 0); tripoint delta(0, 0, 0); for( int distance = row; distance <= radius; distance++ ) { delta.y = -distance; bool started_row = false; float current_transparency = 0.0; for( delta.x = -distance; delta.x <= 0; delta.x++ ) { int currentX = offsetX + delta.x * xx + delta.y * xy; int currentY = offsetY + delta.x * yx + delta.y * yy; float leadingEdge = (delta.x - 0.5f) / (delta.y + 0.5f); float trailingEdge = (delta.x + 0.5f) / (delta.y - 0.5f); if( !(currentX >= 0 && currentY >= 0 && currentX < SEEX * MAPSIZE && currentY < SEEY * MAPSIZE) || start < trailingEdge ) { continue; } else if( end > leadingEdge ) { break; } if( !started_row ) { started_row = true; current_transparency = input_array[ currentX ][ currentY ]; } //check if it's within the visible area and mark visible if so if( rl_dist(origin, delta) <= radius ) { /* float bright = (float) (1 - (rStrat.radius(delta.x, delta.y) / radius)); lightMap[currentX][currentY] = bright; */ // TODO: handle circular distance. output_cache[ currentX ][ currentY] = true; } float new_transparency = input_array[ currentX ][ currentY ]; if( new_transparency != current_transparency ) { // Only cast recursively if previous span was not opaque. if( current_transparency != LIGHT_TRANSPARENCY_SOLID ) { castLight<xx, xy, yx, yy>( output_cache, input_array, offsetX, offsetY, offsetDistance, distance + 1, start, leadingEdge); newStart = trailingEdge; } // We either recursed into a transparent span, or did NOT recurse into an opaque span, // either way the new span starts at the trailing edge of the previous square. if( new_transparency != LIGHT_TRANSPARENCY_SOLID ) { start = newStart; } current_transparency = new_transparency; } else if( current_transparency == LIGHT_TRANSPARENCY_SOLID ) { newStart = trailingEdge; } } if( current_transparency == LIGHT_TRANSPARENCY_SOLID ) { // If we reach the end of the span with terrain being opaque, we don't iterate further. break; } } }