From 9d911d8c721341d7f441ca3ee631ff3f25f6bd3c Mon Sep 17 00:00:00 2001 From: Alex Page Date: Wed, 2 Feb 2022 03:58:48 -0500 Subject: [PATCH] Heavily reorganize code --- src/components/monster.rs | 2 +- src/graphics/entities.rs | 19 +++ src/graphics/map.rs | 9 ++ src/graphics/mod.rs | 16 +++ src/{ => graphics}/sidebar.rs | 64 ++++----- src/{ => graphics}/vga_color.rs | 0 src/graphics/whip.rs | 53 +++++++ src/input/mod.rs | 50 +++++++ src/input/player.rs | 95 ++++++++++++ src/main.rs | 38 ++--- src/resources/clock.rs | 9 ++ src/resources/map.rs | 23 +-- src/resources/mod.rs | 40 +----- src/state.rs | 240 ++----------------------------- src/systems/mod.rs | 16 +++ src/systems/monster_ai_system.rs | 119 +++++++-------- src/systems/time_system.rs | 45 ++++++ src/tile_data.rs | 2 +- 18 files changed, 454 insertions(+), 386 deletions(-) create mode 100644 src/graphics/entities.rs create mode 100644 src/graphics/map.rs create mode 100644 src/graphics/mod.rs rename src/{ => graphics}/sidebar.rs (52%) rename src/{ => graphics}/vga_color.rs (100%) create mode 100644 src/graphics/whip.rs create mode 100644 src/input/mod.rs create mode 100644 src/input/player.rs create mode 100644 src/resources/clock.rs create mode 100644 src/systems/time_system.rs diff --git a/src/components/monster.rs b/src/components/monster.rs index 7df8a42..7aa6c32 100644 --- a/src/components/monster.rs +++ b/src/components/monster.rs @@ -1,6 +1,6 @@ use crate::{ + graphics::vga_color as vga, resources::{sound_output::SoundSamples, SoundEffects}, - vga_color as vga, }; use bracket_lib::prelude::*; use specs::prelude::*; diff --git a/src/graphics/entities.rs b/src/graphics/entities.rs new file mode 100644 index 0000000..9086204 --- /dev/null +++ b/src/graphics/entities.rs @@ -0,0 +1,19 @@ +use bracket_lib::prelude::*; +use specs::prelude::*; + +use crate::{components::*, constants::*}; + +pub fn draw(world: &World, bterm: &mut BTerm) { + let positions = world.read_storage::(); + let renderables = world.read_storage::(); + + for (pos, render) in (&positions, &renderables).join() { + bterm.set( + pos.x + MAP_X as i32, + pos.y + MAP_Y as i32, + render.fg, + render.bg, + render.glyph, + ); + } +} diff --git a/src/graphics/map.rs b/src/graphics/map.rs new file mode 100644 index 0000000..0731052 --- /dev/null +++ b/src/graphics/map.rs @@ -0,0 +1,9 @@ +use bracket_lib::prelude::*; +use specs::prelude::*; + +use crate::resources::*; + +pub fn draw(world: &World, bterm: &mut BTerm) { + let map = world.fetch::(); + map.draw(bterm); +} diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs new file mode 100644 index 0000000..4b148ba --- /dev/null +++ b/src/graphics/mod.rs @@ -0,0 +1,16 @@ +use bracket_lib::prelude::*; +use specs::prelude::*; + +mod entities; +mod map; +mod sidebar; +pub mod vga_color; +mod whip; + +pub fn draw(world: &World, bterm: &mut BTerm) { + bterm.cls(); + map::draw(world, bterm); + entities::draw(world, bterm); + whip::draw(world, bterm); + sidebar::draw(world, bterm); +} diff --git a/src/sidebar.rs b/src/graphics/sidebar.rs similarity index 52% rename from src/sidebar.rs rename to src/graphics/sidebar.rs index 4e09acf..bd3ef9d 100644 --- a/src/sidebar.rs +++ b/src/graphics/sidebar.rs @@ -1,12 +1,12 @@ use crate::constants::{SIDEBAR_POS_X, SIDEBAR_POS_Y}; +use crate::graphics::vga_color as vga; use crate::resources::{Clock, LevelNumber, ShowDebugInfo, Stats}; -use crate::vga_color as vga; use bracket_lib::prelude::*; use specs::prelude::*; -pub fn draw(ecs: &World, ctx: &mut BTerm) { +pub fn draw(world: &World, bterm: &mut BTerm) { // Blue background - ctx.fill_region( + bterm.fill_region( Rect { x1: SIDEBAR_POS_X, x2: SIDEBAR_POS_X + 14, @@ -20,7 +20,7 @@ pub fn draw(ecs: &World, ctx: &mut BTerm) { // Gray number boxes (1..17).step_by(3).for_each(|y| { - ctx.fill_region( + bterm.fill_region( Rect { x1: SIDEBAR_POS_X + 3, x2: SIDEBAR_POS_X + 10, @@ -34,27 +34,27 @@ pub fn draw(ecs: &World, ctx: &mut BTerm) { }); // Stats - ctx.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y, "Score"); - ctx.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 3, "Level"); - ctx.print_centered_at( + bterm.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y, "Score"); + bterm.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 3, "Level"); + bterm.print_centered_at( SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 4, - ecs.read_resource::().0 + 1, + world.read_resource::().0 + 1, ); - ctx.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 6, "Gems"); - ctx.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 9, "Whips"); - ctx.print(SIDEBAR_POS_X + 2, SIDEBAR_POS_Y + 12, "Teleports"); - ctx.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 15, "Keys"); + bterm.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 6, "Gems"); + bterm.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 9, "Whips"); + bterm.print(SIDEBAR_POS_X + 2, SIDEBAR_POS_Y + 12, "Teleports"); + bterm.print(SIDEBAR_POS_X + 4, SIDEBAR_POS_Y + 15, "Keys"); - let stats = ecs.read_resource::(); - ctx.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 1, stats.score); - ctx.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 7, stats.gems); - ctx.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 10, stats.whips); - ctx.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 13, stats.teleports); - ctx.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 16, stats.keys); + let stats = world.read_resource::(); + bterm.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 1, stats.score); + bterm.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 7, stats.gems); + bterm.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 10, stats.whips); + bterm.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 13, stats.teleports); + bterm.print_centered_at(SIDEBAR_POS_X + 6, SIDEBAR_POS_Y + 16, stats.keys); // Hotkey list - ctx.print_color( + bterm.print_color( SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 18, RGB::named(vga::CYAN_BRIGHT), @@ -62,7 +62,7 @@ pub fn draw(ecs: &World, ctx: &mut BTerm) { "OPTIONS", ); - ctx.fill_region( + bterm.fill_region( Rect { x1: SIDEBAR_POS_X, x2: SIDEBAR_POS_X + 14, @@ -74,7 +74,7 @@ pub fn draw(ecs: &World, ctx: &mut BTerm) { RGB::named(vga::BLUE), ); - ctx.fill_region( + bterm.fill_region( Rect { x1: SIDEBAR_POS_X + 3, x2: SIDEBAR_POS_X + 4, @@ -86,28 +86,28 @@ pub fn draw(ecs: &World, ctx: &mut BTerm) { RGB::named(vga::BLUE), ); - ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 19, "Whip"); - ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 20, "Teleport"); - ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 21, "Pause"); - ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 22, "Quit"); - ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 23, "Save"); - ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 24, "Restore"); + bterm.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 19, "Whip"); + bterm.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 20, "Teleport"); + bterm.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 21, "Pause"); + bterm.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 22, "Quit"); + bterm.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 23, "Save"); + bterm.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 24, "Restore"); - if ecs.read_resource::().0 { - ctx.print_color_right( + if world.read_resource::().0 { + bterm.print_color_right( SIDEBAR_POS_X + 14, SIDEBAR_POS_Y, RGB::named(vga::GREEN_BRIGHT), RGB::named(vga::BLACK), - &format!("{}", ctx.fps), + &format!("{}", bterm.fps), ); - ctx.print_color( + bterm.print_color( 0, 0, RGB::named(vga::YELLOW_BRIGHT), RGB::named(vga::BLACK), - &format!("{}", ecs.read_resource::().ticks), + &format!("{}", world.read_resource::().ticks), ); } } diff --git a/src/vga_color.rs b/src/graphics/vga_color.rs similarity index 100% rename from src/vga_color.rs rename to src/graphics/vga_color.rs diff --git a/src/graphics/whip.rs b/src/graphics/whip.rs new file mode 100644 index 0000000..f20fff3 --- /dev/null +++ b/src/graphics/whip.rs @@ -0,0 +1,53 @@ +use bracket_lib::prelude::*; +use specs::prelude::*; + +use crate::{components::*, constants::*, resources::*}; + +use super::{vga_color as vga, vga_color::get_by_index}; + +pub fn draw(world: &World, bterm: &mut BTerm) { + let positions = world.read_storage::(); + let wants_to_whips = world.read_storage::(); + let map = world.read_resource::(); + let mut rng = RandomNumberGenerator::new(); + for (position, wants_to_whip) in (&positions, &wants_to_whips).join() { + let color = RGB::named(get_by_index(rng.range(1, 16))); + let mut rendered_frame = wants_to_whip.frame; + let frame_data = loop { + let frame_data = match rendered_frame { + 0 => Some((-1, -1, '\\')), + 1 => Some((-1, 0, '─')), + 2 => Some((-1, 1, '/')), + 3 => Some((0, 1, '│')), + 4 => Some((1, 1, '\\')), + 5 => Some((1, 0, '─')), + 6 => Some((1, -1, '/')), + 7 => Some((0, -1, '│')), + _ => None, + }; + + if let Some(data) = frame_data { + let dest = Point { + x: position.x + data.0, + y: position.y + data.1, + }; + if map.in_bounds(dest) { + break frame_data; + } + rendered_frame += 1; + if rendered_frame > 7 { + break None; + } + } + }; + if let Some(data) = frame_data { + bterm.set( + (position.x + MAP_X as i32) + data.0, + (position.y + MAP_Y as i32) + data.1, + color, + RGB::named(vga::BLACK), + to_cp437(data.2), + ); + }; + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 0000000..4da204b --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,50 @@ +use bracket_lib::prelude::*; +use specs::prelude::*; + +use crate::resources::*; + +mod player; + +pub fn handle(world: &World, bterm: &mut BTerm) { + match bterm.key { + None => {} + Some(key) => match key { + VirtualKeyCode::Left | VirtualKeyCode::J => { + player::try_move(-1, 0, world); + } + VirtualKeyCode::U | VirtualKeyCode::Home => player::try_move(-1, -1, world), + VirtualKeyCode::Up | VirtualKeyCode::I => { + player::try_move(0, -1, world); + } + VirtualKeyCode::O | VirtualKeyCode::PageUp => player::try_move(1, -1, world), + VirtualKeyCode::Right | VirtualKeyCode::K => { + player::try_move(1, 0, world); + } + VirtualKeyCode::Comma | VirtualKeyCode::PageDown => player::try_move(1, 1, world), + VirtualKeyCode::Down | VirtualKeyCode::M => { + player::try_move(0, 1, world); + } + VirtualKeyCode::N | VirtualKeyCode::End => player::try_move(-1, 1, world), + VirtualKeyCode::W => { + player::whip(world); + } + VirtualKeyCode::S => { + let mut sound_system = world.write_resource::(); + let sound_effects = world.fetch::(); + sound_system.play_sound(sound_effects.pickup.clone()); + } + VirtualKeyCode::D => { + let mut show_debug_info = world.write_resource::(); + show_debug_info.0 = !show_debug_info.0; + } + VirtualKeyCode::Escape | VirtualKeyCode::Q => { + bterm.quit(); + } + _ => { + let mut sound_system = world.write_resource::(); + let sound_effects = world.fetch::(); + sound_system.play_sound(sound_effects.bad_key.clone()); + } + }, + } +} diff --git a/src/input/player.rs b/src/input/player.rs new file mode 100644 index 0000000..3d38fe5 --- /dev/null +++ b/src/input/player.rs @@ -0,0 +1,95 @@ +use std::time::{Duration, Instant}; + +use bracket_lib::prelude::*; +use specs::prelude::*; + +use crate::{ + components::monster::*, components::*, constants::*, resources::*, systems::TimeSystem, +}; + +pub fn try_move(delta_x: i32, delta_y: i32, world: &World) { + let entities = world.entities(); + let mut positions = world.write_storage::(); + let mut players = world.write_storage::(); + let monsters = world.write_storage::(); + let mut map = world.write_resource::(); + let mut stats = world.write_resource::(); + let mut sound_output = world.write_resource::(); + let wants_to_whips = world.read_storage::(); + let mut clock = world.write_resource::(); + + for (player_entity, player, pos) in (&entities, &mut players, &mut positions).join() { + // The player shouldn't be able to move while whipping + if let Some(_wants_to_whip) = wants_to_whips.get(player_entity) { + continue; + } + + let now = Instant::now(); + if now - player.last_moved > Duration::from_secs_f32(PLAYER_STEP_PERIOD) { + let destination = Point { + x: pos.x + delta_x, + y: pos.y + delta_y, + }; + + let mut sound_effects = world.fetch_mut::(); + + if map.in_bounds(destination) { + if map.is_solid(destination) { + sound_output.play_sound(sound_effects.blocked.clone()); + } else { + if let Some(e) = map.get_tile_content_at(destination) { + if let Some(monster) = monsters.get(e) { + stats.add_score(damage_for_kind(monster.kind)); + stats.take_gems(damage_for_kind(monster.kind)); + sound_output + .play_sound(sound_effect_for_kind(monster.kind, &sound_effects)); + let _ = entities.delete(e); + } + } + + map.clear_tile_content_at(Point::from(*pos)); + + pos.x = destination.x; + pos.y = destination.y; + + map.set_tile_content_at(destination, player_entity); + + TimeSystem::force_tick(&mut clock); + + sound_output.play_sound(sound_effects.step.clone()); + } + } else { + let static_sound = sound_effects.get_new_static_effect(&sound_output); + sound_output.play_sound(static_sound); + } + + player.last_moved = now; + } + } +} + +pub fn whip(world: &World) { + let entities = world.entities(); + + let players = world.read_storage::(); + let positions = world.read_storage::(); + let mut wants_to_whips = world.write_storage::(); + + let mut stats = world.write_resource::(); + let mut sound_output = world.write_resource::(); + let sound_effects = world.fetch::(); + + for (entity, _player, _position) in (&entities, &players, &positions).join() { + if wants_to_whips.get(entity).is_none() && stats.whips > 0 { + let _ = wants_to_whips.insert( + entity, + WantsToWhip { + frame: 0, + last_frame: Instant::now(), + sound: Some(sound_output.play_sound(sound_effects.whipping.clone())), + }, + ); + stats.whips -= 1; + } + } +} diff --git a/src/main.rs b/src/main.rs index af06cc6..33cd6df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,13 @@ pub mod components; pub mod constants; pub mod difficulty; +mod graphics; +pub mod input; pub mod levels; pub mod resources; -mod sidebar; mod state; pub mod systems; pub mod tile_data; -pub mod vga_color; use bracket_lib::prelude::*; use components::{Monster, Player, Position, Renderable, WantsToWhip}; @@ -30,23 +30,23 @@ fn main() -> BError { let sound_effects = SoundEffects::new(&sound_system); sound_system.play_sound(sound_effects.startup.clone()); - let mut ecs = World::new(); + let mut world = World::new(); let starting_level = 0; - ecs.insert(sound_system); - ecs.insert(LevelNumber(starting_level)); - ecs.insert(ShowDebugInfo(false)); - ecs.insert(StopClock(false)); - ecs.insert(Clock { + world.insert(sound_system); + world.insert(LevelNumber(starting_level)); + world.insert(ShowDebugInfo(false)); + world.insert(StopClock(false)); + world.insert(Clock { last_ticked: Instant::now(), has_ticked: false, ticks: 0, }); - ecs.insert(sound_effects); + world.insert(sound_effects); let selected_difficulty = difficulty::SECRET; - ecs.insert(selected_difficulty); - ecs.insert(Stats { + world.insert(selected_difficulty); + world.insert(Stats { score: 0, gems: selected_difficulty.starting_gems, whips: selected_difficulty.starting_whips, @@ -55,15 +55,15 @@ fn main() -> BError { keys: selected_difficulty.starting_keys, }); - ecs.register::(); - ecs.register::(); - ecs.register::(); - ecs.register::(); - ecs.register::(); + world.register::(); + world.register::(); + world.register::(); + world.register::(); + world.register::(); let mut map = Map::from_level(levels::get_level(starting_level)); - map.spawn_entities(&mut ecs); - ecs.insert(map); + map.spawn_entities(&mut world); + world.insert(map); // let descent_sounds: Vec = (20..100) // .rev() @@ -83,5 +83,5 @@ fn main() -> BError { // let _ = gs.sound_system.play_sound(descent_effect); - main_loop(context, State::new(ecs)) + main_loop(context, State::new(world)) } diff --git a/src/resources/clock.rs b/src/resources/clock.rs new file mode 100644 index 0000000..02ca0de --- /dev/null +++ b/src/resources/clock.rs @@ -0,0 +1,9 @@ +use std::time::Instant; + +pub struct Clock { + pub last_ticked: Instant, + pub has_ticked: bool, + pub ticks: u128, +} + +pub struct StopClock(pub bool); diff --git a/src/resources/map.rs b/src/resources/map.rs index b9bb6a2..3c257b0 100644 --- a/src/resources/map.rs +++ b/src/resources/map.rs @@ -3,9 +3,9 @@ use std::time::Instant; use crate::{ components::{monster::*, Monster, Player, Position, Renderable}, constants::*, + graphics::vga_color as vga, levels::Level, tile_data::{self, TileType}, - vga_color as vga, }; use bracket_lib::{prelude::*, random::RandomNumberGenerator}; use specs::{Builder, Entity, World, WorldExt}; @@ -80,7 +80,7 @@ impl Map { } } - pub fn spawn_entities(&mut self, ecs: &mut World) { + pub fn spawn_entities(&mut self, world: &mut World) { for (index, tile) in self .tiles .iter_mut() @@ -90,7 +90,7 @@ impl Map { let point = Point::new(index % MAP_WIDTH, index / MAP_WIDTH); match tile { TileType::Player => { - let player_entity = ecs + let player_entity = world .create_entity() .with(Position { x: point.x, @@ -105,13 +105,12 @@ impl Map { last_moved: Instant::now(), }) .build(); - ecs.insert(point); - ecs.insert(player_entity); self.tile_content[index] = Some(player_entity); } TileType::Slow => { let mut rng = RandomNumberGenerator::new(); - ecs.create_entity() + world + .create_entity() .with(Position { x: point.x, y: point.y, @@ -131,7 +130,8 @@ impl Map { } TileType::Medium => { let mut rng = RandomNumberGenerator::new(); - ecs.create_entity() + world + .create_entity() .with(Position { x: point.x, y: point.y, @@ -151,7 +151,8 @@ impl Map { } TileType::Fast => { let mut rng = RandomNumberGenerator::new(); - ecs.create_entity() + world + .create_entity() .with(Position { x: point.x, y: point.y, @@ -199,9 +200,9 @@ impl Map { &self.tiles } - pub fn draw(&self, ctx: &mut BTerm) { + pub fn draw(&self, bterm: &mut BTerm) { // Border - ctx.fill_region( + bterm.fill_region( Rect { x1: MAP_X as i32 - 1, x2: MAP_WIDTH as i32 + MAP_X as i32 + 1, @@ -218,7 +219,7 @@ impl Map { let (x, y) = (point.x as usize, point.y as usize); let data = tile_data::tile_data(*tile); - ctx.set( + bterm.set( x + MAP_X, y + MAP_Y, if *tile == TileType::Gem { diff --git a/src/resources/mod.rs b/src/resources/mod.rs index df734a6..636a13d 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -1,52 +1,22 @@ +pub mod clock; pub mod map; pub mod sound_effects; pub mod sound_output; +use bracket_lib::prelude::*; +pub use clock::{Clock, StopClock}; pub use map::Map; pub use sound_effects::SoundEffects; pub use sound_output::SoundOutput; -use crate::constants::CLOCK_PERIOD; -use std::time::{Duration, Instant}; - #[derive(Default)] pub struct LevelNumber(pub u32); #[derive(Default)] pub struct ShowDebugInfo(pub bool); -pub struct Clock { - pub last_ticked: Instant, - pub has_ticked: bool, - pub ticks: u128, -} - -impl Clock { - pub fn try_tick(&mut self) { - if Instant::now() - self.last_ticked > Duration::from_secs_f32(CLOCK_PERIOD) { - self.tick(); - } else { - self.has_ticked = false; - } - } - - pub fn force_tick(&mut self) { - self.tick(); - } - - fn tick(&mut self) { - self.has_ticked = true; - self.last_ticked = Instant::now(); - self.ticks += 1; - } - - pub fn reset(&mut self) { - self.last_ticked = Instant::now(); - self.has_ticked = false; - } -} - -pub struct StopClock(pub bool); +#[derive(Default)] +pub struct PlayerInput(pub Option); pub struct Stats { pub score: u32, diff --git a/src/state.rs b/src/state.rs index cb61925..261e071 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,245 +1,23 @@ -use std::time::{Duration, Instant}; - -use crate::components::monster::damage_for_kind; -use crate::components::monster::sound_effect_for_kind; -use crate::components::*; -use crate::resources::*; +use crate::graphics::draw; +use crate::input::handle; use crate::systems::*; -use crate::vga_color as vga; -use crate::{constants::*, sidebar}; use bracket_lib::prelude::*; use specs::prelude::*; pub struct State { - ecs: World, + world: World, } impl GameState for State { - fn tick(&mut self, ctx: &mut BTerm) { - ctx.cls(); - - self.handle_input(ctx); - self.run_systems(); - - let map = self.ecs.fetch::(); - map.draw(ctx); - - let positions = self.ecs.read_storage::(); - let renderables = self.ecs.read_storage::(); - - for (pos, render) in (&positions, &renderables).join() { - ctx.set( - pos.x + MAP_X as i32, - pos.y + MAP_Y as i32, - render.fg, - render.bg, - render.glyph, - ); - } - - self.draw_whip(ctx); - - sidebar::draw(&self.ecs, ctx); + fn tick(&mut self, bterm: &mut BTerm) { + handle(&self.world, bterm); + run_systems(&mut self.world); + draw(&self.world, bterm); } } impl State { - pub fn new(ecs: World) -> Self { - State { ecs } - } - - fn handle_input(&mut self, ctx: &mut BTerm) { - // Player movement - match ctx.key { - None => {} // Nothing happened - Some(key) => match key { - VirtualKeyCode::Left | VirtualKeyCode::J => { - self.try_move_player(-1, 0); - } - VirtualKeyCode::U | VirtualKeyCode::Home => self.try_move_player(-1, -1), - VirtualKeyCode::Up | VirtualKeyCode::I => { - self.try_move_player(0, -1); - } - VirtualKeyCode::O | VirtualKeyCode::PageUp => self.try_move_player(1, -1), - VirtualKeyCode::Right | VirtualKeyCode::K => { - self.try_move_player(1, 0); - } - VirtualKeyCode::Comma | VirtualKeyCode::PageDown => self.try_move_player(1, 1), - VirtualKeyCode::Down | VirtualKeyCode::M => { - self.try_move_player(0, 1); - } - VirtualKeyCode::N | VirtualKeyCode::End => self.try_move_player(-1, 1), - VirtualKeyCode::W => { - let player_entity = self.ecs.read_resource::(); - let mut stats = self.ecs.write_resource::(); - let positions = self.ecs.read_storage::(); - let mut wants_to_whips = self.ecs.write_storage::(); - if let Some(_position) = positions.get(*player_entity) { - if wants_to_whips.get(*player_entity).is_none() && stats.whips > 0 { - let mut sound_output = self.ecs.write_resource::(); - let sound_effects = self.ecs.fetch::(); - let _ = wants_to_whips.insert( - *player_entity, - WantsToWhip { - frame: 0, - last_frame: Instant::now(), - sound: Some( - sound_output.play_sound(sound_effects.whipping.clone()), - ), - }, - ); - stats.whips -= 1; - } - } - } - VirtualKeyCode::S => { - let mut sound_system = self.ecs.write_resource::(); - let sound_effects = self.ecs.fetch::(); - sound_system.play_sound(sound_effects.pickup.clone()); - } - VirtualKeyCode::D => { - let mut show_debug_info = self.ecs.write_resource::(); - show_debug_info.0 = !show_debug_info.0; - } - VirtualKeyCode::Escape | VirtualKeyCode::Q => { - ctx.quit(); - } - _ => { - let mut sound_system = self.ecs.write_resource::(); - let sound_effects = self.ecs.fetch::(); - sound_system.play_sound(sound_effects.bad_key.clone()); - } - }, - } - } - - fn try_move_player(&mut self, delta_x: i32, delta_y: i32) { - let entities = self.ecs.entities(); - let mut positions = self.ecs.write_storage::(); - let mut players = self.ecs.write_storage::(); - let monsters = self.ecs.write_storage::(); - let mut map = self.ecs.write_resource::(); - let mut stats = self.ecs.write_resource::(); - let mut sound_output = self.ecs.write_resource::(); - let wants_to_whips = self.ecs.read_storage::(); - - for (player_entity, player, pos) in (&entities, &mut players, &mut positions).join() { - // The player shouldn't be able to move while whipping - if let Some(_wants_to_whip) = wants_to_whips.get(player_entity) { - continue; - } - - let now = Instant::now(); - if now - player.last_moved > Duration::from_secs_f32(PLAYER_STEP_PERIOD) { - let destination = Point { - x: pos.x + delta_x, - y: pos.y + delta_y, - }; - - let mut sound_effects = self.ecs.fetch_mut::(); - - if map.in_bounds(destination) { - if map.is_solid(destination) { - sound_output.play_sound(sound_effects.blocked.clone()); - } else { - if let Some(e) = map.get_tile_content_at(destination) { - if let Some(monster) = monsters.get(e) { - stats.add_score(damage_for_kind(monster.kind)); - stats.take_gems(damage_for_kind(monster.kind)); - sound_output.play_sound(sound_effect_for_kind( - monster.kind, - &sound_effects, - )); - let _ = entities.delete(e); - } - } - - map.clear_tile_content_at(Point::from(*pos)); - - pos.x = destination.x; - pos.y = destination.y; - - let mut player_pos = self.ecs.write_resource::(); - player_pos.x = pos.x; - player_pos.y = pos.y; - - map.set_tile_content_at(destination, player_entity); - - self.ecs.write_resource::().force_tick(); - - sound_output.play_sound(sound_effects.step.clone()); - } - } else { - let static_sound = sound_effects.get_new_static_effect(&sound_output); - sound_output.play_sound(static_sound); - } - - player.last_moved = now; - } - } - } - - pub fn draw_whip(&self, ctx: &mut BTerm) { - let positions = self.ecs.read_storage::(); - let wants_to_whips = self.ecs.read_storage::(); - let map = self.ecs.read_resource::(); - let mut rng = RandomNumberGenerator::new(); - for (position, wants_to_whip) in (&positions, &wants_to_whips).join() { - let color = RGB::named(vga::get_by_index(rng.range(1, 16))); - let mut rendered_frame = wants_to_whip.frame; - let frame_data = loop { - let frame_data = match rendered_frame { - 0 => Some((-1, -1, '\\')), - 1 => Some((-1, 0, '─')), - 2 => Some((-1, 1, '/')), - 3 => Some((0, 1, '│')), - 4 => Some((1, 1, '\\')), - 5 => Some((1, 0, '─')), - 6 => Some((1, -1, '/')), - 7 => Some((0, -1, '│')), - _ => None, - }; - - if let Some(data) = frame_data { - let dest = Point { - x: position.x + data.0, - y: position.y + data.1, - }; - if map.in_bounds(dest) { - break frame_data; - } - rendered_frame += 1; - if rendered_frame > 7 { - break None; - } - } - }; - if let Some(data) = frame_data { - ctx.set( - (position.x + MAP_X as i32) + data.0, - (position.y + MAP_Y as i32) + data.1, - color, - RGB::named(vga::BLACK), - to_cp437(data.2), - ); - }; - } - } - - fn run_systems(&mut self) { - let mut whip_system = WhipSystem {}; - whip_system.run_now(&self.ecs); - - let mut monster_ai_system = MonsterAiSystem {}; - monster_ai_system.run_now(&self.ecs); - - self.ecs.maintain(); - - let mut clock = self.ecs.write_resource::(); - if !self.ecs.read_resource::().0 { - clock.try_tick(); - } else { - clock.reset(); - } + pub fn new(world: World) -> Self { + State { world } } } diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 2845938..3a65728 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -1,5 +1,21 @@ pub mod monster_ai_system; +pub mod time_system; pub mod whip_system; pub use monster_ai_system::MonsterAiSystem; +use specs::prelude::*; +pub use time_system::TimeSystem; pub use whip_system::WhipSystem; + +pub fn run_systems(world: &mut World) { + let mut whip_system = WhipSystem {}; + whip_system.run_now(world); + + let mut monster_ai_system = MonsterAiSystem {}; + monster_ai_system.run_now(world); + + let mut time_system = TimeSystem {}; + time_system.run_now(world); + + world.maintain(); +} diff --git a/src/systems/monster_ai_system.rs b/src/systems/monster_ai_system.rs index 231f8be..7967435 100644 --- a/src/systems/monster_ai_system.rs +++ b/src/systems/monster_ai_system.rs @@ -16,7 +16,6 @@ impl<'a> System<'a> for MonsterAiSystem { type SystemData = ( Entities<'a>, ReadExpect<'a, Clock>, - ReadExpect<'a, Point>, WriteExpect<'a, Map>, WriteExpect<'a, Stats>, ReadExpect<'a, SoundEffects>, @@ -32,7 +31,6 @@ impl<'a> System<'a> for MonsterAiSystem { ( entities, clock, - player_pos, mut map, mut stats, sound_effects, @@ -43,69 +41,78 @@ impl<'a> System<'a> for MonsterAiSystem { players, ): Self::SystemData, ) { - let mut data = (&entities, &mut monsters, &mut positions, &mut renderables) - .join() - .collect::>(); + let mut player_position = None; + for (_entity, _player, position) in (&entities, &players, &positions).join() { + player_position = Some(Point::from(*position)); + } - for (entity, monster, position, renderable) in &mut data { - if clock.has_ticked { - monster.ticks_until_move -= 1; - if monster.ticks_until_move <= 0 { - // Change glyph - let mut rng = RandomNumberGenerator::new(); - if let Some(glyph) = - rng.random_slice_entry(&monster::glyphs_for_kind(monster.kind)) - { - renderable.glyph = *glyph; - } + if let Some(player_pos) = player_position { + let mut data = (&entities, &mut monsters, &mut positions, &mut renderables) + .join() + .collect::>(); - // Move monster - let (mut delta_x, mut delta_y) = (0, 0); - if player_pos.x < position.x { - delta_x = -1; - } - if player_pos.x > position.x { - delta_x = 1; - } - if player_pos.y < position.y { - delta_y = -1; - } - if player_pos.y > position.y { - delta_y = 1; - } - let destination = Point { - x: position.x + delta_x, - y: position.y + delta_y, - }; - - if let Some(e) = map.get_tile_content_at(destination) { - if let Some(_player) = players.get(e) { - // TODO: Sound - map.clear_tile_content_at(Point::from(**position)); - stats.take_gems(damage_for_kind(monster.kind)); - sound_output - .play_sound(sound_effect_for_kind(monster.kind, &sound_effects)); - let _ = entities.delete(*entity); + for (entity, monster, position, renderable) in &mut data { + if clock.has_ticked { + monster.ticks_until_move -= 1; + if monster.ticks_until_move <= 0 { + // Change glyph + let mut rng = RandomNumberGenerator::new(); + if let Some(glyph) = + rng.random_slice_entry(&monster::glyphs_for_kind(monster.kind)) + { + renderable.glyph = *glyph; } - } else { - let tile = map.get_tile_at_mut(destination); - match tile { - TileType::Wall => {} - TileType::Block => { + + // Move monster + let (mut delta_x, mut delta_y) = (0, 0); + if player_pos.x < position.x { + delta_x = -1; + } + if player_pos.x > position.x { + delta_x = 1; + } + if player_pos.y < position.y { + delta_y = -1; + } + if player_pos.y > position.y { + delta_y = 1; + } + let destination = Point { + x: position.x + delta_x, + y: position.y + delta_y, + }; + + if let Some(e) = map.get_tile_content_at(destination) { + if let Some(_player) = players.get(e) { // TODO: Sound - *tile = TileType::Floor; + map.clear_tile_content_at(Point::from(**position)); + stats.take_gems(damage_for_kind(monster.kind)); + sound_output.play_sound(sound_effect_for_kind( + monster.kind, + &sound_effects, + )); let _ = entities.delete(*entity); } - _ => { - map.clear_tile_content_at(Point::from(**position)); - position.x = destination.x; - position.y = destination.y; - map.set_tile_content_at(destination, *entity); + } else { + let tile = map.get_tile_at_mut(destination); + match tile { + TileType::Wall => {} + TileType::Block => { + // TODO: Sound + *tile = TileType::Floor; + let _ = entities.delete(*entity); + } + _ => { + map.clear_tile_content_at(Point::from(**position)); + position.x = destination.x; + position.y = destination.y; + map.set_tile_content_at(destination, *entity); + } } } - } - monster.ticks_until_move = ticks_for_kind(monster.kind); + monster.ticks_until_move = ticks_for_kind(monster.kind); + } } } } diff --git a/src/systems/time_system.rs b/src/systems/time_system.rs new file mode 100644 index 0000000..a4900e1 --- /dev/null +++ b/src/systems/time_system.rs @@ -0,0 +1,45 @@ +use crate::constants::CLOCK_PERIOD; +use specs::prelude::*; +use std::time::{Duration, Instant}; + +use crate::resources::{Clock, StopClock}; + +pub struct TimeSystem {} + +#[allow(clippy::type_complexity)] +impl<'a> System<'a> for TimeSystem { + type SystemData = (WriteExpect<'a, Clock>, ReadExpect<'a, StopClock>); + + fn run(&mut self, (mut clock, stop_clock): Self::SystemData) { + if !stop_clock.0 { + Self::try_tick(&mut clock); + } else { + Self::reset(&mut clock); + } + } +} + +impl TimeSystem { + pub fn try_tick(clock: &mut Clock) { + if Instant::now() - clock.last_ticked > Duration::from_secs_f32(CLOCK_PERIOD) { + Self::tick(clock); + } else { + clock.has_ticked = false; + } + } + + pub fn force_tick(clock: &mut Clock) { + Self::tick(clock); + } + + fn tick(clock: &mut Clock) { + clock.has_ticked = true; + clock.last_ticked = Instant::now(); + clock.ticks += 1; + } + + pub fn reset(clock: &mut Clock) { + clock.last_ticked = Instant::now(); + clock.has_ticked = false; + } +} diff --git a/src/tile_data.rs b/src/tile_data.rs index b9dba3a..db8714f 100644 --- a/src/tile_data.rs +++ b/src/tile_data.rs @@ -1,6 +1,6 @@ use bracket_lib::prelude::*; -use crate::vga_color as vga; +use crate::graphics::vga_color as vga; #[derive(PartialEq, Copy, Clone)] pub struct TileData {