/** * 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 ); } } }
bool map::process_fields_in_submap(game *g, int gridn) { bool found_field = false; field *cur; field_id curtype; for (int locx = 0; locx < SEEX; locx++) { for (int locy = 0; locy < SEEY; locy++) { cur = &(grid[gridn].fld[locx][locy]); int x = locx + SEEX * (gridn % my_MAPSIZE), y = locy + SEEY * int(gridn / my_MAPSIZE); curtype = cur->type; if (!found_field && curtype != fd_null) found_field = true; if (cur->density > 3 || cur->density < 1) debugmsg("Whoooooa density of %d", cur->density); if (cur->age == 0) // Don't process "newborn" fields curtype = fd_null; int part; vehicle *veh; switch (curtype) { case fd_null: break; // Do nothing, obviously. OBVIOUSLY. case fd_blood: case fd_bile: if (has_flag(swimmable, x, y)) // Dissipate faster in water cur->age += 250; break; case fd_acid: if (has_flag(swimmable, x, y)) // Dissipate faster in water cur->age += 20; for (int i = 0; i < i_at(x, y).size(); i++) { item *melting = &(i_at(x, y)[i]); if (melting->made_of(LIQUID) || melting->made_of(VEGGY) || melting->made_of(FLESH) || melting->made_of(POWDER) || melting->made_of(COTTON) || melting->made_of(WOOL) || melting->made_of(PAPER) || melting->made_of(PLASTIC) || (melting->made_of(GLASS) && !one_in(3)) || one_in(4)) { // Acid destructable objects here melting->damage++; if (melting->damage >= 5 || (melting->made_of(PAPER) && melting->damage >= 3)) { cur->age += melting->volume(); for (int m = 0; m < i_at(x, y)[i].contents.size(); m++) i_at(x, y).push_back( i_at(x, y)[i].contents[m] ); i_at(x, y).erase(i_at(x, y).begin() + i); i--; } } } break; case fd_sap: break; // It doesn't do anything. case fd_fire: { // Consume items as fuel to help us grow/last longer. bool destroyed = false; int vol = 0, smoke = 0, consumed = 0; for (int i = 0; i < i_at(x, y).size() && consumed < cur->density * 2; i++) { destroyed = false; vol = i_at(x, y)[i].volume(); item *it = &(i_at(x, y)[i]); if (it->is_ammo() && it->ammo_type() != AT_BATT && it->ammo_type() != AT_NAIL && it->ammo_type() != AT_BB && it->ammo_type() != AT_BOLT && it->ammo_type() != AT_ARROW) { cur->age /= 2; cur->age -= 600; destroyed = true; smoke += 6; consumed++; } else if (it->made_of(PAPER)) { destroyed = it->burn(cur->density * 3); consumed++; if (cur->density == 1) cur->age -= vol * 10; if (vol >= 4) smoke++; } else if ((it->made_of(WOOD) || it->made_of(VEGGY))) { if (vol <= cur->density * 10 || cur->density == 3) { cur->age -= 4; destroyed = it->burn(cur->density); smoke++; consumed++; } else if (it->burnt < cur->density) { destroyed = it->burn(1); smoke++; } } else if ((it->made_of(COTTON) || it->made_of(WOOL))) { if (vol <= cur->density * 5 || cur->density == 3) { cur->age--; destroyed = it->burn(cur->density); smoke++; consumed++; } else if (it->burnt < cur->density) { destroyed = it->burn(1); smoke++; } } else if (it->made_of(FLESH)) { if (vol <= cur->density * 5 || (cur->density == 3 && one_in(vol / 20))) { cur->age--; destroyed = it->burn(cur->density); smoke += 3; consumed++; } else if (it->burnt < cur->density * 5 || cur->density >= 2) { destroyed = it->burn(1); smoke++; } } else if (it->made_of(LIQUID)) { switch (it->type->id) { // TODO: Make this be not a hack. case itm_whiskey: case itm_vodka: case itm_rum: case itm_tequila: cur->age -= 300; smoke += 6; break; default: cur->age += rng(80 * vol, 300 * vol); smoke++; } destroyed = true; consumed++; } else if (it->made_of(POWDER)) { cur->age -= vol; destroyed = true; smoke += 2; } else if (it->made_of(PLASTIC)) { smoke += 3; if (it->burnt <= cur->density * 2 || (cur->density == 3 && one_in(vol))) { destroyed = it->burn(cur->density); if (one_in(vol + it->burnt)) cur->age--; } } if (destroyed) { for (int m = 0; m < i_at(x, y)[i].contents.size(); m++) i_at(x, y).push_back( i_at(x, y)[i].contents[m] ); i_at(x, y).erase(i_at(x, y).begin() + i); i--; } } veh = &(veh_at(x, y, part)); if (veh->type != veh_null && (veh->parts[part].flags & VHP_FUEL_TANK) && veh->fuel_type == AT_GAS) { if (cur->density > 1 && one_in (8) && veh->fuel > 0) veh->explode (g, x, y); } // Consume the terrain we're on if (has_flag(explodes, x, y)) { ter(x, y) = ter_id(int(ter(x, y)) + 1); cur->age = 0; cur->density = 3; g->explosion(x, y, 40, 0, true); } else if (has_flag(inflammable, x, y) && one_in(32 - cur->density * 10)) { cur->age -= cur->density * cur->density * 40; smoke += 15; if (cur->density == 3) ter(x, y) = t_ash; } else if (has_flag(flammable, x, y) && one_in(32 - cur->density * 10)) { cur->age -= cur->density * cur->density * 40; smoke += 15; if (cur->density == 3) ter(x, y) = t_rubble; } else if (has_flag(meltable, x, y) && one_in(32 - cur->density * 10)) { cur->age -= cur->density * cur->density * 40; if (cur->density == 3) ter(x, y) = t_b_metal; } else if (terlist[ter(x, y)].flags & mfb(swimmable)) cur->age += 800; // Flames die quickly on water // If we consumed a lot, the flames grow higher while (cur->density < 3 && cur->age < 0) { cur->age += 300; cur->density++; } // If the flames are in a pit, it can't spread to non-pit bool in_pit = (ter(x, y) == t_pit); // If the flames are REALLY big, they contribute to adjacent flames if (cur->density == 3 && cur->age < 0) { // Randomly offset our x/y shifts by 0-2, to randomly pick a square to spread to int starti = rng(0, 2); int startj = rng(0, 2); for (int i = 0; i < 3 && cur->age < 0; i++) { for (int j = 0; j < 3 && cur->age < 0; j++) { int fx = x + ((i + starti) % 3) - 1, fy = y + ((j + startj) % 3) - 1; if (field_at(fx, fy).type == fd_fire && field_at(fx, fy).density < 3 && (!in_pit || ter(fx, fy) == t_pit)) { field_at(fx, fy).density++; field_at(fx, fy).age = 0; cur->age = 0; } } } } // Consume adjacent fuel / terrain to spread. // Randomly offset our x/y shifts by 0-2, to randomly pick a square to spread to int starti = rng(0, 2); int startj = rng(0, 2); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { int fx = x + ((i + starti) % 3) - 1, fy = y + ((j + startj) % 3) - 1; if (INBOUNDS(fx, fy)) { int spread_chance = 20 * (cur->density - 1) + 10 * smoke; if (has_flag(explodes, fx, fy) && one_in(8 - cur->density)) { ter(fx, fy) = ter_id(int(ter(fx, fy)) + 1); g->explosion(fx, fy, 40, 0, true); } else if ((i != 0 || j != 0) && rng(1, 100) < spread_chance && (!in_pit || ter(fx, fy) == t_pit) && ((cur->density == 3 && (has_flag(flammable, fx, fy) || one_in(20))) || flammable_items_at(fx, fy) || field_at(fx, fy).type == fd_web)) { if (field_at(fx, fy).type == fd_smoke || field_at(fx, fy).type == fd_web) field_at(fx, fy) = field(fd_fire, 1, 0); else add_field(g, fx, fy, fd_fire, 1); } else { bool nosmoke = true; for (int ii = -1; ii <= 1; ii++) { for (int jj = -1; jj <= 1; jj++) { if (field_at(x+ii, y+jj).type == fd_fire && field_at(x+ii, y+jj).density == 3) smoke++; else if (field_at(x+ii, y+jj).type == fd_smoke) nosmoke = false; } } // If we're not spreading, maybe we'll stick out some smoke, huh? if (move_cost(fx, fy) > 0 && (!one_in(smoke) || (nosmoke && one_in(40))) && rng(3, 35) < cur->density * 10 && cur->age < 1000) { smoke--; add_field(g, fx, fy, fd_smoke, rng(1, cur->density)); } } } } } } break; case fd_smoke: for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 50; if (one_in(2)) { std::vector <point> spread; for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if ((field_at(x+a, y+b).type == fd_smoke && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; if (field_at(p.x, p.y).type == fd_smoke && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_smoke, 1)){ cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_tear_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 30; // One in three chance that it spreads (less than smoke!) if (one_in(3)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby teargas grows thicker if (field_at(p.x, p.y).type == fd_tear_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke is converted into teargas } else if (field_at(p.x, p.y).type == fd_smoke) { field_at(p.x, p.y).type = fd_tear_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_tear_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_toxic_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 40; if (one_in(2)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas || field_at(x+a, y+b).type == fd_toxic_gas || field_at(x+a, y+b).type == fd_nuke_gas ) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby toxic gas grows thicker if (field_at(p.x, p.y).type == fd_toxic_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke & teargas is converted into toxic gas } else if (field_at(p.x, p.y).type == fd_smoke || field_at(p.x, p.y).type == fd_tear_gas) { field_at(p.x, p.y).type = fd_toxic_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_toxic_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_nuke_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 40; // Increase long-term radiation in the land underneath radiation(x, y) += rng(0, cur->density); if (one_in(2)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas || field_at(x+a, y+b).type == fd_toxic_gas || field_at(x+a, y+b).type == fd_nuke_gas ) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby nukegas grows thicker if (field_at(p.x, p.y).type == fd_nuke_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke, tear, and toxic gas is converted into nukegas } else if (field_at(p.x, p.y).type == fd_smoke || field_at(p.x, p.y).type == fd_toxic_gas || field_at(p.x, p.y).type == fd_tear_gas) { field_at(p.x, p.y).type = fd_nuke_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_nuke_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_gas_vent: for (int i = x - 1; i <= x + 1; i++) { for (int j = y - 1; j <= y + 1; j++) { if (field_at(i, j).type == fd_toxic_gas && field_at(i, j).density < 3) field_at(i, j).density++; else add_field(g, i, j, fd_toxic_gas, 3); } } break; case fd_fire_vent: if (cur->density > 1) { if (one_in(3)) cur->density--; } else { cur->type = fd_flame_burst; cur->density = 3; } break; case fd_flame_burst: if (cur->density > 1) cur->density--; else { cur->type = fd_fire_vent; cur->density = 3; } break; case fd_electricity: if (!one_in(5)) { // 4 in 5 chance to spread std::vector<point> valid; if (move_cost(x, y) == 0 && cur->density > 1) { // We're grounded int tries = 0; while (tries < 10 && cur->age < 50) { int cx = x + rng(-1, 1), cy = y + rng(-1, 1); if (move_cost(cx, cy) != 0 && field_at(cx, cy).is_null()) { add_field(g, cx, cy, fd_electricity, 1); cur->density--; tries = 0; } else tries++; } } else { // We're not grounded; attempt to ground for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (move_cost(x + a, y + b) == 0 && // Grounded tiles first field_at(x + a, y + b).is_null()) valid.push_back(point(x + a, y + b)); } } if (valid.size() == 0) { // Spread to adjacent space, then int px = x + rng(-1, 1), py = y + rng(-1, 1); if (move_cost(px, py) > 0 && field_at(px, py).type == fd_electricity && field_at(px, py).density < 3) field_at(px, py).density++; else if (move_cost(px, py) > 0) add_field(g, px, py, fd_electricity, 1); cur->density--; } while (valid.size() > 0 && cur->density > 0) { int index = rng(0, valid.size() - 1); add_field(g, valid[index].x, valid[index].y, fd_electricity, 1); cur->density--; valid.erase(valid.begin() + index); } } } break; case fd_fatigue: if (cur->density < 3 && int(g->turn) % 3600 == 0 && one_in(10)) cur->density++; else if (cur->density == 3 && one_in(600)) { // Spawn nether creature! mon_id type = mon_id(rng(mon_flying_polyp, mon_blank)); monster creature(g->mtypes[type]); creature.spawn(x + rng(-3, 3), y + rng(-3, 3)); g->z.push_back(creature); } break; } cur->age++; if (fieldlist[cur->type].halflife > 0) { if (cur->age > 0 && dice(3, cur->age) > dice(3, fieldlist[cur->type].halflife)) { cur->age = 0; cur->density--; } if (cur->density <= 0) { // Totally dissapated. grid[gridn].field_count--; grid[gridn].fld[locx][locy] = field(); } } } } return found_field; }
/** * 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, const int target_z ) { auto &map_cache = get_cache( target_z ); float (&transparency_cache)[MAPSIZE*SEEX][MAPSIZE*SEEY] = map_cache.transparency_cache; float (&seen_cache)[MAPSIZE*SEEX][MAPSIZE*SEEY] = map_cache.seen_cache; std::uninitialized_fill_n( &seen_cache[0][0], MAPSIZE*SEEX * MAPSIZE*SEEY, LIGHT_TRANSPARENCY_SOLID); if( !fov_3d ) { seen_cache[origin.x][origin.y] = LIGHT_TRANSPARENCY_CLEAR; castLight<0, 1, 1, 0, sight_calc, sight_check>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<1, 0, 0, 1, sight_calc, sight_check>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<0, -1, 1, 0, sight_calc, sight_check>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<-1, 0, 0, 1, sight_calc, sight_check>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<0, 1, -1, 0, sight_calc, sight_check>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<1, 0, 0, -1, sight_calc, sight_check>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<0, -1, -1, 0, sight_calc, sight_check>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); castLight<-1, 0, 0, -1, sight_calc, sight_check>( seen_cache, transparency_cache, origin.x, origin.y, 0 ); } else { if( origin.z == target_z ) { seen_cache[origin.x][origin.y] = LIGHT_TRANSPARENCY_CLEAR; } // Cache the caches (pointers to them) std::array<const float (*)[MAPSIZE*SEEX][MAPSIZE*SEEY], OVERMAP_LAYERS> transparency_caches; std::array<float (*)[MAPSIZE*SEEX][MAPSIZE*SEEY], OVERMAP_LAYERS> seen_caches; std::array<const bool (*)[MAPSIZE*SEEX][MAPSIZE*SEEY], OVERMAP_LAYERS> floor_caches; for( int z = -OVERMAP_DEPTH; z <= OVERMAP_HEIGHT; z++ ) { auto &cur_cache = get_cache( z ); transparency_caches[z + OVERMAP_DEPTH] = &cur_cache.transparency_cache; seen_caches[z + OVERMAP_DEPTH] = &cur_cache.seen_cache; floor_caches[z + OVERMAP_DEPTH] = &cur_cache.floor_cache; } // Down cast_zlight<0, 1, 0, 1, 0, 0, -1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<1, 0, 0, 0, 1, 0, -1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<0, -1, 0, 1, 0, 0, -1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<-1, 0, 0, 0, 1, 0, -1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<0, 1, 0, -1, 0, 0, -1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<1, 0, 0, 0, -1, 0, -1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<0, -1, 0, -1, 0, 0, -1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<-1, 0, 0, 0, -1, 0, -1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<0, 1, 0, 1, 0, 0, 1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<1, 0, 0, 0, 1, 0, 1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); // Up cast_zlight<0, -1, 0, 1, 0, 0, 1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<-1, 0, 0, 0, 1, 0, 1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<0, 1, 0, -1, 0, 0, 1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<1, 0, 0, 0, -1, 0, 1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<0, -1, 0, -1, 0, 0, 1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); cast_zlight<-1, 0, 0, 0, -1, 0, 1, sight_calc, sight_check>( seen_caches, transparency_caches, floor_caches, origin, 0 ); } int part; vehicle *veh = veh_at( origin, part ); if( veh == nullptr ) { return; } // 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" ) && seen_cache[mirror_pos.x][mirror_pos.y] < LIGHT_TRANSPARENCY_SOLID + 0.1 ) { 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] = LIGHT_TRANSPARENCY_OPEN_AIR; } // @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, sight_calc, sight_check>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<1, 0, 0, 1, sight_calc, sight_check>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<0, -1, 1, 0, sight_calc, sight_check>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<-1, 0, 0, 1, sight_calc, sight_check>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<0, 1, -1, 0, sight_calc, sight_check>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<1, 0, 0, -1, sight_calc, sight_check>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<0, -1, -1, 0, sight_calc, sight_check>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); castLight<-1, 0, 0, -1, sight_calc, sight_check>( seen_cache, transparency_cache, mirror_pos.x, mirror_pos.y, offsetDistance ); } }