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,
}
/// Entities that the user can control using the keyboard.
/// Entities that users can control.
#[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.
#[derive(Component)]
@ -46,6 +49,18 @@ pub fn register_all(world: &mut World) {
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.
#[derive(Clone, Copy)]
pub enum MobAction {

View file

@ -4,7 +4,12 @@ use pancurses::Window;
use rand::Rng;
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.
pub const LEVEL_SIZE: (usize, usize) = (80, 24);
@ -39,6 +44,23 @@ pub enum DungeonTile {
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 {
/// Whether this tile is considered a floor tile, for the purposes
/// of rendering walls.
@ -99,20 +121,22 @@ impl DungeonLevel {
/// Draws a level on the display window. Draws only the cells for
/// which `filter` returns true; use `|_| true` to draw the whole
/// 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 {
win.mv(y as _, 0);
for x in 0..LEVEL_SIZE.0 {
if (x + y) % 2 == 0 {
set_color(win, Color::Yellow);
} else {
set_color(win, Color::Red);
}
win.addch(if filter((x as _, y as _)) {
self.render_tile(x, y)
} else {
' '
win.addch(match visibility((x as _, y as _)) {
DrawStyle::Undiscovered => ' ',
DrawStyle::Discovered => {
// Using red as a placeholder; black doesn't
// seem to work rn(?)
set_color(win, Color::Red);
self.render_tile(x, y)
}
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 {
&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 {

View file

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

View file

@ -5,9 +5,8 @@ use specs::prelude::*;
use crate::{
components::{CharRender, MobAction, Mobile, Player, Position},
level::DungeonLevel,
io::quit,
visibility::{visible, CellVisibility, Lighting},
level::{DrawStyle, DungeonLevel},
};
/// 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.
let level = ecs.fetch::<DungeonLevel>();
let known_cells = &plrs.join().next().expect("Player must exist").known_cells;
level.draw(screen, |cell| {
visible(
(player_pos.x, player_pos.y),
cell,
Some(10),
|(x, y)| {
if level.tile(x, y).is_navigable() {
CellVisibility::Transparent
match level.can_see(player_pos.into(), cell) {
true => DrawStyle::Visible,
false => {
if known_cells[cell.1 as usize][cell.0 as usize] {
DrawStyle::Discovered
} else {
CellVisibility::Blocking
DrawStyle::Undiscovered
}
},
// Level is fully lit for now.
|(_x, _y)| Lighting::Lit,
)
}
}
});
// Draw all renderable entities.

View file

@ -2,7 +2,10 @@
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
/// 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.
#[derive(Debug, PartialEq)]
pub enum Lighting {
/// Monsters can only see in this cell if the cell is immediately
/// adjacent to the monster.
Dark,
// /// Monsters can only see in this cell if the cell is immediately
// /// adjacent to the monster.
// Dark,
/// Monsters can see in this cell from far away.
Lit,
}