From 89f6470ec07d899e0126acb891a942d498d8f775 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Mon, 31 Jan 2022 11:53:38 -0500 Subject: [PATCH] Create ParticleProjectile effect, and add to ranged attacks --- src/components.rs | 9 +++++++++ src/effects.rs | 11 +++++++++- src/effects/particles.rs | 40 +++++++++++++++++++++++++++++++++++++ src/gui.rs | 40 +++++++++++++++++++++++-------------- src/particle_system.rs | 23 ++++++++++++++++----- src/player.rs | 10 +++++++--- src/ranged_combat_system.rs | 40 ++++++++++++++++++------------------- src/state.rs | 2 +- 8 files changed, 130 insertions(+), 45 deletions(-) diff --git a/src/components.rs b/src/components.rs index 62138e2..cd5c9f0 100644 --- a/src/components.rs +++ b/src/components.rs @@ -167,9 +167,18 @@ pub struct Wearable { pub slot: EquipmentSlot, } +#[derive(Serialize, Deserialize, Clone)] +pub struct ParticleAnimation { + pub step_time: f32, + pub path: Vec, + pub current_step: usize, + pub timer: f32, +} + #[derive(Component, Serialize, Deserialize, Clone)] pub struct ParticleLifetime { pub lifetime_ms: f32, + pub animation: Option, } #[derive(Component, Serialize, Deserialize, Clone)] diff --git a/src/effects.rs b/src/effects.rs index 4dfb883..333971c 100644 --- a/src/effects.rs +++ b/src/effects.rs @@ -8,7 +8,7 @@ mod triggers; use std::collections::{HashSet, VecDeque}; use std::sync::Mutex; -use ::rltk::{FontCharType, RGB}; +use ::rltk::{FontCharType, Point, RGB}; use ::specs::prelude::*; pub use targeting::*; @@ -31,6 +31,14 @@ pub enum EffectType { bg: RGB, lifespan: f32, }, + ParticleProjectile { + glyph: FontCharType, + fg: RGB, + bg: RGB, + lifespan: f32, + speed: f32, + path: Vec, + }, EntityDeath, ItemUse { item: Entity, @@ -155,6 +163,7 @@ fn affect_tile(ecs: &mut World, effect: &mut EffectSpawner, tile_idx: i32) { match &effect.effect_type { EffectType::Bloodstain => damage::bloodstain(ecs, tile_idx), EffectType::Particle { .. } => particles::particle_to_tile(ecs, tile_idx, effect), + EffectType::ParticleProjectile { .. } => particles::projectile(ecs, tile_idx, effect), _ => {} } } diff --git a/src/effects/particles.rs b/src/effects/particles.rs index 25efea1..64a58b7 100644 --- a/src/effects/particles.rs +++ b/src/effects/particles.rs @@ -1,6 +1,7 @@ use ::specs::prelude::*; use super::{EffectSpawner, EffectType}; +use crate::components::{ParticleAnimation, ParticleLifetime, Position, Renderable}; use crate::map::Map; use crate::particle_system::ParticleBuilder; @@ -24,3 +25,42 @@ pub fn particle_to_tile(ecs: &mut World, tile_idx: i32, effect: &EffectSpawner) ); } } + +pub fn projectile(ecs: &mut World, tile_idx: i32, effect: &EffectSpawner) { + if let EffectType::ParticleProjectile { + glyph, + fg, + bg, + lifespan: _, + speed, + path, + } = &effect.effect_type + { + let x: i32; + let y: i32; + { + let map = ecs.fetch::(); + x = tile_idx % map.width; + y = tile_idx / map.width; + } + + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + fg: *fg, + bg: *bg, + glyph: *glyph, + render_order: 0, + }) + .with(ParticleLifetime { + lifetime_ms: path.len() as f32 * speed, + animation: Some(ParticleAnimation { + step_time: *speed, + path: path.to_vec(), + current_step: 0, + timer: 0.0, + }), + }) + .build(); + } +} diff --git a/src/gui.rs b/src/gui.rs index 0262dfb..991ede2 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -219,21 +219,31 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { y += 1; if let Some(weapon) = weapon.get(entity) { - let mut weapon_info = if weapon.damage_bonus < 0 { - format!( - "┤ {} ({}d{}{})", - &name, weapon.damage_n_dice, weapon.damage_die_type, weapon.damage_bonus - ) - } else if weapon.damage_bonus == 0 { - format!( - "┤ {} ({}d{})", - &name, weapon.damage_n_dice, weapon.damage_die_type - ) - } else { - format!( - "┤ {} ({}d{}+{})", - &name, weapon.damage_n_dice, weapon.damage_die_type, weapon.damage_bonus - ) + let mut weapon_info = match weapon.damage_bonus.cmp(&0) { + Ordering::Less => { + format!( + "┤ {} ({}d{}{})", + &name, + weapon.damage_n_dice, + weapon.damage_die_type, + weapon.damage_bonus + ) + } + Ordering::Equal => { + format!( + "┤ {} ({}d{})", + &name, weapon.damage_n_dice, weapon.damage_die_type + ) + } + Ordering::Greater => { + format!( + "┤ {} ({}d{}+{})", + &name, + weapon.damage_n_dice, + weapon.damage_die_type, + weapon.damage_bonus + ) + } }; if let Some(range) = weapon.range { diff --git a/src/particle_system.rs b/src/particle_system.rs index 53cefbd..f34f14e 100644 --- a/src/particle_system.rs +++ b/src/particle_system.rs @@ -1,16 +1,29 @@ use ::rltk::{Rltk, RGB}; use ::specs::prelude::*; -use crate::components::{ParticleLifetime, Renderable}; -use crate::Position; +use crate::components::{ParticleLifetime, Position, Renderable}; -pub fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) { +pub fn update_particles(ecs: &mut World, ctx: &Rltk) { let mut dead_particles: Vec = Vec::new(); { // Age out particles let mut particles = ecs.write_storage::(); let entities = ecs.entities(); for (entity, mut particle) in (&entities, &mut particles).join() { + if let Some(animation) = &mut particle.animation { + animation.timer += ctx.frame_time_ms; + if animation.timer > animation.step_time + && animation.current_step < animation.path.len() - 2 + { + animation.current_step += 1; + + if let Some(pos) = ecs.write_storage::().get_mut(entity) { + pos.x = animation.path[animation.current_step].x; + pos.y = animation.path[animation.current_step].y; + } + } + } + particle.lifetime_ms -= ctx.frame_time_ms; if particle.lifetime_ms < 0.0 { @@ -20,8 +33,7 @@ pub fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) { } for dead in dead_particles.iter() { - ecs.delete_entity(*dead) - .expect("Failed to delete dead particle entity"); + ecs.delete_entity(*dead).expect("Particle will not die."); } } @@ -109,6 +121,7 @@ impl<'a> System<'a> for ParticleSpawnSystem { p, ParticleLifetime { lifetime_ms: new_particle.lifetime, + animation: None, }, ) .expect("Failed to insert Lifetime of new particle"); diff --git a/src/player.rs b/src/player.rs index 6036286..c5ef998 100644 --- a/src/player.rs +++ b/src/player.rs @@ -404,14 +404,18 @@ fn cycle_target(ecs: &mut World) { let mut index = 0; for (i, target) in possible_targets.iter().enumerate() { if target.1 == current_target { - index = 1; + index = i; } } if index > possible_targets.len() - 2 { - targets.insert(possible_targets[0].1, Target {}); + targets + .insert(possible_targets[0].1, Target {}) + .expect("Failed to insert Target tag"); } else { - targets.insert(possible_targets[index + 1].1, Target {}); + targets + .insert(possible_targets[index + 1].1, Target {}) + .expect("Failed to insert Target tag"); } } } diff --git a/src/ranged_combat_system.rs b/src/ranged_combat_system.rs index 7f0a5ed..07d48de 100644 --- a/src/ranged_combat_system.rs +++ b/src/ranged_combat_system.rs @@ -69,26 +69,26 @@ impl<'a> System<'a> for RangedCombatSystem { let target_name = names.get(wants_shoot.target).unwrap(); // Fire projectile effect - // let apos = positions.get(entity).unwrap(); - // let dpos = positions.get(wants_shoot.target).unwrap(); - // add_effect( - // None, - // EffectType::ParticleProjectile { - // glyph: to_cp437('*'), - // fg: colors::CYAN, - // bg: colors::BLACK, - // lifespan: 300.0, - // speed: 50.0, - // path: ::rltk::line2d( - // LineAlg::Bresenham, - // Point::from(*apos), - // Point::from(*dpos), - // ), - // }, - // Targets::Tile { - // tile_idx: map.xy_idx(apos.x, apos.y) as i32, - // }, - // ); + let apos = positions.get(entity).unwrap(); + let dpos = positions.get(wants_shoot.target).unwrap(); + add_effect( + None, + EffectType::ParticleProjectile { + glyph: to_cp437('*'), + fg: colors::CYAN, + bg: colors::BLACK, + lifespan: 300.0, + speed: 50.0, + path: ::rltk::line2d( + LineAlg::Bresenham, + Point::from(*apos), + Point::from(*dpos), + ), + }, + Targets::Tile { + tile_idx: map.xy_idx(apos.x, apos.y) as i32, + }, + ); // Define the basic unarmed attack -- overridden by wielding check below if a weapon is equipped let mut weapon_info = Weapon { diff --git a/src/state.rs b/src/state.rs index 4291a95..2e0e143 100644 --- a/src/state.rs +++ b/src/state.rs @@ -176,7 +176,7 @@ impl GameState for State { } ctx.cls(); - particle_system::cull_dead_particles(&mut self.ecs, ctx); + particle_system::update_particles(&mut self.ecs, ctx); match newrunstate { RunState::MainMenu { .. } => {}