diff --git a/src/components.rs b/src/components.rs index 082890a..03e1eba 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,7 +1,6 @@ +use rltk::RGB; use specs::prelude::*; use specs_derive::Component; -use rltk::{RGB}; - #[derive(Component)] pub struct Position { @@ -21,10 +20,21 @@ pub struct Player {} #[derive(Component)] pub struct Viewshed { - pub visible_tiles : Vec, - pub range : i32, - pub dirty : bool + pub visible_tiles: Vec, + pub range: i32, + pub dirty: bool, } #[derive(Component, Debug)] -pub struct Monster {} \ No newline at end of file +pub struct Monster {} + +#[derive(Component, Debug)] +pub struct Name { + pub name: String, +} + +#[derive(PartialEq, Copy, Clone)] +pub enum RunState { + Paused, + Running, +} diff --git a/src/main.rs b/src/main.rs index 94f5e77..c10f238 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use rltk::{GameState, Rltk, RGB}; +use rltk::{GameState, Point, Rltk, RGB}; use specs::prelude::*; mod components; @@ -16,14 +16,20 @@ pub use rect::Rect; mod visibility_system; pub use visibility_system::*; +mod monster_ai_system; +pub use monster_ai_system::*; + pub struct State { pub ecs: World, + pub runstate: RunState, } impl State { fn run_systems(&mut self) { - let mut vis = VisibilitySystem{}; + let mut vis = VisibilitySystem {}; vis.run_now(&self.ecs); + let mut mob = MonsterAI {}; + mob.run_now(&self.ecs); self.ecs.maintain(); } } @@ -32,8 +38,12 @@ impl GameState for State { fn tick(&mut self, ctx: &mut Rltk) { ctx.cls(); - player_input(self, ctx); - self.run_systems(); + if self.runstate == RunState::Running { + self.run_systems(); + self.runstate = RunState::Paused; + } else { + self.runstate = player_input(self, ctx); + } // let map = self.ecs.fetch::(); draw_map(&self.ecs, ctx); @@ -44,7 +54,9 @@ impl GameState for State { for (pos, render) in (&positions, &renderables).join() { let idx = map.xy_idx(pos.x, pos.y); - if map.visible_tiles[idx] {ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) } + if map.visible_tiles[idx] { + ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) + } } } } @@ -54,17 +66,21 @@ fn main() -> rltk::BError { let context = RltkBuilder::simple80x50() .with_title("Rougelike Tutorial") .build()?; - let mut gs = State { ecs: World::new() }; + let mut gs = State { + ecs: World::new(), + runstate: RunState::Running, + }; gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); let map = Map::new_map_rooms_and_corridors(); let (player_x, player_y) = map.rooms[0].center(); - + gs.ecs.insert(Point::new(player_x, player_y)); gs.ecs .create_entity() .with(Position { @@ -77,34 +93,55 @@ fn main() -> rltk::BError { bg: RGB::named(rltk::BLACK), }) .with(Player {}) - .with(Viewshed{ visible_tiles : Vec::new(), range : 8, dirty : true}) + .with(Viewshed { + visible_tiles: Vec::new(), + range: 8, + dirty: true, + }) + .with(Name { + name: "Player".to_string(), + }) .build(); let mut rng = rltk::RandomNumberGenerator::new(); - for room in map.rooms.iter().skip(1) { - let (x,y) = room.center(); + for (i, room) in map.rooms.iter().skip(1).enumerate() { + let (x, y) = room.center(); - let glyph : rltk::FontCharType; + let glyph: rltk::FontCharType; + let name: String; let roll = rng.roll_dice(1, 2); match roll { - 1 => { glyph = rltk::to_cp437('g') }, - _ => { glyph = rltk::to_cp437('o') } + 1 => { + glyph = rltk::to_cp437('g'); + name = "Goblin".to_string() + } + _ => { + glyph = rltk::to_cp437('o'); + name = "Orc".to_string() + } } - gs.ecs.create_entity() - .with(Position{ x, y }) - .with(Renderable{ + gs.ecs + .create_entity() + .with(Position { x, y }) + .with(Renderable { glyph: glyph, fg: RGB::named(rltk::RED), bg: RGB::named(rltk::BLACK), }) - .with(Viewshed{ visible_tiles : Vec::new(), range: 8, dirty: true}) + .with(Viewshed { + visible_tiles: Vec::new(), + range: 8, + dirty: true, + }) + .with(Monster {}) + .with(Name { + name: format!("{} #{}", &name, i), + }) .build(); - } gs.ecs.insert(map); // Main loop runner rltk::main_loop(context, gs) } - \ No newline at end of file diff --git a/src/map.rs b/src/map.rs index 20f9896..52c61e5 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,7 +1,7 @@ -use super::{Rect}; -use rltk::{RandomNumberGenerator, Rltk, RGB, Algorithm2D, Point, BaseMap}; -use std::{cmp::{max, min}}; +use super::Rect; +use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, SmallVec, RGB}; use specs::prelude::*; +use std::cmp::{max, min}; #[derive(PartialEq, Copy, Clone)] pub enum TileType { @@ -11,12 +11,12 @@ pub enum TileType { #[derive(Default)] pub struct Map { - pub tiles : Vec::, - pub rooms : Vec::, - pub width : i32, - pub height : i32, - pub revealed_tiles : Vec, - pub visible_tiles : Vec + pub tiles: Vec, + pub rooms: Vec, + pub width: i32, + pub height: i32, + pub revealed_tiles: Vec, + pub visible_tiles: Vec, } impl Map { @@ -32,7 +32,7 @@ impl Map { } } } - + fn apply_horizontal_tunnel(&mut self, x1: i32, x2: i32, y: i32) { for x in min(x1, x2)..=max(x1, x2) { let idx = self.xy_idx(x, y); @@ -41,7 +41,7 @@ impl Map { } } } - + fn apply_vertical_tunnel(&mut self, y1: i32, y2: i32, x: i32) { for y in min(y1, y2)..=max(y1, y2) { let idx = self.xy_idx(x, y); @@ -52,21 +52,21 @@ impl Map { } pub fn new_map_rooms_and_corridors() -> Map { - let mut map = Map{ - tiles : vec![TileType::Wall; 80*50], - rooms : Vec::new(), - width : 80, - height : 50, - revealed_tiles : vec![false; 80*50], - visible_tiles : vec![false; 80*50] + let mut map = Map { + tiles: vec![TileType::Wall; 80 * 50], + rooms: Vec::new(), + width: 80, + height: 50, + revealed_tiles: vec![false; 80 * 50], + visible_tiles: vec![false; 80 * 50], }; - + const MAX_ROOMS: i32 = 30; const MIN_SIZE: i32 = 6; const MAX_SIZE: i32 = 10; - + let mut rng = RandomNumberGenerator::new(); - + for _ in 0..MAX_ROOMS { let w = rng.range(MIN_SIZE, MAX_SIZE); let h = rng.range(MIN_SIZE, MAX_SIZE); @@ -95,10 +95,47 @@ impl Map { map.rooms.push(new_room); } } - + map } + pub fn is_exit_valid(&self, x: i32, y: i32) -> bool { + if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 { + return false; + } + let idx = self.xy_idx(x, y); + self.tiles[idx as usize] != TileType::Wall + } + + pub fn get_available_exits(&self, idx: usize) -> rltk::SmallVec<[(usize, f32); 10]> { + let mut exits = rltk::SmallVec::new(); + let x = idx as i32 % self.width; + let y = idx as i32 / self.width; + let w = self.width as usize; + + // cardinal directions + if self.is_exit_valid(x - 1, y) { + exits.push((idx - 1, 1.0)) + }; + if self.is_exit_valid(x + 1, y) { + exits.push((idx + 1, 1.0)) + }; + if self.is_exit_valid(x, y - 1) { + exits.push((idx - w, 1.0)) + }; + if self.is_exit_valid(x, y + 1) { + exits.push((idx + w, 1.0)) + }; + + exits + } + + pub fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 { + let w = self.width as usize; + let p1 = Point::new(idx1 % w, idx1 / w); + let p2 = Point::new(idx2 % w, idx2 / w); + rltk::DistanceAlg::Pythagoras.distance2d(p1, p2) + } } impl Algorithm2D for Map { @@ -140,10 +177,6 @@ impl BaseMap for Map { // map // } - - - - pub fn draw_map(ecs: &World, ctx: &mut Rltk) { let map = ecs.fetch::(); @@ -163,14 +196,15 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) { fg = RGB::from_f32(0., 1.0, 0.); } } - if !map.visible_tiles[idx] { fg = fg.to_greyscale() } + if !map.visible_tiles[idx] { + fg = fg.to_greyscale() + } ctx.set(x, y, fg, RGB::from_f32(0., 0., 0.), glyph); } x += 1; - if x > 79 { - x = 0; - y += 1; - } + if x > 79 { + x = 0; + y += 1; + } } - } diff --git a/src/monster_ai_system.rs b/src/monster_ai_system.rs new file mode 100644 index 0000000..146329d --- /dev/null +++ b/src/monster_ai_system.rs @@ -0,0 +1,41 @@ +use super::{Map, Monster, Name, Position, Viewshed}; +use rltk::{console, Point}; +use specs::prelude::*; + +pub struct MonsterAI {} + +impl<'a> System<'a> for MonsterAI { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteExpect<'a, Map>, + ReadExpect<'a, Point>, + WriteStorage<'a, Viewshed>, + ReadStorage<'a, Monster>, + ReadStorage<'a, Name>, + WriteStorage<'a, Position>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut map, player_pos, mut viewshed, monster, name, mut position) = data; + + for (mut viewshed, _monster, name, mut pos) in + (&mut viewshed, &monster, &name, &mut position).join() + { + if viewshed.visible_tiles.contains(&*player_pos) { + console::log(&format!("{} shouts insults at you", name.name)); + let path = rltk::a_star_search( + map.xy_idx(pos.x, pos.y) as i32, + map.xy_idx(player_pos.x, player_pos.y) as i32, + &mut *map, + ); + console::log(path.success); + if path.success && path.steps.len() > 1 { + pos.x = path.steps[1] as i32 % map.width; + pos.y = path.steps[1] as i32 / map.width; + console::log("here 2"); + viewshed.dirty = true; + } + } + } + } +} diff --git a/src/player.rs b/src/player.rs index 50e9379..2725078 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,12 +1,13 @@ -use rltk::{VirtualKeyCode, Rltk}; +use super::{Map, Player, Position, RunState, State, TileType, Viewshed}; +use rltk::{Point, Rltk, VirtualKeyCode}; use specs::prelude::*; -use super::{Position, Player, TileType, Map, State, Viewshed}; -use std::cmp::{min, max}; +use std::cmp::{max, min}; pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { let mut positions = ecs.write_storage::(); let mut players = ecs.write_storage::(); let mut viewsheds = ecs.write_storage::(); + let mut ppos = ecs.write_resource::(); let map = ecs.fetch::(); for (_player, pos, viewshed) in (&mut players, &mut positions, &mut viewsheds).join() { @@ -14,32 +15,34 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { if map.tiles[destination_idx] != TileType::Wall { pos.x = min(79, max(0, pos.x + delta_x)); pos.y = min(49, max(0, pos.y + delta_y)); - + ppos.x = pos.x; + ppos.y = pos.y; viewshed.dirty = true } } } -pub fn player_input(gs: &mut State, ctx: &mut Rltk) { +pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { match ctx.key { - None => {} + None => return RunState::Paused, Some(key) => match key { - VirtualKeyCode::Left | - VirtualKeyCode::Numpad4 | - VirtualKeyCode::H => try_move_player(-1, 0, &mut gs.ecs), + VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => { + try_move_player(-1, 0, &mut gs.ecs) + } - VirtualKeyCode::Right | - VirtualKeyCode::Numpad6 | - VirtualKeyCode::L => try_move_player(1, 0, &mut gs.ecs), + VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => { + try_move_player(1, 0, &mut gs.ecs) + } - VirtualKeyCode::Up | - VirtualKeyCode::Numpad8 | - VirtualKeyCode::K => try_move_player(0, -1, &mut gs.ecs), + VirtualKeyCode::Up | VirtualKeyCode::Numpad8 | VirtualKeyCode::K => { + try_move_player(0, -1, &mut gs.ecs) + } - VirtualKeyCode::Down | - VirtualKeyCode::Numpad2 | - VirtualKeyCode::J => try_move_player(0, 1, &mut gs.ecs), - _ => {} + VirtualKeyCode::Down | VirtualKeyCode::Numpad2 | VirtualKeyCode::J => { + try_move_player(0, 1, &mut gs.ecs) + } + _ => return RunState::Paused, }, } -} \ No newline at end of file + RunState::Running +}