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;
let bleeders = ecs.read_storage::<Bleeds>();
if target_pool.hit_points.current < 1 {
super::DEAD_ENTITIES.lock().unwrap().push_back(target);
add_effect(damage.source, EffectType::EntityDeath, Targets::Entity { target });
for i in 0..3 {
let sprite = (
match i {
0 => "explode1",
1 => "explode2",
_ => "explode3",
}
).to_string();
add_effect(
None,
EffectType::Particle {
glyph: to_cp437('‼'),
sprite,
fg: RGB::named(RED),
lifespan: 75.0,
delay: 75.0 * (i as f32),
},
Targets::Entity { target }
);
if let Some(bleeds) = bleeders.get(target) {
// If the target bleeds, handle bloodstains and use the bleed colour for sfx.
if let Some(bleeds) = bleeders.get(target) {
if target_pool.hit_points.current < 1 {
super::DEAD_ENTITIES.lock().unwrap().push_back(target);
add_effect(damage.source, EffectType::EntityDeath, Targets::Entity {
target,
});
for i in 0..3 {
let sprite = (
match i {
0 => "explode1",
1 => "explode2",
_ => "explode3",
}
).to_string();
add_effect(
None,
EffectType::Particle {
glyph: to_cp437('‼'),
sprite,
fg: bleeds.colour,
lifespan: 75.0,
delay: 75.0 * (i as f32),
},
Targets::Entity { target }
);
add_effect(
None,
EffectType::Bloodstain { colour: bleeds.colour },
Targets::Entity { target }
);
}
}
} else {
// Regular damage taken effect - use damagetype to determine which one to play.
add_effect(
None,
EffectType::Particle {
glyph: to_cp437('‼'),
sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES
fg: RGB::named(ORANGE),
lifespan: DEFAULT_PARTICLE_LIFETIME,
delay: 0.0,
},
Targets::Entity { target }
);
if let Some(bleeds) = bleeders.get(target) {
} else {
// Regular damage taken effect - use damagetype to determine which one to play.
add_effect(
None,
EffectType::Particle {
glyph: to_cp437('‼'),
sprite: "gnome".to_string(), // FIXME: REMOVE THE GNOMES
fg: RGB::named(ORANGE),
lifespan: DEFAULT_PARTICLE_LIFETIME,
delay: 0.0,
},
Targets::Entity { target }
);
add_effect(
None,
EffectType::Bloodstain { colour: bleeds.colour },
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 bracket_lib::prelude::*;
use specs::prelude::*;
use notan::prelude::*;
use std::collections::VecDeque;
use std::sync::Mutex;
use crate::components::*;
@ -13,6 +14,7 @@ mod targeting;
mod triggers;
mod attr;
mod intrinsics;
pub mod sound;
pub use targeting::aoe_tiles;
@ -63,6 +65,9 @@ pub enum EffectType {
TriggerFire {
trigger: Entity,
},
Sound {
sound: String,
},
}
#[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.
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.
loop {
let dead_entity: Option<Entity> = DEAD_ENTITIES.lock().unwrap().pop_front();
@ -111,7 +116,7 @@ pub fn run_effects_queue(ecs: &mut World) {
loop {
let effect: Option<EffectSpawner> = EFFECT_QUEUE.lock().unwrap().pop_front();
if let Some(effect) = effect {
target_applicator(ecs, &effect);
target_applicator(app, ecs, &effect);
} else {
break;
}
@ -119,7 +124,7 @@ pub fn run_effects_queue(ecs: &mut World) {
}
/// 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
// as the source, passing all effects attached to the item into the queue.
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.
match &effect.target {
Targets::Tile { target } => affect_tile(ecs, effect, *target),
Targets::Tile { target } => affect_tile(app, ecs, effect, *target),
Targets::TileList { targets } =>
targets.iter().for_each(|target| affect_tile(ecs, effect, *target)),
Targets::Entity { target } => affect_entity(ecs, effect, *target),
targets.iter().for_each(|target| affect_tile(app, ecs, effect, *target)),
Targets::Entity { target } => affect_entity(app, ecs, effect, *target),
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
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) {
spatial::for_each_tile_content(target, |entity| {
affect_entity(ecs, effect, entity);
affect_entity(app, ecs, effect, entity);
});
}
match &effect.effect_type {
EffectType::Particle { .. } => particles::particle_to_tile(ecs, target as i32, &effect),
EffectType::Sound { .. } => sound::play_sound(app, ecs, &effect, target),
_ => {}
}
// Run the effect
@ -168,7 +174,7 @@ fn tile_effect_hits_entities(effect: &EffectType) -> bool {
}
/// 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 {
EffectType::Damage { .. } => damage::inflict_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::ModifyNutrition { .. } => hunger::modify_nutrition(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()
}
fn setup(_app: &mut App, gfx: &mut Graphics) -> State {
/*
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();
*/
fn setup(app: &mut App, gfx: &mut Graphics) -> State {
effects::sound::init_sounds(app);
let texture = gfx
.create_texture()
.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 particle_system = particle_system::ParticleSpawnSystem {};
// 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.
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.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)
particle_system.run_now(&self.ecs); // Spawn/delete particles (turn independent)
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 item_equip_system = inventory::ItemEquipSystem {};
let mut item_use_system = inventory::ItemUseSystem {};
@ -121,7 +121,7 @@ impl State {
key_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) {
@ -207,13 +207,13 @@ impl State {
particle_system::particle_ticker(&mut self.ecs, ctx);
match new_runstate {
RunState::PreRun => {
self.run_systems();
self.run_systems(ctx);
self.ecs.maintain();
new_runstate = RunState::AwaitingInput;
}
RunState::AwaitingInput => {
self.refresh_indexes();
effects::run_effects_queue(&mut self.ecs);
effects::run_effects_queue(ctx, &mut self.ecs);
let mut can_act = false;
{
let player_entity = self.ecs.fetch::<Entity>();
@ -231,7 +231,7 @@ impl State {
}
RunState::Ticking => {
while new_runstate == RunState::Ticking && particle_system::check_queue(&self.ecs) {
self.run_systems();
self.run_systems(ctx);
self.ecs.maintain();
try_spawn_interval(&mut self.ecs);
maybe_map_message(&mut self.ecs);
@ -585,7 +585,7 @@ impl State {
match new_runstate {
RunState::PreRun => {
self.run_systems();
//self.run_systems();
self.ecs.maintain();
new_runstate = RunState::AwaitingInput;
}
@ -594,7 +594,7 @@ impl State {
// still be in the queue, just to make 100% sure that
// there are no lingering effects from the last tick.
self.refresh_indexes();
effects::run_effects_queue(&mut self.ecs);
//effects::run_effects_queue(&mut self.ecs);
// Sanity-checking that the player actually *should*
// be taking a turn before giving them one. If they
// don't have a turn component, go back to ticking.
@ -615,7 +615,7 @@ impl State {
}
RunState::Ticking => {
while new_runstate == RunState::Ticking && particle_system::check_queue(&self.ecs) {
self.run_systems();
//self.run_systems();
self.ecs.maintain();
try_spawn_interval(&mut self.ecs);
maybe_map_message(&mut self.ecs);