diff options
Diffstat (limited to 'crawl-ref/source/mon-los.cc')
-rw-r--r-- | crawl-ref/source/mon-los.cc | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/crawl-ref/source/mon-los.cc b/crawl-ref/source/mon-los.cc new file mode 100644 index 0000000000..17ad5c3e16 --- /dev/null +++ b/crawl-ref/source/mon-los.cc @@ -0,0 +1,407 @@ +/* + * File: mon-los.cc + * Summary: Monster line-of-sight. + */ + +#include "AppHdr.h" +REVISION("$Rev$"); +#include "mon-los.h" + +#include "los.h" +#include "ray.h" +#include "stuff.h" + +// Static class members must be initialised outside of the class +// declaration, or gcc won't define them in view.o and we'll get a +// linking error. +const int monster_los::LSIZE = _monster_los_LSIZE; +const int monster_los::L_VISIBLE = 1; +const int monster_los::L_UNKNOWN = 0; +const int monster_los::L_BLOCKED = -1; + +///////////////////////////////////////////////////////////////////////////// +// monster_los + +monster_los::monster_los() + : gridx(0), gridy(0), mons(), range(LOS_RADIUS), los_field() +{ +} + +monster_los::~monster_los() +{ +} + +void monster_los::set_monster(monsters *mon) +{ + mons = mon; + set_los_centre(mon->pos()); +} + +void monster_los::set_los_centre(int x, int y) +{ + if (!in_bounds(x, y)) + return; + + gridx = x; + gridy = y; +} + +void monster_los::set_los_range(int r) +{ + ASSERT (r >= 1 && r <= LOS_RADIUS); + range = r; +} + +coord_def monster_los::pos_to_index(coord_def &p) +{ + int ix = LOS_RADIUS + p.x - gridx; + int iy = LOS_RADIUS + p.y - gridy; + + ASSERT(ix >= 0 && ix < LSIZE); + ASSERT(iy >= 0 && iy < LSIZE); + + return (coord_def(ix, iy)); +} + +coord_def monster_los::index_to_pos(coord_def &i) +{ + int px = i.x + gridx - LOS_RADIUS; + int py = i.y + gridy - LOS_RADIUS; + + ASSERT(in_bounds(px, py)); + return (coord_def(px, py)); +} + +void monster_los::set_los_value(int x, int y, bool blocked, bool override) +{ + if (!override && !is_unknown(x,y)) + return; + + coord_def c(x,y); + coord_def lpos = pos_to_index(c); + + int value = (blocked ? L_BLOCKED : L_VISIBLE); + + if (value != los_field[lpos.x][lpos.y]) + los_field[lpos.x][lpos.y] = value; +} + +int monster_los::get_los_value(int x, int y) +{ + // Too far away -> definitely out of sight! + if (distance(x, y, gridx, gridy) > get_los_radius_squared()) + return (L_BLOCKED); + + coord_def c(x,y); + coord_def lpos = pos_to_index(c); + return (los_field[lpos.x][lpos.y]); +} + +bool monster_los::in_sight(int x, int y) +{ + // Is the path to (x,y) clear? + return (get_los_value(x,y) == L_VISIBLE); +} + +bool monster_los::is_blocked(int x, int y) +{ + // Is the path to (x,y) blocked? + return (get_los_value(x, y) == L_BLOCKED); +} + +bool monster_los::is_unknown(int x, int y) +{ + return (get_los_value(x, y) == L_UNKNOWN); +} + +static bool _blocks_movement_sight(monsters *mon, dungeon_feature_type feat) +{ + if (feat < DNGN_MINMOVE) + return (true); + + if (!mon) // No monster defined? + return (false); + + if (!mon->can_pass_through_feat(feat)) + return (true); + + return (false); +} + +void monster_los::fill_los_field() +{ + int pos_x, pos_y; + for (int k = 1; k <= range; k++) + for (int i = -1; i <= 1; i++) + for (int j = -1; j <= 1; j++) + { + if (i == 0 && j == 0) // Ignore centre grid. + continue; + + pos_x = gridx + k*i; + pos_y = gridy + k*j; + + if (!in_bounds(pos_x, pos_y)) + continue; + + if (!_blocks_movement_sight(mons, grd[pos_x][pos_y])) + set_los_value(pos_x, pos_y, false); + else + { + set_los_value(pos_x, pos_y, true); + // Check all beam potentially going through a blocked grid. + check_los_beam(pos_x, pos_y); + } + } +} + +// (cx, cy) is the centre point +// (dx, dy) is the target we're aiming *through* +// target1 and target2 are targets we'll be aiming *at* to fire through (dx,dy) +static bool _set_beam_target(int cx, int cy, int dx, int dy, + int &target1_x, int &target1_y, + int &target2_x, int &target2_y, + int range) +{ + const int xdist = dx - cx; + const int ydist = dy - cy; + + if (xdist == 0 && ydist == 0) + return (false); // Nothing to be done. + + if (xdist <= -range || xdist >= range + || ydist <= -range || ydist >= range) + { + // Grids on the edge of a monster's LOS don't block sight any further. + return (false); + } + +/* + * The following code divides the field into eights of different directions. + * + * \ NW | NE / + * \ | / + * WN \ | / EN + * ---------------- + * WS / | \ ES + * / | \ + * / SW | SE \ + * + * target1_x and target1_y mark the base line target, so the base beam ends + * on the diagonal line closest to the target (or on one of the straight + * lines if cx == dx or dx == dy). + * + * target2_x and target2_y then mark the second target our beam finder should + * cycle through. It'll always be target2_x = dx or target2_y = dy, the other + * being on the edge of LOS, which one depending on the quadrant. + * + * The beam finder can then cycle from the nearest corner (target1) to the + * second edge target closest to (dx,dy). + */ + + if (xdist == 0) + { + target1_x = cx; + target1_y = (ydist > 0 ? cy + range + : cy - range); + + target2_x = target1_x; + target2_y = target1_y; + } + else if (ydist == 0) + { + target1_x = (xdist > 0 ? cx + range + : cx - range); + target1_y = cy; + + target2_x = target1_x; + target2_y = target1_y; + } + else if (xdist < 0 && ydist < 0 || xdist > 0 && ydist > 0) + { + if (xdist < 0) + { + target1_x = cx - range; + target1_y = cy - range; + } + else + { + target1_x = cx + range; + target1_y = cy + range; + } + + if (xdist == ydist) + { + target2_x = target1_x; + target2_y = target1_y; + } + else + { + if (xdist < 0) // both are negative (upper left corner) + { + if (dx > dy) + { + target2_x = dx; + target2_y = cy - range; + } + else + { + target2_x = cx - range; + target2_y = dy; + } + } + else // both are positive (lower right corner) + { + if (dx > dy) + { + target2_x = cx + range; + target2_y = dy; + } + else + { + target2_x = dx; + target2_y = cy + range; + } + } + } + } + else if (xdist < 0 && ydist > 0 || xdist > 0 && ydist < 0) + { + if (xdist < 0) // lower left corner + { + target1_x = cx - range; + target1_y = cy + range; + } + else // upper right corner + { + target1_x = cx + range; + target1_y = cy - range; + } + + if (xdist == -ydist) + { + target2_x = target1_x; + target2_y = target1_y; + } + else + { + if (xdist < 0) // ydist > 0 + { + if (-xdist > ydist) + { + target2_x = cx - range; + target2_y = dy; + } + else + { + target2_x = dx; + target2_y = cy + range; + } + } + else // xdist > 0, ydist < 0 + { + if (-xdist > ydist) + { + target2_x = dx; + target2_y = cy - range; + } + else + { + target2_x = cx + range; + target2_y = dy; + } + } + } + } + else + { + // Everything should have been handled above. + ASSERT(false); + } + + return (true); +} + +void monster_los::check_los_beam(int dx, int dy) +{ + ray_def ray; + + int target1_x = 0, target1_y = 0, target2_x = 0, target2_y = 0; + if (!_set_beam_target(gridx, gridy, dx, dy, target1_x, target1_y, + target2_x, target2_y, range)) + { + // Nothing to be done. + return; + } + + if (target1_x > target2_x || target1_y > target2_y) + { + // Swap the two targets so our loop will work correctly. + int help = target1_x; + target1_x = target2_x; + target2_x = help; + + help = target1_y; + target1_y = target2_y; + target2_y = help; + } + + const int max_dist = range; + int dist; + bool blocked = false; + for (int tx = target1_x; tx <= target2_x; tx++) + for (int ty = target1_y; ty <= target2_y; ty++) + { + // If (tx, ty) lies outside the level boundaries, there's nothing + // that shooting a ray into that direction could bring us, esp. + // as earlier grids in the ray will already have been handled, and + // out of bounds grids are simply skipped in any LoS check. + if (!map_bounds(tx, ty)) + continue; + + // Already calculated a beam to (tx, ty), don't do so again. + if (!is_unknown(tx, ty)) + continue; + + dist = 0; + ray.fullray_idx = -1; // to quiet valgrind + find_ray( coord_def(gridx, gridy), coord_def(tx, ty), + true, ray, 0, true, true ); + + if (ray.x() == gridx && ray.y() == gridy) + ray.advance(true); + + while (dist++ <= max_dist) + { + // The ray brings us out of bounds of the level map. + // Since we're always shooting outwards there's nothing more + // to look at in that direction, and we can break the loop. + if (!map_bounds(ray.x(), ray.y())) + break; + + if (blocked) + { + // Earlier grid blocks this beam, set to blocked if + // unknown, but don't overwrite visible grids. + set_los_value(ray.x(), ray.y(), true); + } + else if (_blocks_movement_sight(mons, grd[ray.x()][ray.y()])) + { + set_los_value(ray.x(), ray.y(), true); + // The rest of the beam will now be blocked. + blocked = true; + } + else + { + // Allow overriding in case another beam has marked this + // field as blocked, because we've found a solution where + // that isn't the case. + set_los_value(ray.x(), ray.y(), false, true); + } + if (ray.x() == tx && ray.y() == ty) + break; + + ray.advance(true); + } + } +} |