fov
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
use crate::archetype::Archetype;
|
||||
use crate::floor::Floor;
|
||||
use crate::fov::{SIGHT_RADIUS, Visibility};
|
||||
use crate::glyph::Glyph;
|
||||
use doryen_fov::{FovAlgorithm, FovRecursiveShadowCasting, MapData};
|
||||
use crate::log::LogLine;
|
||||
use crate::object_def::ObjectDef;
|
||||
use crate::utils::Direction;
|
||||
@@ -102,6 +104,12 @@ pub struct Board {
|
||||
/// rather than being tied to a specific object cell. Scripts live in
|
||||
/// [`World::scripts`](crate::world::World) and are looked up by this name.
|
||||
pub board_script_name: Option<String>,
|
||||
/// When `true`, this board is "dark": front-ends reveal only the cells
|
||||
/// within the player's field of view (see [`Board::player_fov`]) and draw
|
||||
/// everything else as unlit darkness. Sight is blocked by opaque cells.
|
||||
/// Loaded from / saved to the `dark` key in the map file's `[map]` header;
|
||||
/// defaults to `false` (fully lit).
|
||||
pub dark: bool,
|
||||
/// Nonfatal problems collected while loading this map (e.g. unknown
|
||||
/// archetypes, dropped objects, recovered placement chars), as red-on-black
|
||||
/// [`LogLine`]s. Empty for a clean load; see [`Board::is_valid`]. Not part of
|
||||
@@ -267,6 +275,50 @@ impl Board {
|
||||
self.solid_at(x, y).is_none()
|
||||
}
|
||||
|
||||
/// Returns `true` if cell `(x, y)` blocks line of sight.
|
||||
///
|
||||
/// A cell is sight-blocking if its grid terrain is opaque (e.g. a `Wall`)
|
||||
/// **or** any object on it is opaque. This is the input to field-of-view on
|
||||
/// [`dark`](Board::dark) boards; see [`Board::player_fov`].
|
||||
/// Panics if `x` or `y` are out of bounds.
|
||||
pub fn is_opaque_at(&self, x: usize, y: usize) -> bool {
|
||||
self.get(x, y).1.behavior().opaque
|
||||
|| self.objects.values().any(|o| o.x == x && o.y == y && o.opaque)
|
||||
}
|
||||
|
||||
/// Computes the player's field of view on this board.
|
||||
///
|
||||
/// Returns `None` unless the board is [`dark`](Board::dark) — a lit board
|
||||
/// needs no FOV, and front-ends draw every cell. On a dark board it builds a
|
||||
/// [`doryen_fov`] transparency map (opaque cells from [`is_opaque_at`](Board::is_opaque_at)
|
||||
/// block sight), casts recursive shadow-casting from the player out to
|
||||
/// [`SIGHT_RADIUS`](crate::fov::SIGHT_RADIUS), and returns the resulting
|
||||
/// [`Visibility`] for the front-end to query per cell.
|
||||
pub fn player_fov(&self) -> Option<Visibility> {
|
||||
if !self.dark {
|
||||
return None;
|
||||
}
|
||||
// Seed every cell's transparency; MapData defaults to all-transparent, so
|
||||
// we only need to knock out the opaque ones — but set all to stay explicit.
|
||||
let mut map = MapData::new(self.width, self.height);
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
map.set_transparent(x, y, !self.is_opaque_at(x, y));
|
||||
}
|
||||
}
|
||||
// `light_walls = true` lights opaque cells at the edge of sight (the wall
|
||||
// you're looking *at* is visible), rather than only the open cells before it.
|
||||
let mut algo = FovRecursiveShadowCasting::new();
|
||||
algo.compute_fov(
|
||||
&mut map,
|
||||
self.player.x as usize,
|
||||
self.player.y as usize,
|
||||
SIGHT_RADIUS,
|
||||
true,
|
||||
);
|
||||
Some(Visibility::new(map))
|
||||
}
|
||||
|
||||
/// Whether the cell's single solid occupant (if any) can be pushed in `dir`.
|
||||
///
|
||||
/// Non-solid things are never pushable: `pushable` only matters for solids.
|
||||
@@ -720,6 +772,7 @@ pub(crate) mod tests {
|
||||
next_object_id,
|
||||
portals: Vec::new(),
|
||||
board_script_name: None,
|
||||
dark: false,
|
||||
load_errors: Vec::new(),
|
||||
registry: HashMap::new(),
|
||||
}
|
||||
@@ -1130,4 +1183,36 @@ pub(crate) mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_fov_none_when_not_dark() {
|
||||
// A lit board needs no FOV; front-ends draw every cell.
|
||||
let board = open_board(5, 1, (0, 0), vec![]);
|
||||
assert!(!board.dark);
|
||||
assert!(board.player_fov().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wall_is_opaque_empty_is_not() {
|
||||
let mut board = open_board(3, 1, (0, 0), vec![]);
|
||||
wall_at(&mut board, 1, 0);
|
||||
assert!(board.is_opaque_at(1, 0)); // wall blocks sight
|
||||
assert!(!board.is_opaque_at(2, 0)); // empty cell is transparent
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dark_board_hides_cells_behind_a_wall() {
|
||||
// Player at the left end of a 1-wide corridor; a wall at x=2 occludes
|
||||
// everything past it. Cells before the wall (and the wall itself) are
|
||||
// visible; the cell behind the wall is not.
|
||||
let mut board = open_board(5, 1, (0, 0), vec![]);
|
||||
board.dark = true;
|
||||
wall_at(&mut board, 2, 0);
|
||||
|
||||
let vis = board.player_fov().expect("dark board yields a Visibility");
|
||||
assert!(vis.is_visible(0, 0)); // the player's own cell
|
||||
assert!(vis.is_visible(1, 0)); // open cell before the wall
|
||||
assert!(vis.is_visible(2, 0)); // the wall itself (light_walls = true)
|
||||
assert!(!vis.is_visible(3, 0)); // occluded behind the wall
|
||||
assert!(!vis.is_visible(4, 0)); // occluded behind the wall
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user