cleans up particles, and SFX/ambience w/ placeholders
This commit is contained in:
parent
465cd51a60
commit
e8d376fecf
5 changed files with 142 additions and 67 deletions
|
|
@ -38,9 +38,13 @@ 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 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 });
|
||||
add_effect(damage.source, EffectType::EntityDeath, Targets::Entity {
|
||||
target,
|
||||
});
|
||||
for i in 0..3 {
|
||||
let sprite = (
|
||||
match i {
|
||||
|
|
@ -54,20 +58,18 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
|
|||
EffectType::Particle {
|
||||
glyph: to_cp437('‼'),
|
||||
sprite,
|
||||
fg: RGB::named(RED),
|
||||
fg: bleeds.colour,
|
||||
lifespan: 75.0,
|
||||
delay: 75.0 * (i as f32),
|
||||
},
|
||||
Targets::Entity { target }
|
||||
);
|
||||
if let Some(bleeds) = bleeders.get(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(
|
||||
|
|
@ -81,13 +83,16 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
|
|||
},
|
||||
Targets::Entity { target }
|
||||
);
|
||||
if let Some(bleeds) = bleeders.get(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
65
src/effects/sound.rs
Normal 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;
|
||||
}
|
||||
10
src/main.rs
10
src/main.rs
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue