cleans up particles, and SFX/ambience w/ placeholders

This commit is contained in:
Llywelwyn 2023-10-12 08:05:17 +01:00
parent 465cd51a60
commit e8d376fecf
5 changed files with 142 additions and 67 deletions

View file

@ -38,56 +38,61 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
}; };
target_pool.hit_points.current -= ((amount as f32) * mult) as i32; target_pool.hit_points.current -= ((amount as f32) * mult) as i32;
let bleeders = ecs.read_storage::<Bleeds>(); let bleeders = ecs.read_storage::<Bleeds>();
if target_pool.hit_points.current < 1 { // If the target bleeds, handle bloodstains and use the bleed colour for sfx.
super::DEAD_ENTITIES.lock().unwrap().push_back(target); if let Some(bleeds) = bleeders.get(target) {
add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target }); if target_pool.hit_points.current < 1 {
for i in 0..3 { super::DEAD_ENTITIES.lock().unwrap().push_back(target);
let sprite = ( add_effect(damage.source, EffectType::EntityDeath, Targets::Entity {
match i { target,
0 => "explode1", });
1 => "explode2", for i in 0..3 {
_ => "explode3", let sprite = (
} match i {
).to_string(); 0 => "explode1",
add_effect( 1 => "explode2",
None, _ => "explode3",
EffectType::Particle { }
glyph: to_cp437('‼'), ).to_string();
sprite, add_effect(
fg: RGB::named(RED), None,
lifespan: 75.0, EffectType::Particle {
delay: 75.0 * (i as f32), glyph: to_cp437('‼'),
}, sprite,
Targets::Entity { target } fg: bleeds.colour,
); lifespan: 75.0,
if let Some(bleeds) = bleeders.get(target) { delay: 75.0 * (i as f32),
},
Targets::Entity { target }
);
add_effect( add_effect(
None, None,
EffectType::Bloodstain { colour: bleeds.colour }, EffectType::Bloodstain { colour: bleeds.colour },
Targets::Entity { target } Targets::Entity { target }
); );
} }
} } else {
} else { // Regular damage taken effect - use damagetype to determine which one to play.
// Regular damage taken effect - use damagetype to determine which one to play. add_effect(
add_effect( None,
None, EffectType::Particle {
EffectType::Particle { glyph: to_cp437('‼'),
glyph: to_cp437('‼'), sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES
sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES fg: RGB::named(ORANGE),
fg: RGB::named(ORANGE), lifespan: DEFAULT_PARTICLE_LIFETIME,
lifespan: DEFAULT_PARTICLE_LIFETIME, delay: 0.0,
delay: 0.0, },
}, Targets::Entity { target }
Targets::Entity { target } );
);
if let Some(bleeds) = bleeders.get(target) {
add_effect( add_effect(
None, None,
EffectType::Bloodstain { colour: bleeds.colour }, EffectType::Bloodstain { colour: bleeds.colour },
Targets::Entity { target } Targets::Entity { target }
); );
} }
} else {
// TODO: Damage taken particle effects when the target does not bleed.
// Also damage types, etc.
return;
} }
} }
} }

View file

@ -2,6 +2,7 @@ use super::BUC;
use crate::spatial; use crate::spatial;
use bracket_lib::prelude::*; use bracket_lib::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use notan::prelude::*;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Mutex; use std::sync::Mutex;
use crate::components::*; use crate::components::*;
@ -13,6 +14,7 @@ mod targeting;
mod triggers; mod triggers;
mod attr; mod attr;
mod intrinsics; mod intrinsics;
pub mod sound;
pub use targeting::aoe_tiles; pub use targeting::aoe_tiles;
@ -63,6 +65,9 @@ pub enum EffectType {
TriggerFire { TriggerFire {
trigger: Entity, trigger: Entity,
}, },
Sound {
sound: String,
},
} }
#[derive(Clone)] #[derive(Clone)]
@ -95,7 +100,7 @@ pub fn add_effect(source: Option<Entity>, effect_type: EffectType, target: Targe
} }
/// Iterates through the effects queue, applying each effect to their target. /// Iterates through the effects queue, applying each effect to their target.
pub fn run_effects_queue(ecs: &mut World) { pub fn run_effects_queue(app: &mut App, ecs: &mut World) {
// First removes any effect in the EFFECT_QUEUE with a dead entity as its source. // First removes any effect in the EFFECT_QUEUE with a dead entity as its source.
loop { loop {
let dead_entity: Option<Entity> = DEAD_ENTITIES.lock().unwrap().pop_front(); let dead_entity: Option<Entity> = DEAD_ENTITIES.lock().unwrap().pop_front();
@ -111,7 +116,7 @@ pub fn run_effects_queue(ecs: &mut World) {
loop { loop {
let effect: Option<EffectSpawner> = EFFECT_QUEUE.lock().unwrap().pop_front(); let effect: Option<EffectSpawner> = EFFECT_QUEUE.lock().unwrap().pop_front();
if let Some(effect) = effect { if let Some(effect) = effect {
target_applicator(ecs, &effect); target_applicator(app, ecs, &effect);
} else { } else {
break; break;
} }
@ -119,7 +124,7 @@ pub fn run_effects_queue(ecs: &mut World) {
} }
/// Applies an effect to the correct target(s). /// Applies an effect to the correct target(s).
fn target_applicator(ecs: &mut World, effect: &EffectSpawner) { fn target_applicator(app: &mut App, ecs: &mut World, effect: &EffectSpawner) {
// Item use is handled differently - it creates other effects with itself // Item use is handled differently - it creates other effects with itself
// as the source, passing all effects attached to the item into the queue. // as the source, passing all effects attached to the item into the queue.
if let EffectType::ItemUse { item } = effect.effect_type { if let EffectType::ItemUse { item } = effect.effect_type {
@ -131,25 +136,26 @@ fn target_applicator(ecs: &mut World, effect: &EffectSpawner) {
} }
// Otherwise, just match the effect and enact it directly. // Otherwise, just match the effect and enact it directly.
match &effect.target { match &effect.target {
Targets::Tile { target } => affect_tile(ecs, effect, *target), Targets::Tile { target } => affect_tile(app, ecs, effect, *target),
Targets::TileList { targets } => Targets::TileList { targets } =>
targets.iter().for_each(|target| affect_tile(ecs, effect, *target)), targets.iter().for_each(|target| affect_tile(app, ecs, effect, *target)),
Targets::Entity { target } => affect_entity(ecs, effect, *target), Targets::Entity { target } => affect_entity(app, ecs, effect, *target),
Targets::EntityList { targets } => Targets::EntityList { targets } =>
targets.iter().for_each(|target| affect_entity(ecs, effect, *target)), targets.iter().for_each(|target| affect_entity(app, ecs, effect, *target)),
} }
} }
/// Runs an effect on a given tile index /// Runs an effect on a given tile index
fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) { fn affect_tile(app: &mut App, ecs: &mut World, effect: &EffectSpawner, target: usize) {
if tile_effect_hits_entities(&effect.effect_type) { if tile_effect_hits_entities(&effect.effect_type) {
spatial::for_each_tile_content(target, |entity| { spatial::for_each_tile_content(target, |entity| {
affect_entity(ecs, effect, entity); affect_entity(app, ecs, effect, entity);
}); });
} }
match &effect.effect_type { match &effect.effect_type {
EffectType::Particle { .. } => particles::particle_to_tile(ecs, target as i32, &effect), EffectType::Particle { .. } => particles::particle_to_tile(ecs, target as i32, &effect),
EffectType::Sound { .. } => sound::play_sound(app, ecs, &effect, target),
_ => {} _ => {}
} }
// Run the effect // Run the effect
@ -168,7 +174,7 @@ fn tile_effect_hits_entities(effect: &EffectType) -> bool {
} }
/// Runs an effect on a given entity /// Runs an effect on a given entity
fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) { fn affect_entity(app: &mut App, ecs: &mut World, effect: &EffectSpawner, target: Entity) {
match &effect.effect_type { match &effect.effect_type {
EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target), EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target),
EffectType::Healing { .. } => damage::heal_damage(ecs, effect, target), EffectType::Healing { .. } => damage::heal_damage(ecs, effect, target),
@ -187,6 +193,11 @@ fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
EffectType::EntityDeath => damage::entity_death(ecs, effect, target), EffectType::EntityDeath => damage::entity_death(ecs, effect, target),
EffectType::ModifyNutrition { .. } => hunger::modify_nutrition(ecs, effect, target), EffectType::ModifyNutrition { .. } => hunger::modify_nutrition(ecs, effect, target),
EffectType::AddIntrinsic { .. } => intrinsics::add_intrinsic(ecs, effect, target), EffectType::AddIntrinsic { .. } => intrinsics::add_intrinsic(ecs, effect, target),
EffectType::Sound { .. } => {
if let Some(pos) = targeting::entity_position(ecs, target) {
sound::play_sound(app, ecs, &effect, pos);
}
}
_ => {} _ => {}
} }
} }

65
src/effects/sound.rs Normal file
View file

@ -0,0 +1,65 @@
use bracket_lib::prelude::*;
use notan::prelude::*;
use specs::prelude::*;
use std::sync::Mutex;
use std::collections::HashMap;
use super::{ EffectSpawner, EffectType };
use crate::Map;
lazy_static::lazy_static! {
pub static ref SOUNDS: Mutex<HashMap<String, (AudioSource, AudioType)>> = Mutex::new(HashMap::new());
pub static ref VOLUME: Mutex<f32> = Mutex::new(1.0);
}
#[derive(PartialEq, Copy, Clone)]
pub enum AudioType {
Ambient,
SFX,
}
pub fn play_sound(app: &mut App, ecs: &mut World, effect: &EffectSpawner, target: usize) {
// Extract sound from the EffectType, or panic if we somehow called this with the wrong effect.
let sound = if let EffectType::Sound { sound } = &effect.effect_type {
sound
} else {
unreachable!("add_intrinsic() called with the wrong EffectType")
};
// Fetch all the relevant precursors.
let sounds = SOUNDS.lock().unwrap();
let volume = VOLUME.lock().unwrap();
let source = sounds.get(sound).unwrap();
let (vol, repeat) = match source.1 {
AudioType::Ambient => (*volume * 0.5, true),
AudioType::SFX => {
let map = ecs.fetch::<Map>();
let ppos = ecs.fetch::<Point>();
// Calc distance from player to target.
let dist = DistanceAlg::PythagorasSquared.distance2d(
*ppos,
Point::new((target as i32) % map.width, (target as i32) / map.width)
);
// Play sound at volume proportional to distance.
(*volume * (1.0 - (dist as f32) / 14.0), false)
}
};
// Play the sound.
app.audio.play_sound(&source.0, vol, repeat);
}
pub fn init_sounds(app: &mut App) {
let list: Vec<(&str, (&[u8], AudioType))> = vec![
//key, (bytes, type) - audiotype determines final volume, looping, etc.
("hit", (include_bytes!("../../resources/sounds/hit.wav"), AudioType::Ambient)),
("other", (include_bytes!("../../resources/sounds/hit.wav"), AudioType::SFX)),
("another", (include_bytes!("../../resources/sounds/hit.wav"), AudioType::SFX))
];
let mut sounds = SOUNDS.lock().unwrap();
for (k, (bytes, audiotype)) in list.iter() {
sounds.insert(k.to_string(), (app.audio.create_source(bytes).unwrap(), *audiotype));
}
}
pub fn set_volume(vol: f32) {
let mut volume = VOLUME.lock().unwrap();
*volume = vol;
}

View file

@ -33,14 +33,8 @@ fn main() -> Result<(), String> {
.build() .build()
} }
fn setup(_app: &mut App, gfx: &mut Graphics) -> State { fn setup(app: &mut App, gfx: &mut Graphics) -> State {
/* effects::sound::init_sounds(app);
let sound = app.audio.create_source(include_bytes!("../resources/sounds/hit.wav")).unwrap();
let sounds: HashMap<String, AudioSource> = vec![("hit".to_string(), sound)]
.into_iter()
.collect();
*/
let texture = gfx let texture = gfx
.create_texture() .create_texture()
.from_image(include_bytes!("../resources/atlas.png")) .from_image(include_bytes!("../resources/atlas.png"))

View file

@ -85,23 +85,23 @@ impl State {
} }
} }
fn run_systems(&mut self) { fn run_systems(&mut self, ctx: &mut App) {
let mut hunger_clock = hunger_system::HungerSystem {}; let mut hunger_clock = hunger_system::HungerSystem {};
let mut particle_system = particle_system::ParticleSpawnSystem {}; let mut particle_system = particle_system::ParticleSpawnSystem {};
// Order is *very* important here, to ensure effects take place in the right order, // Order is *very* important here, to ensure effects take place in the right order,
// and that the player/AI are making decisions based on the most up-to-date info. // and that the player/AI are making decisions based on the most up-to-date info.
self.resolve_entity_decisions(); // Push Player messages of intent to effects queue, and run it. self.resolve_entity_decisions(ctx); // Push Player messages of intent to effects queue, and run it.
self.refresh_indexes(); // Get up-to-date map and viewsheds prior to AI turn. self.refresh_indexes(); // Get up-to-date map and viewsheds prior to AI turn.
self.run_ai(); // Get AI decision-making. self.run_ai(); // Get AI decision-making.
self.resolve_entity_decisions(); // Push AI messages of intent to effects queue, and run it. self.resolve_entity_decisions(ctx); // Push AI messages of intent to effects queue, and run it.
hunger_clock.run_now(&self.ecs); // Tick the hunger clock (on the turn clock's turn) hunger_clock.run_now(&self.ecs); // Tick the hunger clock (on the turn clock's turn)
particle_system.run_now(&self.ecs); // Spawn/delete particles (turn independent) particle_system.run_now(&self.ecs); // Spawn/delete particles (turn independent)
self.ecs.maintain(); self.ecs.maintain();
} }
fn resolve_entity_decisions(&mut self) { fn resolve_entity_decisions(&mut self, ctx: &mut App) {
let mut trigger_system = trigger_system::TriggerSystem {}; let mut trigger_system = trigger_system::TriggerSystem {};
let mut item_equip_system = inventory::ItemEquipSystem {}; let mut item_equip_system = inventory::ItemEquipSystem {};
let mut item_use_system = inventory::ItemUseSystem {}; let mut item_use_system = inventory::ItemUseSystem {};
@ -121,7 +121,7 @@ impl State {
key_system.run_now(&self.ecs); key_system.run_now(&self.ecs);
melee_system.run_now(&self.ecs); melee_system.run_now(&self.ecs);
effects::run_effects_queue(&mut self.ecs); effects::run_effects_queue(ctx, &mut self.ecs);
} }
fn refresh_indexes(&mut self) { fn refresh_indexes(&mut self) {
@ -207,13 +207,13 @@ impl State {
particle_system::particle_ticker(&mut self.ecs, ctx); particle_system::particle_ticker(&mut self.ecs, ctx);
match new_runstate { match new_runstate {
RunState::PreRun => { RunState::PreRun => {
self.run_systems(); self.run_systems(ctx);
self.ecs.maintain(); self.ecs.maintain();
new_runstate = RunState::AwaitingInput; new_runstate = RunState::AwaitingInput;
} }
RunState::AwaitingInput => { RunState::AwaitingInput => {
self.refresh_indexes(); self.refresh_indexes();
effects::run_effects_queue(&mut self.ecs); effects::run_effects_queue(ctx, &mut self.ecs);
let mut can_act = false; let mut can_act = false;
{ {
let player_entity = self.ecs.fetch::<Entity>(); let player_entity = self.ecs.fetch::<Entity>();
@ -231,7 +231,7 @@ impl State {
} }
RunState::Ticking => { RunState::Ticking => {
while new_runstate == RunState::Ticking && particle_system::check_queue(&self.ecs) { while new_runstate == RunState::Ticking && particle_system::check_queue(&self.ecs) {
self.run_systems(); self.run_systems(ctx);
self.ecs.maintain(); self.ecs.maintain();
try_spawn_interval(&mut self.ecs); try_spawn_interval(&mut self.ecs);
maybe_map_message(&mut self.ecs); maybe_map_message(&mut self.ecs);
@ -585,7 +585,7 @@ impl State {
match new_runstate { match new_runstate {
RunState::PreRun => { RunState::PreRun => {
self.run_systems(); //self.run_systems();
self.ecs.maintain(); self.ecs.maintain();
new_runstate = RunState::AwaitingInput; new_runstate = RunState::AwaitingInput;
} }
@ -594,7 +594,7 @@ impl State {
// still be in the queue, just to make 100% sure that // still be in the queue, just to make 100% sure that
// there are no lingering effects from the last tick. // there are no lingering effects from the last tick.
self.refresh_indexes(); self.refresh_indexes();
effects::run_effects_queue(&mut self.ecs); //effects::run_effects_queue(&mut self.ecs);
// Sanity-checking that the player actually *should* // Sanity-checking that the player actually *should*
// be taking a turn before giving them one. If they // be taking a turn before giving them one. If they
// don't have a turn component, go back to ticking. // don't have a turn component, go back to ticking.
@ -615,7 +615,7 @@ impl State {
} }
RunState::Ticking => { RunState::Ticking => {
while new_runstate == RunState::Ticking && particle_system::check_queue(&self.ecs) { while new_runstate == RunState::Ticking && particle_system::check_queue(&self.ecs) {
self.run_systems(); //self.run_systems();
self.ecs.maintain(); self.ecs.maintain();
try_spawn_interval(&mut self.ecs); try_spawn_interval(&mut self.ecs);
maybe_map_message(&mut self.ecs); maybe_map_message(&mut self.ecs);