summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/view.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/view.cc')
-rw-r--r--crawl-ref/source/view.cc359
1 files changed, 359 insertions, 0 deletions
diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc
index 6afaa25843..7f62bf9057 100644
--- a/crawl-ref/source/view.cc
+++ b/crawl-ref/source/view.cc
@@ -5295,3 +5295,362 @@ void handle_terminal_resize(bool redraw)
if (redraw)
redraw_screen();
}
+
+/////////////////////////////////////////////////////////////////////////////
+// monster_los
+
+monster_los::monster_los()
+ : gridx(0), gridy(0), mons(), los_field()
+{
+}
+
+monster_los::~monster_los()
+{
+}
+
+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) > LOS_RADIUS * LOS_RADIUS)
+ 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::set_monster(monsters *mon)
+{
+ mons = mon;
+ if (gridx != mons->x)
+ gridx = mons->x;
+ if (gridy != mons->y)
+ gridy = mons->y;
+}
+
+void monster_los::set_los_centre(int x, int y)
+{
+ if (!in_bounds(x, y))
+ return;
+
+ gridx = x;
+ gridy = y;
+}
+
+void monster_los::fill_los_field()
+{
+ int pos_x, pos_y;
+ for (int k = 1; k <= LOS_RADIUS; 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)
+{
+ const int xdist = dx - cx;
+ const int ydist = dy - cy;
+
+ if (xdist == 0 && ydist == 0)
+ return (false); // Nothing to be done.
+
+ if (xdist <= -LOS_RADIUS || xdist >= LOS_RADIUS
+ || ydist <= -LOS_RADIUS || ydist >= LOS_RADIUS)
+ {
+ // 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 one of the straight line
+ * 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 0 or 2*LOS_RADIUS 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 + LOS_RADIUS
+ : cy - LOS_RADIUS);
+
+ target2_x = target1_x;
+ target2_y = target1_y;
+ }
+ else if (ydist == 0)
+ {
+ target1_x = (xdist > 0 ? cx + LOS_RADIUS
+ : cx - LOS_RADIUS);
+ 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 - LOS_RADIUS;
+ target1_y = cy - LOS_RADIUS;
+ }
+ else
+ {
+ target1_x = cx + LOS_RADIUS;
+ target1_y = cy + LOS_RADIUS;
+ }
+
+ 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 - LOS_RADIUS;
+ }
+ else
+ {
+ target2_x = cx - LOS_RADIUS;
+ target2_y = dy;
+ }
+ }
+ else // both are positive (lower right corner)
+ {
+ if (dx > dy)
+ {
+ target2_x = cx + LOS_RADIUS;
+ target2_y = dy;
+ }
+ else
+ {
+ target2_x = dx;
+ target2_y = cy + LOS_RADIUS;
+ }
+ }
+ }
+ }
+ else if (xdist < 0 && ydist > 0 || xdist > 0 && ydist < 0)
+ {
+ if (xdist < 0) // lower left corner
+ {
+ target1_x = cx - LOS_RADIUS;
+ target1_y = cy + LOS_RADIUS;
+ }
+ else // upper right corner
+ {
+ target1_x = cx + LOS_RADIUS;
+ target1_y = cy - LOS_RADIUS;
+ }
+
+ if (xdist == -ydist)
+ {
+ target2_x = target1_x;
+ target2_y = target1_y;
+ }
+ else
+ {
+ if (xdist < 0) // ydist > 0
+ {
+ if (-xdist > ydist)
+ {
+ target2_x = cx - LOS_RADIUS;
+ target2_y = dy;
+ }
+ else
+ {
+ target2_x = dx;
+ target2_y = cy + LOS_RADIUS;
+ }
+ }
+ else // xdist > 0, ydist < 0
+ {
+ if (-xdist > ydist)
+ {
+ target2_x = dx;
+ target2_y = cy - LOS_RADIUS;
+ }
+ else
+ {
+ target2_x = cx + LOS_RADIUS;
+ 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))
+ {
+ // Nothing to be done.
+ return;
+ }
+
+ const int max_dist = LOS_RADIUS;
+ int dist;
+ bool blocked = false;
+ for (int tx = target1_x; tx <= target2_x; tx++)
+ for (int ty = target1_y; ty <= target2_y; ty++)
+ {
+ // 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( gridx, gridy, tx, ty, true, ray, 0, true, true );
+
+ ASSERT(in_bounds(ray.x(), ray.y()));
+ if (ray.x() == gridx && ray.y() == gridy)
+ ray.advance(true);
+
+ while (dist++ <= max_dist)
+ {
+ ASSERT(in_bounds(ray.x(), ray.y()));
+ 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);
+ }
+ }
+}