Set up discovery system

This commit is contained in:
Alex Bethel 2022-03-27 16:56:23 -06:00
parent 98cea211fc
commit 763f4b7396
6 changed files with 122 additions and 41 deletions

View file

@ -16,9 +16,12 @@ pub struct CharRender {
pub glyph: char, pub glyph: char,
} }
/// Entities that the user can control using the keyboard. /// Entities that users can control.
#[derive(Component)] #[derive(Component)]
pub struct Player; pub struct Player {
/// The list of cells that are known to the player.
pub known_cells: Vec<Vec<bool>>,
}
/// Entities that take turns periodically. /// Entities that take turns periodically.
#[derive(Component)] #[derive(Component)]
@ -46,6 +49,18 @@ pub fn register_all(world: &mut World) {
world.register::<Mobile>(); world.register::<Mobile>();
} }
impl From<&Position> for (i32, i32) {
fn from(pos: &Position) -> Self {
(pos.x, pos.y)
}
}
impl From<(i32, i32)> for Position {
fn from((x, y): (i32, i32)) -> Self {
Self { x, y }
}
}
/// An action that a mob can perform that takes up a turn. /// An action that a mob can perform that takes up a turn.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum MobAction { pub enum MobAction {

View file

@ -4,7 +4,12 @@ use pancurses::Window;
use rand::Rng; use rand::Rng;
use specs::prelude::*; use specs::prelude::*;
use crate::{components::{CharRender, Position}, io::{Color, set_color}, rooms}; use crate::{
components::{CharRender, Position},
io::{set_color, Color},
rooms,
visibility::{visible, CellVisibility, Lighting},
};
/// The size of a dungeon level, in tiles. /// The size of a dungeon level, in tiles.
pub const LEVEL_SIZE: (usize, usize) = (80, 24); pub const LEVEL_SIZE: (usize, usize) = (80, 24);
@ -39,6 +44,23 @@ pub enum DungeonTile {
Downstair, Downstair,
} }
/// A style for drawing a particular tile in the dungeon.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DrawStyle {
/// Don't draw the tile. (The player has never seen this tile
/// before, and is unaware of its contents.)
Undiscovered,
/// Draw the tile in a darker color than normal. (The player has
/// seen this tile before and remembers its contents, but is not
/// actively looking at it.)
Discovered,
/// Draw the tile in a normal color. (The player can see the tile
/// from where they are standing.)
Visible,
}
impl DungeonTile { impl DungeonTile {
/// Whether this tile is considered a floor tile, for the purposes /// Whether this tile is considered a floor tile, for the purposes
/// of rendering walls. /// of rendering walls.
@ -99,20 +121,22 @@ impl DungeonLevel {
/// Draws a level on the display window. Draws only the cells for /// Draws a level on the display window. Draws only the cells for
/// which `filter` returns true; use `|_| true` to draw the whole /// which `filter` returns true; use `|_| true` to draw the whole
/// level. /// level.
pub fn draw(&self, win: &Window, filter: impl Fn((i32, i32)) -> bool) { pub fn draw(&self, win: &Window, visibility: impl Fn((i32, i32)) -> DrawStyle) {
for y in 0..LEVEL_SIZE.1 { for y in 0..LEVEL_SIZE.1 {
win.mv(y as _, 0); win.mv(y as _, 0);
for x in 0..LEVEL_SIZE.0 { for x in 0..LEVEL_SIZE.0 {
if (x + y) % 2 == 0 { win.addch(match visibility((x as _, y as _)) {
set_color(win, Color::Yellow); DrawStyle::Undiscovered => ' ',
} else { DrawStyle::Discovered => {
// Using red as a placeholder; black doesn't
// seem to work rn(?)
set_color(win, Color::Red); set_color(win, Color::Red);
}
win.addch(if filter((x as _, y as _)) {
self.render_tile(x, y) self.render_tile(x, y)
} else { }
' ' DrawStyle::Visible => {
set_color(win, Color::White);
self.render_tile(x, y)
}
}); });
} }
} }
@ -168,6 +192,25 @@ impl DungeonLevel {
pub fn tile(&self, x: i32, y: i32) -> &DungeonTile { pub fn tile(&self, x: i32, y: i32) -> &DungeonTile {
&self.tiles[y as usize][x as usize] &self.tiles[y as usize][x as usize]
} }
/// Whether a monster standing at `from` can see the contents of cell
/// `to`.
pub fn can_see(&self, from: (i32, i32), to: (i32, i32)) -> bool {
visible(
from,
to,
Some(10),
|(x, y)| {
if self.tile(x, y).is_navigable() {
CellVisibility::Transparent
} else {
CellVisibility::Blocking
}
},
// Level is fully lit for now.
|(_x, _y)| Lighting::Lit,
)
}
} }
impl Display for DungeonLevel { impl Display for DungeonLevel {

View file

@ -1,11 +1,11 @@
use components::{register_all, CharRender, MobAction, Mobile, Player, Position, TurnTaker}; use components::{register_all, CharRender, MobAction, Mobile, Player, Position, TurnTaker};
use io::init_window; use io::init_window;
use level::DungeonLevel; use level::{DungeonLevel, LEVEL_SIZE};
use player::player_turn; use player::player_turn;
use rand::thread_rng; use rand::thread_rng;
use specs::prelude::*; use specs::prelude::*;
use systems::{MobSystem, TimeSystem}; use systems::{DiscoverySystem, MobSystem, TimeSystem};
mod components; mod components;
mod io; mod io;
@ -28,12 +28,13 @@ fn main() {
world world
.create_entity() .create_entity()
.with(Position { .with(Position::from(spawn_pos))
x: spawn_pos.0,
y: spawn_pos.1,
})
.with(CharRender { glyph: '@' }) .with(CharRender { glyph: '@' })
.with(Player) .with(Player {
known_cells: (0..LEVEL_SIZE.1)
.map(|_| (0..LEVEL_SIZE.0).map(|_| false).collect())
.collect(),
})
.with(Mobile { .with(Mobile {
next_action: MobAction::Nop, next_action: MobAction::Nop,
}) })
@ -46,6 +47,7 @@ fn main() {
let mut dispatcher = DispatcherBuilder::new() let mut dispatcher = DispatcherBuilder::new()
.with(TimeSystem, "time", &[]) .with(TimeSystem, "time", &[])
.with(MobSystem, "mobs", &[]) .with(MobSystem, "mobs", &[])
.with(DiscoverySystem, "discovery", &[])
.build(); .build();
let mut window = match init_window() { let mut window = match init_window() {
@ -53,7 +55,7 @@ fn main() {
Err(err) => { Err(err) => {
println!("Error initializing window: {}", err); println!("Error initializing window: {}", err);
return; return;
}, }
}; };
loop { loop {

View file

@ -5,9 +5,8 @@ use specs::prelude::*;
use crate::{ use crate::{
components::{CharRender, MobAction, Mobile, Player, Position}, components::{CharRender, MobAction, Mobile, Player, Position},
level::DungeonLevel,
io::quit, io::quit,
visibility::{visible, CellVisibility, Lighting}, level::{DrawStyle, DungeonLevel},
}; };
/// Runs a player turn on the ECS, using the given `screen` for input /// Runs a player turn on the ECS, using the given `screen` for input
@ -96,21 +95,18 @@ fn render_screen(ecs: &mut World, screen: &mut Window) {
// Draw the base level. // Draw the base level.
let level = ecs.fetch::<DungeonLevel>(); let level = ecs.fetch::<DungeonLevel>();
let known_cells = &plrs.join().next().expect("Player must exist").known_cells;
level.draw(screen, |cell| { level.draw(screen, |cell| {
visible( match level.can_see(player_pos.into(), cell) {
(player_pos.x, player_pos.y), true => DrawStyle::Visible,
cell, false => {
Some(10), if known_cells[cell.1 as usize][cell.0 as usize] {
|(x, y)| { DrawStyle::Discovered
if level.tile(x, y).is_navigable() {
CellVisibility::Transparent
} else { } else {
CellVisibility::Blocking DrawStyle::Undiscovered
}
}
} }
},
// Level is fully lit for now.
|(_x, _y)| Lighting::Lit,
)
}); });
// Draw all renderable entities. // Draw all renderable entities.

View file

@ -2,7 +2,10 @@
use specs::prelude::*; use specs::prelude::*;
use crate::components::{MobAction, Mobile, Position, TurnTaker}; use crate::{
components::{MobAction, Mobile, Player, Position, TurnTaker},
level::DungeonLevel,
};
/// System for ticking the turn counter on every entity; this system /// System for ticking the turn counter on every entity; this system
/// implements the relationship between real-world time and in-game /// implements the relationship between real-world time and in-game
@ -46,3 +49,26 @@ impl<'a> System<'a> for MobSystem {
} }
} }
} }
/// System for updating player-discovered cells.
pub struct DiscoverySystem;
impl<'a> System<'a> for DiscoverySystem {
type SystemData = (
WriteStorage<'a, Player>,
ReadStorage<'a, Position>,
ReadExpect<'a, DungeonLevel>,
);
fn run(&mut self, (mut players, position, level): Self::SystemData) {
for (player, pos) in (&mut players, &position).join() {
for (y, row) in player.known_cells.iter_mut().enumerate() {
for (x, known) in row.iter_mut().enumerate() {
if level.can_see(pos.into(), (x as _, y as _)) {
*known = true;
}
}
}
}
}
}

View file

@ -14,10 +14,9 @@ pub enum CellVisibility {
/// How well-lit a cell is. /// How well-lit a cell is.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Lighting { pub enum Lighting {
/// Monsters can only see in this cell if the cell is immediately // /// Monsters can only see in this cell if the cell is immediately
/// adjacent to the monster. // /// adjacent to the monster.
Dark, // Dark,
/// Monsters can see in this cell from far away. /// Monsters can see in this cell from far away.
Lit, Lit,
} }