From 784e2053579a1aeec6c3eb718e10a99b6bfeed5c Mon Sep 17 00:00:00 2001 From: Alex Page Date: Sun, 30 Jan 2022 17:06:27 -0500 Subject: [PATCH] Implement whipping --- src/components/mod.rs | 14 ++++ src/main.rs | 8 ++- src/resources/map.rs | 4 +- src/resources/mod.rs | 7 ++ src/resources/sound_effects.rs | 20 ++++++ src/resources/sound_output.rs | 7 +- src/state.rs | 82 +++++++++++++++++++++-- src/systems/mod.rs | 2 + src/systems/whip_system.rs | 116 +++++++++++++++++++++++++++++++++ 9 files changed, 249 insertions(+), 11 deletions(-) create mode 100644 src/systems/whip_system.rs diff --git a/src/components/mod.rs b/src/components/mod.rs index f024eb4..2875e23 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,3 +1,8 @@ +use std::time::Instant; + +use specs::prelude::*; +use specs_derive::Component; + pub mod monster; pub mod player; pub mod position; @@ -7,3 +12,12 @@ pub use monster::Monster; pub use player::Player; pub use position::Position; pub use renderable::Renderable; + +use crate::resources::sound_output::SoundEffectHandle; + +#[derive(Component)] +pub struct WantsToWhip { + pub frame: u8, + pub last_frame: Instant, + pub sound: Option, +} diff --git a/src/main.rs b/src/main.rs index 344cc9d..c0c6f0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,10 @@ pub mod tile_data; pub mod vga_color; use bracket_lib::prelude::*; -use components::{Monster, Player, Position, Renderable}; -use resources::{Clock, LevelNumber, Map, ShowDebugInfo, SoundEffects, SoundOutput, Stats}; +use components::{Monster, Player, Position, Renderable, WantsToWhip}; +use resources::{ + Clock, LevelNumber, Map, ShowDebugInfo, SoundEffects, SoundOutput, Stats, StopClock, +}; use specs::prelude::*; use state::State; use std::time::Instant; @@ -34,6 +36,7 @@ fn main() -> BError { ecs.insert(sound_system); ecs.insert(LevelNumber(starting_level)); ecs.insert(ShowDebugInfo(false)); + ecs.insert(StopClock(false)); ecs.insert(Clock { last_ticked: Instant::now(), has_ticked: false, @@ -56,6 +59,7 @@ fn main() -> BError { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); let map = Map::from_level(levels::get_level(starting_level)); map.spawn_entities(&mut ecs); diff --git a/src/resources/map.rs b/src/resources/map.rs index d561306..4cbc00c 100644 --- a/src/resources/map.rs +++ b/src/resources/map.rs @@ -85,7 +85,8 @@ impl Map { let point = self.index_to_point2d(index); match tile { TileType::Player => { - ecs.create_entity() + let player_entity = ecs + .create_entity() .with(Position { x: point.x, y: point.y, @@ -100,6 +101,7 @@ impl Map { }) .build(); ecs.insert(point); + ecs.insert(player_entity); } TileType::Slow => { let mut rng = RandomNumberGenerator::new(); diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 1a39908..08af502 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -39,8 +39,15 @@ impl Clock { 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); + pub struct Stats { pub score: u32, pub gems: u32, diff --git a/src/resources/sound_effects.rs b/src/resources/sound_effects.rs index 9fcb9df..e3f3259 100644 --- a/src/resources/sound_effects.rs +++ b/src/resources/sound_effects.rs @@ -29,6 +29,8 @@ pub struct SoundEffects { pub pickup: SoundSamples, pub bad_key: SoundSamples, pub blocked: SoundSamples, + pub whipping: SoundSamples, + pub whipping_hit: SoundSamples, rng: RandomNumberGenerator, } @@ -105,6 +107,24 @@ impl SoundEffects { }) .collect(), }), + whipping: ss.render_sound_effect(&SoundEffect { + sounds: vec![Sound { + sound_type: SoundType::Tone(70), + duration: Duration::from_secs(3), + }], + }), + whipping_hit: ss.render_sound_effect(&SoundEffect { + sounds: vec![ + Sound { + sound_type: SoundType::Tone(400), + duration: Duration::from_millis(20), + }, + Sound { + sound_type: SoundType::Tone(90), + duration: Duration::from_secs(3), + }, + ], + }), rng: RandomNumberGenerator::new(), } } diff --git a/src/resources/sound_output.rs b/src/resources/sound_output.rs index d951719..2dba98a 100644 --- a/src/resources/sound_output.rs +++ b/src/resources/sound_output.rs @@ -4,12 +4,13 @@ use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, SampleRate, Stream, }; -use oddio::{Frames, Handle, Mixer}; +use oddio::{Frames, FramesSignal, Gain, Handle, Mixer, MonoToStereo, Stop}; use rand::Rng; use super::sound_effects::{SoundEffect, SoundType}; pub type SoundSamples = Arc>; +pub type SoundEffectHandle = Handle>>>>; pub struct SoundOutput { mixer_handle: Handle>, @@ -114,12 +115,12 @@ impl SoundOutput { oddio::Frames::from_iter(self.sample_rate.0, effect_buffer.iter().copied()) } - pub fn play_sound(&mut self, samples: SoundSamples) { + pub fn play_sound(&mut self, samples: SoundSamples) -> SoundEffectHandle { self.mixer_handle .control::, _>() .play(oddio::MonoToStereo::new(oddio::Gain::new( oddio::FramesSignal::from(samples), 0.20, - ))); + ))) } } diff --git a/src/state.rs b/src/state.rs index e69f991..54d3ea5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,9 +1,10 @@ use std::time::{Duration, Instant}; use crate::components::monster::damage_for_kind; -use crate::components::{Monster, Player, Position, Renderable}; -use crate::resources::{Clock, Map, ShowDebugInfo, SoundEffects, SoundOutput, Stats}; -use crate::systems::{MapIndexingSystem, MonsterAiSystem}; +use crate::components::*; +use crate::resources::*; +use crate::systems::*; +use crate::vga_color as vga; use crate::{constants::*, sidebar}; use bracket_lib::prelude::*; use specs::prelude::*; @@ -35,6 +36,8 @@ impl GameState for State { ); } + self.draw_whip(ctx); + sidebar::draw(&self.ecs, ctx); } } @@ -65,6 +68,29 @@ impl State { 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::(); @@ -94,8 +120,14 @@ impl State { let map = self.ecs.read_resource::(); let mut stats = self.ecs.write_resource::(); let mut sound_system = 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; + } - for (player, pos) in (&mut players, &mut positions).join() { let now = Instant::now(); if now - player.last_moved > Duration::from_secs_f32(PLAYER_STEP_PERIOD) { let destination = Point { @@ -137,12 +169,52 @@ impl State { } } + pub fn draw_whip(&self, ctx: &mut BTerm) { + let positions = self.ecs.read_storage::(); + let wants_to_whips = self.ecs.read_storage::(); + 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 frame_data = match wants_to_whip.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 { + 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 map_indexing_system = MapIndexingSystem {}; map_indexing_system.run_now(&self.ecs); + + 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(); - self.ecs.write_resource::().try_tick(); + + let mut clock = self.ecs.write_resource::(); + if !self.ecs.read_resource::().0 { + clock.try_tick(); + } else { + clock.reset(); + } } } diff --git a/src/systems/mod.rs b/src/systems/mod.rs index a3880b6..477f4e3 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -1,5 +1,7 @@ pub mod map_indexing_system; pub mod monster_ai_system; +pub mod whip_system; pub use map_indexing_system::MapIndexingSystem; pub use monster_ai_system::MonsterAiSystem; +pub use whip_system::WhipSystem; diff --git a/src/systems/whip_system.rs b/src/systems/whip_system.rs new file mode 100644 index 0000000..d22c764 --- /dev/null +++ b/src/systems/whip_system.rs @@ -0,0 +1,116 @@ +use std::time::{Duration, Instant}; + +use bracket_lib::prelude::*; +use specs::prelude::*; + +use crate::{ + components::{Monster, Position, WantsToWhip}, + resources::{Map, SoundEffects, SoundOutput, StopClock}, +}; + +pub struct WhipSystem {} + +#[allow(clippy::type_complexity)] +impl<'a> System<'a> for WhipSystem { + type SystemData = ( + WriteExpect<'a, Map>, + WriteExpect<'a, StopClock>, + WriteExpect<'a, SoundOutput>, + ReadExpect<'a, SoundEffects>, + ReadStorage<'a, Position>, + WriteStorage<'a, WantsToWhip>, + WriteStorage<'a, Monster>, + Entities<'a>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + map, + mut stop_clock, + mut sound_output, + sound_effects, + positions, + mut wants_to_whips, + monsters, + entities, + ) = data; + + let mut entities_to_remove: Vec = vec![]; + + for (entity, position, mut wants_to_whip) in + (&entities, &positions, &mut wants_to_whips).join() + { + stop_clock.0 = true; + let now = Instant::now(); + if now - wants_to_whip.last_frame > Duration::from_secs_f32(0.1) { + let destination = match wants_to_whip.frame { + 0 => Some(Point { + x: position.x - 1, + y: position.y - 1, + }), + 1 => Some(Point { + x: position.x - 1, + y: position.y, + }), + 2 => Some(Point { + x: position.x - 1, + y: position.y + 1, + }), + 3 => Some(Point { + x: position.x, + y: position.y + 1, + }), + 4 => Some(Point { + x: position.x + 1, + y: position.y + 1, + }), + 5 => Some(Point { + x: position.x + 1, + y: position.y, + }), + 6 => Some(Point { + x: position.x + 1, + y: position.y - 1, + }), + 7 => Some(Point { + x: position.x, + y: position.y - 1, + }), + _ => None, + }; + + if let Some(dest) = destination { + if map.in_bounds(dest) { + if let Some(e) = map.get_tile_content(map.point2d_to_index(dest)) { + if let Some(_monster) = monsters.get(e) { + let _ = entities.delete(e); + if let Some(sound) = &mut wants_to_whip.sound { + sound.control::, _>().stop(); + } + wants_to_whip.sound = Some( + sound_output.play_sound(sound_effects.whipping_hit.clone()), + ); + } + } + } + } + + if wants_to_whip.frame < 7 { + (*wants_to_whip).frame += 1; + wants_to_whip.last_frame = now; + } else { + entities_to_remove.push(entity); + stop_clock.0 = false; + if let Some(sound) = &mut wants_to_whip.sound { + sound.control::, _>().stop(); + wants_to_whip.sound = None; + } + } + } + } + + for entity in entities_to_remove { + wants_to_whips.remove(entity); + } + } +}