From 419e16fb4dab72b4c1514028e82b9aa161134bdd Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Sun, 13 Nov 2022 22:33:10 +1100 Subject: [PATCH] Finished chapter 2.8 --- src/components.rs | 40 +++++++++++++++-- src/damage_system.rs | 52 ++++++++++++++++++++++ src/gamelog.rs | 3 ++ src/gui.rs | 81 +++++++++++++++++++++++++++++++++++ src/main.rs | 79 ++++++++++++++++++++++++++++------ src/map.rs | 88 ++++++++++++++++++++++++++------------ src/map_indexing_system.rs | 29 +++++++++++++ src/melee_combat_system.rs | 39 +++++++++++++++++ src/monster_ai_system.rs | 49 +++++++++++++-------- src/player.rs | 32 +++++++++++--- 10 files changed, 424 insertions(+), 68 deletions(-) create mode 100644 src/damage_system.rs create mode 100644 src/gamelog.rs create mode 100644 src/gui.rs create mode 100644 src/map_indexing_system.rs create mode 100644 src/melee_combat_system.rs diff --git a/src/components.rs b/src/components.rs index 03e1eba..6285064 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,6 +1,6 @@ use rltk::RGB; use specs::prelude::*; -use specs_derive::Component; +use specs_derive::*; #[derive(Component)] pub struct Position { @@ -35,6 +35,40 @@ pub struct Name { #[derive(PartialEq, Copy, Clone)] pub enum RunState { - Paused, - Running, + AwaitingInput, + PreRun, + PlayerTurn, + MonsterTurn } + +#[derive(Component, Debug)] +pub struct BlocksTile {} + +#[derive(Component, Debug)] +pub struct CombatStats { + pub max_hp : i32, + pub hp : i32, + pub defence : i32, + pub power : i32 +} + +#[derive(Component, Debug, Clone)] +pub struct WantsToMelee { + pub target: Entity +} + +#[derive(Component, Debug)] +pub struct SufferDamage { + pub amount : Vec +} + +impl SufferDamage { + pub fn new_damage(store: &mut WriteStorage, victim: Entity, amount: i32) { + if let Some(sufferring) = store.get_mut(victim) { + sufferring.amount.push(amount); + } else { + let dmg = SufferDamage { amount : vec![amount] }; + store.insert(victim, dmg).expect("Unable to insert damage"); + } + } +} \ No newline at end of file diff --git a/src/damage_system.rs b/src/damage_system.rs new file mode 100644 index 0000000..9bf6fb7 --- /dev/null +++ b/src/damage_system.rs @@ -0,0 +1,52 @@ +use specs::prelude::*; +use super::{CombatStats, SufferDamage, Player, GameLog, Name}; +use rltk::console; + +pub struct DamageSystem {} + +impl<'a> System <'a> for DamageSystem { + type SystemData = ( WriteStorage<'a, CombatStats>, + WriteStorage<'a, SufferDamage> ); + + fn run(&mut self, data : Self::SystemData) { + let (mut stats, mut damage) = data; + + for (mut stats, damage) in (&mut stats, &damage).join() { + stats.hp -= damage.amount.iter().sum::(); + } + damage.clear() + } + + +} + +pub fn delete_the_dead(ecs : &mut World) { + let mut dead : Vec = Vec::new(); + // Using a scope to mak ethe borrow checker happy + { + let combat_stats = ecs.read_storage::(); + let players = ecs.read_storage::(); + let names = ecs.write_storage::(); + let entities = ecs.entities(); + let mut log = ecs.write_resource::(); + for (entity, stats) in (&entities, &combat_stats).join() { + if stats.hp < 1 { + let player = players.get(entity); + match player { + None => { + let victim_name = names.get(entity); + if let Some(victim_name) = victim_name { + log.entries.push(format!("{} is dead", &victim_name.name)); + } + dead.push(entity) + }, + Some(_) => console::log("You are dead") + } + } + } + } + + for victim in dead { + ecs.delete_entity(victim).expect("Unable to delete"); + } +} \ No newline at end of file diff --git a/src/gamelog.rs b/src/gamelog.rs new file mode 100644 index 0000000..7b598dd --- /dev/null +++ b/src/gamelog.rs @@ -0,0 +1,3 @@ +pub struct GameLog { + pub entries : Vec +} \ No newline at end of file diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 0000000..fbd7040 --- /dev/null +++ b/src/gui.rs @@ -0,0 +1,81 @@ +use rltk::{RGB, Rltk, Console, Point}; +use specs::prelude::*; +use super::{CombatStats, Player, GameLog, Map, Name, Position}; + +fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { + let map = ecs.fetch::(); + let names = ecs.read_storage::(); + let positions = ecs.read_storage::(); + + let mouse_pos = ctx.mouse_pos(); + if mouse_pos.0 >= map.width || mouse_pos.1 >= map.height { return; } + let mut tooltip : Vec = Vec::new(); + for (name, position) in (&names, &positions).join() { + let idx = map.xy_idx(position.x, position.y); + if position.x == mouse_pos.0 && position.y == mouse_pos.1 && map.visible_tiles[idx] { + tooltip.push(name.name.to_string()); + } + } + + if !tooltip.is_empty() { + let mut width : i32 = 0; + for s in tooltip.iter() { + if width < s.len() as i32 { width = s.len() as i32; } + } + width += 3; + + if mouse_pos.0 > 40 { + let arrow_pos = Point::new(mouse_pos.0 -2, mouse_pos.1); + let left_x = mouse_pos.0 - width; + let mut y = mouse_pos.1; + for s in tooltip.iter() { + ctx.print_color(left_x, y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), s); + let padding = (width - s.len() as i32) - 1; + for i in 0..padding { + ctx.print_color(arrow_pos.x - i, y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), &" ".to_string()); + } + y += 1 + } + ctx.print_color(arrow_pos.x, arrow_pos.y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), &"->".to_string()); + } else { + let arrow_pos = Point::new(mouse_pos.0 + 1, mouse_pos.1); + let left_x = mouse_pos.0 + 3; + let mut y = mouse_pos.1; + for s in tooltip.iter() { + ctx.print_color(left_x + 1, y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), s); + let padding = (width - s.len() as i32) - 1; + for i in 0..padding { + ctx.print_color(arrow_pos.x + 1 + i, y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), &" ".to_string()); + + } + y += 1; + } + ctx.print_color(arrow_pos.x, arrow_pos.y, RGB::named(rltk::WHITE), RGB::named(rltk::GREY), &"<-".to_string()); + } + } + +} + +pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { + ctx.draw_box(0, 43, 79, 6, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK)); + + let combat_stats = ecs.read_storage::(); + let players = ecs.read_storage::(); + for (_player, stats) in (&players, &combat_stats).join() { + let health = format!(" HP: {} / {}", stats.hp, stats.max_hp); + ctx.print_color(12, 43, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), &health); + + ctx.draw_bar_horizontal(28, 43, 51, stats.hp, stats.max_hp, RGB::named(rltk::RED), RGB::named(rltk::BLACK)); + } + let log = ecs.fetch::(); + + let mut y = 44; + for s in log.entries.iter().rev() { + if y < 49 { ctx.print(2, y, s); } + y += 1; + } + + let mouse_pos = ctx.mouse_pos(); + ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::MAGENTA)); + draw_tooltips(ecs, ctx); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c10f238..52771c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,23 @@ pub use visibility_system::*; mod monster_ai_system; pub use monster_ai_system::*; +mod map_indexing_system; +pub use map_indexing_system::*; + +mod melee_combat_system; +pub use melee_combat_system::*; + +mod damage_system; +pub use damage_system::*; + +mod gui; +pub use gui::*; + +mod gamelog; +pub use gamelog::*; + pub struct State { - pub ecs: World, - pub runstate: RunState, + pub ecs: World } impl State { @@ -30,6 +44,12 @@ impl State { vis.run_now(&self.ecs); let mut mob = MonsterAI {}; mob.run_now(&self.ecs); + let mut mapindex = MapIndexingSystem{}; + mapindex.run_now(&self.ecs); + let mut melee_combat_system = MeleeCombatSystem{}; + melee_combat_system.run_now(&self.ecs); + let mut damage_system = DamageSystem{}; + damage_system.run_now(&self.ecs); self.ecs.maintain(); } } @@ -37,15 +57,36 @@ impl State { impl GameState for State { fn tick(&mut self, ctx: &mut Rltk) { ctx.cls(); - - if self.runstate == RunState::Running { - self.run_systems(); - self.runstate = RunState::Paused; - } else { - self.runstate = player_input(self, ctx); + let mut newrunstate; + { + let runstate = self.ecs.fetch::(); + newrunstate = *runstate; } - // let map = self.ecs.fetch::(); + match newrunstate { + RunState::PreRun => { + self.run_systems(); + newrunstate = RunState::AwaitingInput; + } + RunState::AwaitingInput => { + newrunstate = player_input(self, ctx); + } + RunState::PlayerTurn => { + self.run_systems(); + newrunstate = RunState::MonsterTurn; + } + RunState::MonsterTurn => { + self.run_systems(); + newrunstate = RunState::AwaitingInput; + } + } + + { + let mut runwriter = self.ecs.write_resource::(); + *runwriter = newrunstate; + } + + damage_system::delete_the_dead(&mut self.ecs); draw_map(&self.ecs, ctx); let positions = self.ecs.read_storage::(); @@ -58,17 +99,18 @@ impl GameState for State { ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) } } + gui::draw_ui(&self.ecs, ctx); } } fn main() -> rltk::BError { use rltk::RltkBuilder; - let context = RltkBuilder::simple80x50() + let mut context = RltkBuilder::simple80x50() .with_title("Rougelike Tutorial") .build()?; + context.with_post_scanlines(true); let mut gs = State { - ecs: World::new(), - runstate: RunState::Running, + ecs: World::new() }; gs.ecs.register::(); gs.ecs.register::(); @@ -76,12 +118,17 @@ fn main() -> rltk::BError { gs.ecs.register::(); 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 + let player_entity = gs.ecs .create_entity() .with(Position { x: player_x, @@ -101,8 +148,10 @@ fn main() -> rltk::BError { .with(Name { name: "Player".to_string(), }) + .with(CombatStats{max_hp: 30, hp: 30, defence: 2, power: 5}) .build(); + gs.ecs.insert(player_entity); let mut rng = rltk::RandomNumberGenerator::new(); for (i, room) in map.rooms.iter().skip(1).enumerate() { let (x, y) = room.center(); @@ -138,9 +187,13 @@ fn main() -> rltk::BError { .with(Name { name: format!("{} #{}", &name, i), }) + .with(BlocksTile{}) + .with(CombatStats{max_hp: 16, hp: 16, defence: 1, power: 4}) .build(); } gs.ecs.insert(map); + gs.ecs.insert(RunState::PreRun); + gs.ecs.insert(gamelog::GameLog{ entries : vec!["Welcome to my rougelike".to_string()]}); // Main loop runner rltk::main_loop(context, gs) diff --git a/src/map.rs b/src/map.rs index 52c61e5..9698e21 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,8 +1,12 @@ use super::Rect; -use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, SmallVec, RGB}; +use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB}; use specs::prelude::*; use std::cmp::{max, min}; +const MAPWIDTH : usize = 80; +const MAPHIEGHT : usize = 43; +const MAPCOUNT : usize = MAPHIEGHT * MAPWIDTH; + #[derive(PartialEq, Copy, Clone)] pub enum TileType { Wall, @@ -17,11 +21,13 @@ pub struct Map { pub height: i32, pub revealed_tiles: Vec, pub visible_tiles: Vec, + pub blocked : Vec, + pub tile_content : Vec> } impl Map { pub fn xy_idx(&self, x: i32, y: i32) -> usize { - (y as usize * 80) + x as usize + (y as usize * MAPWIDTH) + x as usize } pub fn apply_room_to_map(&mut self, room: &Rect) { @@ -53,12 +59,14 @@ impl Map { pub fn new_map_rooms_and_corridors() -> Map { let mut map = Map { - tiles: vec![TileType::Wall; 80 * 50], + tiles: vec![TileType::Wall; MAPCOUNT], rooms: Vec::new(), - width: 80, - height: 50, - revealed_tiles: vec![false; 80 * 50], - visible_tiles: vec![false; 80 * 50], + width: MAPWIDTH as i32, + height: MAPHIEGHT as i32, + revealed_tiles: vec![false; MAPCOUNT], + visible_tiles: vec![false; MAPCOUNT], + blocked: vec![false; MAPCOUNT], + tile_content: vec![Vec::new(); MAPCOUNT] }; const MAX_ROOMS: i32 = 30; @@ -99,15 +107,50 @@ impl Map { map } - pub fn is_exit_valid(&self, x: i32, y: i32) -> bool { + 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 + !self.blocked[idx] } - pub fn get_available_exits(&self, idx: usize) -> rltk::SmallVec<[(usize, f32); 10]> { + + + + pub fn populate_blocked(&mut self) { + for (i, tile) in self.tiles.iter_mut().enumerate() { + self.blocked[i] = *tile == TileType::Wall; + } + } + + pub fn clear_content_index(&mut self) { + for content in self.tile_content.iter_mut() { + content.clear() + } + } + +} + +impl Algorithm2D for Map { + fn dimensions(&self) -> rltk::Point { + Point::new(self.width, self.height) + } +} + +impl BaseMap for Map { + fn is_opaque(&self, idx: usize) -> bool { + self.tiles[idx as usize] == TileType::Wall + } + + 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) + } + + 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; @@ -127,32 +170,21 @@ impl Map { exits.push((idx + w, 1.0)) }; + // Diagonals + if self.is_exit_valid(x-1, y-1) { exits.push(((idx-w)-1, 1.45)); } + if self.is_exit_valid(x+1, y-1) { exits.push(((idx-w)+1, 1.45)); } + if self.is_exit_valid(x-1, y+1) { exits.push(((idx+w)-1, 1.45)); } + if self.is_exit_valid(x+1, y+1) { exits.push(((idx+w)+1, 1.45)); } + 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 { - fn dimensions(&self) -> rltk::Point { - Point::new(self.width, self.height) - } -} - -impl BaseMap for Map { - fn is_opaque(&self, idx: usize) -> bool { - self.tiles[idx as usize] == TileType::Wall - } } // /// Makes a map with solid boundaries and 400 randomly placed walls, this is bad // pub fn new_map_test() -> Vec { -// let mut map = vec![TileType::Floor; 80 * 50]; +// let mut map = vec![TileType::Floor; MAPCOUNT]; // for x in 0..80 { // map[xy_idx(x, 0)] = TileType::Wall; diff --git a/src/map_indexing_system.rs b/src/map_indexing_system.rs new file mode 100644 index 0000000..a9493b0 --- /dev/null +++ b/src/map_indexing_system.rs @@ -0,0 +1,29 @@ +use specs::prelude::*; +use super::{Map, Position, BlocksTile}; + +pub struct MapIndexingSystem {} + +impl<'a> System<'a> for MapIndexingSystem { + type SystemData = ( WriteExpect<'a, Map>, + ReadStorage<'a, Position>, + ReadStorage<'a, BlocksTile>, + Entities<'a>,); + + fn run(&mut self, data : Self::SystemData) { + let (mut map, position, blockers, entities) = data; + + map.populate_blocked(); + map.clear_content_index(); + for (entity, position) in (&entities, &position).join() { + let idx = map.xy_idx(position.x, position.y); + + // If the tile blocks update the blocking list + let _p : Option<&BlocksTile> = blockers.get(entity); + if let Some(_p) = _p { + map.blocked[idx] = true; + } + + map.tile_content[idx].push(entity); + } + } +} \ No newline at end of file diff --git a/src/melee_combat_system.rs b/src/melee_combat_system.rs new file mode 100644 index 0000000..c916130 --- /dev/null +++ b/src/melee_combat_system.rs @@ -0,0 +1,39 @@ +use specs::prelude::*; +use super::{CombatStats, WantsToMelee, Name, SufferDamage, GameLog}; +use rltk::console; + +pub struct MeleeCombatSystem {} + +impl<'a> System<'a> for MeleeCombatSystem { + type SystemData = ( Entities <'a>, + WriteExpect<'a, GameLog>, + WriteStorage<'a, WantsToMelee>, + ReadStorage<'a, Name>, + ReadStorage<'a, CombatStats>, + WriteStorage<'a, SufferDamage> + ); + + fn run(&mut self, data : Self::SystemData) { + let (entities, mut log, mut wants_melee, names, combat_stats, mut inflict_damage) = data; + + for (_entity, wants_melee, name, stats) in (&entities, &wants_melee, &names, &combat_stats).join() { + if stats.hp > 0 { + let target_stats = combat_stats.get(wants_melee.target).unwrap(); + if target_stats.hp > 0 { + let target_name = names.get(wants_melee.target).unwrap(); + + let damage = i32::max(0, stats.power - target_stats.defence); + + if damage == 0 { + log.entries.push(format!("{} is unable to hurt {}", &name.name, &target_name.name)); + } else { + log.entries.push(format!("{} hits {}, for {} hp", &name.name, &target_name.name, damage)); + SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage); + } + } + } + } + + wants_melee.clear(); + } +} \ No newline at end of file diff --git a/src/monster_ai_system.rs b/src/monster_ai_system.rs index 146329d..ad3ed63 100644 --- a/src/monster_ai_system.rs +++ b/src/monster_ai_system.rs @@ -1,5 +1,5 @@ -use super::{Map, Monster, Name, Position, Viewshed}; -use rltk::{console, Point}; +use super::{Map, Monster, Position, Viewshed, WantsToMelee, RunState}; +use rltk::{Point}; use specs::prelude::*; pub struct MonsterAI {} @@ -9,32 +9,45 @@ impl<'a> System<'a> for MonsterAI { type SystemData = ( WriteExpect<'a, Map>, ReadExpect<'a, Point>, + ReadExpect<'a, Entity>, + ReadExpect<'a, RunState>, + Entities<'a>, WriteStorage<'a, Viewshed>, ReadStorage<'a, Monster>, - ReadStorage<'a, Name>, WriteStorage<'a, Position>, + WriteStorage<'a, WantsToMelee>, ); fn run(&mut self, data: Self::SystemData) { - let (mut map, player_pos, mut viewshed, monster, name, mut position) = data; + let (mut map, player_pos, player_entity, runstate, entities, mut viewshed, monster, mut position, mut wants_to_melee) = data; - for (mut viewshed, _monster, name, mut pos) in - (&mut viewshed, &monster, &name, &mut position).join() + if *runstate != RunState::MonsterTurn { return; } + + for (entity, mut viewshed, _monster, mut pos) in (&entities, &mut viewshed, &monster, &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; + let distance = rltk::DistanceAlg::Pythagoras.distance2d(Point::new(pos.x, pos.y), *player_pos); + if distance < 1.5 { + wants_to_melee.insert(entity, WantsToMelee{ target: *player_entity }).expect("Unable to insert attack"); } + else if viewshed.visible_tiles.contains(&*player_pos) { + // Path to the player + let path = rltk::a_star_search( + map.xy_idx(pos.x, pos.y), + map.xy_idx(player_pos.x, player_pos.y), + &mut *map + ); + if path.success && path.steps.len() > 1 { + let mut idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = false; + pos.x = path.steps[1] as i32 % map.width; + pos.y = path.steps[1] as i32 / map.width; + idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = true; + viewshed.dirty = true; + } + } + } } } diff --git a/src/player.rs b/src/player.rs index 2725078..3f98a95 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,4 +1,6 @@ -use super::{Map, Player, Position, RunState, State, TileType, Viewshed}; +use crate::WantsToMelee; + +use super::{Map, Player, Position, RunState, State, Viewshed, CombatStats}; use rltk::{Point, Rltk, VirtualKeyCode}; use specs::prelude::*; use std::cmp::{max, min}; @@ -8,11 +10,24 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { let mut players = ecs.write_storage::(); let mut viewsheds = ecs.write_storage::(); let mut ppos = ecs.write_resource::(); + let combat_stats = ecs.read_storage::(); let map = ecs.fetch::(); + let entities = ecs.entities(); + let mut wants_to_melee = ecs.write_storage::(); - for (_player, pos, viewshed) in (&mut players, &mut positions, &mut viewsheds).join() { + for (entity, _player, pos, viewshed) in (&entities, &players, &mut positions, &mut viewsheds).join() { + if pos.x + delta_x < 1 || pos.x + delta_x > map.width -1 || pos.y + delta_y < 1 || pos.y + delta_y > map.height - 1 { return ;} let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); - if map.tiles[destination_idx] != TileType::Wall { + + for potential_target in map.tile_content[destination_idx].iter() { + let target = combat_stats.get(*potential_target); + + if let Some(_target) = target { + wants_to_melee.insert(entity, WantsToMelee { target: *potential_target }).expect("Add target failed"); + return + } + } + if !map.blocked[destination_idx] { pos.x = min(79, max(0, pos.x + delta_x)); pos.y = min(49, max(0, pos.y + delta_y)); ppos.x = pos.x; @@ -24,7 +39,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { match ctx.key { - None => return RunState::Paused, + None => return RunState::AwaitingInput, Some(key) => match key { VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => { try_move_player(-1, 0, &mut gs.ecs) @@ -41,8 +56,13 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { VirtualKeyCode::Down | VirtualKeyCode::Numpad2 | VirtualKeyCode::J => { try_move_player(0, 1, &mut gs.ecs) } - _ => return RunState::Paused, + + VirtualKeyCode::Numpad9 | VirtualKeyCode::Y => {try_move_player(1, -1, &mut gs.ecs)} + VirtualKeyCode::Numpad7 | VirtualKeyCode::U => {try_move_player(-1, -1, &mut gs.ecs)} + VirtualKeyCode::Numpad3 | VirtualKeyCode::N => {try_move_player(1, 1, &mut gs.ecs)} + VirtualKeyCode::Numpad1 | VirtualKeyCode::B => {try_move_player(-1, 1, &mut gs.ecs)} + _ => return RunState::AwaitingInput, }, } - RunState::Running + RunState::PlayerTurn }