diff --git a/src/components.rs b/src/components.rs index a595a11..9ba7e95 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,4 +1,5 @@ pub use crate::prelude::*; +use std::collections::HashSet; #[derive(Clone, Copy, Debug, PartialEq)] pub struct Render { @@ -44,3 +45,27 @@ pub struct Item; #[derive(Clone, Copy, Debug, PartialEq)] pub struct AmuletOfYala; + +#[derive(Clone, Debug, PartialEq)] +pub struct FieldOfView { + pub visible_tiles: HashSet, + pub radius: i32, + pub is_dirty: bool, +} + +impl FieldOfView { + pub fn new(radius: i32) -> Self { + Self { + visible_tiles: HashSet::new(), + radius, + is_dirty: true, + } + } + pub fn clone_dirty(&self) -> Self { + Self { + visible_tiles: HashSet::new(), + radius: self.radius, + is_dirty: true, + } + } +} diff --git a/src/main.rs b/src/main.rs index e0e686d..a6b4073 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,9 +54,9 @@ impl State { State { ecs, resources, - input_systems: input_build_scheduler(), - player_systems: player_build_scheduler(), - monster_systems: monster_build_scheduler(), + input_systems: build_input_scheduler(), + player_systems: build_player_scheduler(), + monster_systems: build_monster_scheduler(), } } diff --git a/src/map.rs b/src/map.rs index 2031870..704a938 100644 --- a/src/map.rs +++ b/src/map.rs @@ -9,6 +9,7 @@ pub enum TileType { pub struct Map { pub tiles: Vec, + pub revealed_tiles: Vec, } pub fn map_idx(x: i32, y: i32) -> usize { @@ -19,6 +20,7 @@ impl Map { pub fn new() -> Self { Self { tiles: vec![TileType::Floor; NUM_TILES], + revealed_tiles: vec![false; NUM_TILES], } } @@ -62,6 +64,9 @@ impl Algorithm2D for Map { } impl BaseMap for Map { + fn is_opaque(&self, idx: usize) -> bool { + self.tiles[idx as usize] != TileType::Floor + } fn get_available_exits(&self, idx: usize) -> SmallVec<[(usize, f32); 10]> { let mut exits = SmallVec::new(); let location = self.index_to_point2d(idx); diff --git a/src/spawners.rs b/src/spawners.rs index c28f48a..563aa98 100644 --- a/src/spawners.rs +++ b/src/spawners.rs @@ -9,9 +9,10 @@ pub fn spawn_player(ecs: &mut World, pos: Point) { glyph: to_cp437('@'), }, Health { - current: 20, - max: 20, + current: 10, + max: 10, }, + FieldOfView::new(8), )); } @@ -34,6 +35,7 @@ pub fn spawn_monster(ecs: &mut World, rng: &mut RandomNumberGenerator, pos: Poin max: hp, }, Name(name), + FieldOfView::new(6), )); } diff --git a/src/systems/chasing.rs b/src/systems/chasing.rs index 4ac52b1..d172928 100644 --- a/src/systems/chasing.rs +++ b/src/systems/chasing.rs @@ -3,10 +3,11 @@ use crate::prelude::*; #[system] #[read_component(Point)] #[read_component(ChasingPlayer)] +#[read_component(FieldOfView)] #[read_component(Health)] #[read_component(Player)] pub fn chasing(#[resource] map: &Map, ecs: &SubWorld, commands: &mut CommandBuffer) { - let mut movers = <(Entity, &Point, &ChasingPlayer)>::query(); + let mut movers = <(Entity, &Point, &ChasingPlayer, &FieldOfView)>::query(); let mut positions = <(Entity, &Point, &Health)>::query(); let mut player = <(&Point, &Player)>::query(); @@ -16,7 +17,10 @@ pub fn chasing(#[resource] map: &Map, ecs: &SubWorld, commands: &mut CommandBuff let search_targets = vec![player_idx]; let dijkstra_map = DijkstraMap::new(SCREEN_WIDTH, SCREEN_HEIGHT, &search_targets, map, 1024.0); - movers.iter(ecs).for_each(|(entity, pos, _)| { + movers.iter(ecs).for_each(|(entity, pos, _, fov)| { + if !fov.visible_tiles.contains(&player_pos) { + return; + } let idx = map_idx(pos.x, pos.y); if let Some(destination) = DijkstraMap::find_lowest_exit(&dijkstra_map, idx, map) { let distance = DistanceAlg::Pythagoras.distance2d(*pos, *player_pos); diff --git a/src/systems/entity_render.rs b/src/systems/entity_render.rs index 2b81c83..b2b11e4 100644 --- a/src/systems/entity_render.rs +++ b/src/systems/entity_render.rs @@ -3,13 +3,20 @@ use crate::prelude::*; #[system] #[read_component(Point)] #[read_component(Render)] +#[read_component(Player)] +#[read_component(FieldOfView)] pub fn entity_render(ecs: &mut SubWorld, #[resource] camera: &mut Camera) { + let mut renderables = <(&Point, &Render)>::query(); + let mut fov = <&FieldOfView>::query().filter(component::()); + let player_fov = fov.iter(ecs).nth(0).unwrap(); + let mut draw_batch = DrawBatch::new(); draw_batch.target(1); let offset = Point::new(camera.left_x, camera.top_y); - <(&Point, &Render)>::query() + renderables .iter(ecs) + .filter(|(pos, _)| player_fov.visible_tiles.contains(&pos)) .for_each(|(pos, render)| { draw_batch.set(*pos - offset, render.color, render.glyph); }); diff --git a/src/systems/fov.rs b/src/systems/fov.rs new file mode 100644 index 0000000..4b5bc5d --- /dev/null +++ b/src/systems/fov.rs @@ -0,0 +1,15 @@ +use crate::prelude::*; + +#[system] +#[read_component(Point)] +#[write_component(FieldOfView)] +pub fn fov(ecs: &mut SubWorld, #[resource] map: &Map) { + let mut views = <(&Point, &mut FieldOfView)>::query(); + views + .iter_mut(ecs) + .filter(|(_, fov)| fov.is_dirty) + .for_each(|(pos, mut fov)| { + fov.visible_tiles = field_of_view_set(*pos, fov.radius, map); + fov.is_dirty = true; + }); +} diff --git a/src/systems/map_render.rs b/src/systems/map_render.rs index 9bb0b43..4d0e84f 100644 --- a/src/systems/map_render.rs +++ b/src/systems/map_render.rs @@ -1,20 +1,34 @@ use crate::prelude::*; #[system] -pub fn map_render(#[resource] map: &Map, #[resource] camera: &Camera) { +#[read_component(Player)] +#[read_component(FieldOfView)] +pub fn map_render(ecs: &SubWorld, #[resource] map: &Map, #[resource] camera: &Camera) { + let mut fov = <&FieldOfView>::query().filter(component::()); + let player_fov = fov.iter(ecs).nth(0).unwrap(); let mut draw_batch = DrawBatch::new(); draw_batch.target(0); for y in camera.top_y..=camera.bottom_y { for x in camera.left_x..=camera.right_x { let pt = Point::new(x, y); let offset = Point::new(camera.left_x, camera.top_y); - if map.in_bounds(pt) { - let idx = map_idx(x, y); - let glyph = match map.tiles[idx] { - TileType::Floor => to_cp437('.'), - TileType::Wall => to_cp437('#'), + let idx = map_idx(x, y); + if map.in_bounds(pt) + && (player_fov.visible_tiles.contains(&pt) | map.revealed_tiles[idx]) + { + let tint = if player_fov.visible_tiles.contains(&pt) { + WHITE + } else { + DARK_GRAY }; - draw_batch.set(pt - offset, ColorPair::new(WHITE, BLACK), glyph); + match map.tiles[idx] { + TileType::Floor => { + draw_batch.set(pt - offset, ColorPair::new(tint, BLACK), to_cp437('.')); + } + TileType::Wall => { + draw_batch.set(pt - offset, ColorPair::new(tint, BLACK), to_cp437('#')); + } + } } } } diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 705ba1a..64d7c52 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -3,6 +3,7 @@ mod chasing; mod combat; mod end_turn; mod entity_render; +mod fov; mod hud; mod map_render; mod movement; @@ -10,9 +11,10 @@ mod player_input; mod random_move; mod tooltips; -pub fn input_build_scheduler() -> Schedule { +pub fn build_input_scheduler() -> Schedule { Schedule::builder() .add_system(player_input::player_input_system()) + .add_system(fov::fov_system()) .flush() .add_system(map_render::map_render_system()) .add_system(entity_render::entity_render_system()) @@ -20,23 +22,27 @@ pub fn input_build_scheduler() -> Schedule { .add_system(tooltips::tooltips_system()) .build() } -pub fn player_build_scheduler() -> Schedule { +pub fn build_player_scheduler() -> Schedule { Schedule::builder() .add_system(combat::combat_system()) .flush() .add_system(movement::movement_system()) .flush() + .add_system(fov::fov_system()) + .flush() .add_system(map_render::map_render_system()) .add_system(entity_render::entity_render_system()) .add_system(hud::hud_system()) .add_system(end_turn::end_turn_system()) .build() } -pub fn monster_build_scheduler() -> Schedule { +pub fn build_monster_scheduler() -> Schedule { Schedule::builder() .add_system(random_move::random_move_system()) .add_system(chasing::chasing_system()) .flush() + .add_system(fov::fov_system()) + .flush() .add_system(combat::combat_system()) .flush() .add_system(movement::movement_system()) diff --git a/src/systems/movement.rs b/src/systems/movement.rs index b4fdaaf..07a34c2 100644 --- a/src/systems/movement.rs +++ b/src/systems/movement.rs @@ -2,23 +2,29 @@ pub use crate::prelude::*; #[system(for_each)] #[read_component(Player)] +#[read_component(FieldOfView)] pub fn movement( entity: &Entity, want_move: &WantsToMove, - #[resource] map: &Map, + #[resource] map: &mut Map, #[resource] camera: &mut Camera, ecs: &mut SubWorld, commands: &mut CommandBuffer, ) { if map.can_enter_tile(want_move.destination) { commands.add_component(want_move.entity, want_move.destination); - if ecs - .entry_ref(want_move.entity) - .unwrap() - .get_component::() - .is_ok() - { - camera.on_player_move(want_move.destination); + + if let Ok(entry) = ecs.entry_ref(want_move.entity) { + if let Ok(fov) = entry.get_component::() { + commands.add_component(want_move.entity, fov.clone_dirty()); + + if entry.get_component::().is_ok() { + camera.on_player_move(want_move.destination); + fov.visible_tiles.iter().for_each(|pos| { + map.revealed_tiles[map_idx(pos.x, pos.y)] = true; + }); + } + } } } commands.remove(*entity); diff --git a/src/systems/tooltips.rs b/src/systems/tooltips.rs index 93d85e3..d6941fd 100644 --- a/src/systems/tooltips.rs +++ b/src/systems/tooltips.rs @@ -4,11 +4,16 @@ use crate::prelude::*; #[read_component(Point)] #[read_component(Name)] #[read_component(Health)] +#[read_component(Player)] +#[read_component(FieldOfView)] pub fn tooltips( ecs: &SubWorld, #[resource] mouse_pos: &Point, // (1) #[resource] camera: &Camera, // (2) ) { + let mut fov = <&FieldOfView>::query().filter(component::()); + let player_fov = fov.iter(ecs).nth(0).unwrap(); + let mut positions = <(Entity, &Point, &Name)>::query(); // (3) let offset = Point::new(camera.left_x, camera.top_y); let map_pos = *mouse_pos + offset; // (4) @@ -16,7 +21,7 @@ pub fn tooltips( draw_batch.target(2); positions .iter(ecs) - .filter(|(_, pos, _)| **pos == map_pos) // (5) + .filter(|(_, pos, _)| **pos == map_pos && player_fov.visible_tiles.contains(&pos)) // (5) .for_each(|(entity, _, name)| { let screen_pos = *mouse_pos * 4; // (6) let display = if let Ok(health) = ecs