Set up discovery system
This commit is contained in:
parent
98cea211fc
commit
763f4b7396
|
@ -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 {
|
||||||
|
|
63
src/level.rs
63
src/level.rs
|
@ -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 {
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue