Add particle effects for Melee combat

This commit is contained in:
Timothy Warren 2021-11-16 11:33:58 -05:00
parent c4bc637c79
commit 1dd5db42f7
7 changed files with 161 additions and 19 deletions

View File

@ -174,6 +174,11 @@ pub struct DefenseBonus {
pub defense: i32, pub defense: i32,
} }
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct ParticleLifetime {
pub lifetime_ms: f32,
}
// Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an // Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an
// Entity. // Entity.

View File

@ -12,6 +12,7 @@ mod map;
mod map_indexing_system; mod map_indexing_system;
mod melee_combat_system; mod melee_combat_system;
mod monster_ai_system; mod monster_ai_system;
mod particle_system;
mod player; mod player;
pub mod random_table; pub mod random_table;
mod rect; mod rect;
@ -101,6 +102,9 @@ impl State {
let mut item_remove = ItemRemoveSystem {}; let mut item_remove = ItemRemoveSystem {};
item_remove.run_now(&self.ecs); item_remove.run_now(&self.ecs);
let mut particles = particle_system::ParticleSpawnSystem {};
particles.run_now(&self.ecs);
self.ecs.maintain(); self.ecs.maintain();
} }
} }
@ -114,6 +118,7 @@ impl GameState for State {
} }
ctx.cls(); ctx.cls();
particle_system::cull_dead_particles(&mut self.ecs, ctx);
match newrunstate { match newrunstate {
RunState::MainMenu { .. } => {} RunState::MainMenu { .. } => {}
@ -485,6 +490,7 @@ fn main() -> rltk::BError {
MeleePowerBonus, MeleePowerBonus,
DefenseBonus, DefenseBonus,
WantsToRemoveItem, WantsToRemoveItem,
ParticleLifetime,
); );
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new()); gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
@ -506,6 +512,7 @@ fn main() -> rltk::BError {
menu_selection: gui::MainMenuSelection::NewGame, menu_selection: gui::MainMenuSelection::NewGame,
}); });
gs.ecs.insert(GameLog::new("Welcome to Rusty Roguelike")); gs.ecs.insert(GameLog::new("Welcome to Rusty Roguelike"));
gs.ecs.insert(particle_system::ParticleBuilder::new());
rltk::main_loop(context, gs) rltk::main_loop(context, gs)
} }

View File

@ -1,7 +1,8 @@
use crate::components::{ use crate::components::{
CombatStats, DefenseBonus, Equipped, MeleePowerBonus, Name, SufferDamage, WantsToMelee, CombatStats, DefenseBonus, Equipped, MeleePowerBonus, Name, SufferDamage, WantsToMelee,
}; };
use crate::game_log::GameLog; use crate::{game_log::GameLog, particle_system::ParticleBuilder, Position};
use rltk::RGB;
use specs::prelude::*; use specs::prelude::*;
pub struct MeleeCombatSystem {} pub struct MeleeCombatSystem {}
@ -18,6 +19,8 @@ impl<'a> System<'a> for MeleeCombatSystem {
ReadStorage<'a, MeleePowerBonus>, ReadStorage<'a, MeleePowerBonus>,
ReadStorage<'a, DefenseBonus>, ReadStorage<'a, DefenseBonus>,
ReadStorage<'a, Equipped>, ReadStorage<'a, Equipped>,
WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -31,6 +34,8 @@ impl<'a> System<'a> for MeleeCombatSystem {
melee_power_bonuses, melee_power_bonuses,
defense_bonuses, defense_bonuses,
equipped, equipped,
mut particle_builder,
positions,
) = data; ) = data;
for (entity, wants_melee, name, stats) in for (entity, wants_melee, name, stats) in
@ -59,6 +64,17 @@ impl<'a> System<'a> for MeleeCombatSystem {
} }
} }
if let Some(pos) = positions.get(wants_melee.target) {
particle_builder.request(
pos.x,
pos.y,
RGB::named(rltk::ORANGE),
RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
200.0,
);
}
let damage = i32::max( let damage = i32::max(
0, 0,
(stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus), (stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus),

118
src/particle_system.rs Normal file
View File

@ -0,0 +1,118 @@
use crate::components::{ParticleLifetime, Renderable};
use crate::Position;
use rltk::{Rltk, RGB};
use specs::prelude::*;
pub fn cull_dead_particles(ecs: &mut World, ctx: &Rltk) {
let mut dead_particles: Vec<Entity> = Vec::new();
{
// Age out particles
let mut particles = ecs.write_storage::<ParticleLifetime>();
let entities = ecs.entities();
for (entity, mut particle) in (&entities, &mut particles).join() {
particle.lifetime_ms -= ctx.frame_time_ms;
if particle.lifetime_ms < 0.0 {
dead_particles.push(entity);
}
}
}
for dead in dead_particles.iter() {
ecs.delete_entity(*dead)
.expect("Failed to delete dead particle entity");
}
}
struct ParticleRequest {
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: rltk::FontCharType,
lifetime: f32,
}
#[derive(Default)]
pub struct ParticleBuilder {
requests: Vec<ParticleRequest>,
}
impl ParticleBuilder {
pub fn new() -> ParticleBuilder {
ParticleBuilder::default()
}
pub fn request(
&mut self,
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: rltk::FontCharType,
lifetime: f32,
) {
self.requests.push(ParticleRequest {
x,
y,
fg,
bg,
glyph,
lifetime,
})
}
}
pub struct ParticleSpawnSystem {}
impl<'a> System<'a> for ParticleSpawnSystem {
#[allow(clippy::type_complexity)]
type SystemData = (
Entities<'a>,
WriteStorage<'a, Position>,
WriteStorage<'a, Renderable>,
WriteStorage<'a, ParticleLifetime>,
WriteExpect<'a, ParticleBuilder>,
);
fn run(&mut self, data: Self::SystemData) {
let (entities, mut positions, mut renderables, mut particles, mut particle_builder) = data;
for new_particle in particle_builder.requests.iter() {
let p = entities.create();
positions
.insert(
p,
Position {
x: new_particle.x,
y: new_particle.y,
},
)
.expect("Failed to insert Position of new particle");
renderables
.insert(
p,
Renderable {
fg: new_particle.fg,
bg: new_particle.bg,
glyph: new_particle.glyph,
render_order: 0,
},
)
.expect("Failed to insert Renderable of new particle");
particles
.insert(
p,
ParticleLifetime {
lifetime_ms: new_particle.lifetime,
},
)
.expect("Failed to insert Lifetime of new particle");
}
particle_builder.requests.clear();
}
}

View File

@ -175,17 +175,8 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
try_move_player(-1, 1, &mut gs.ecs) try_move_player(-1, 1, &mut gs.ecs)
} }
// Pick up item // Skip Turn
VirtualKeyCode::G => get_item(&mut gs.ecs), VirtualKeyCode::Numpad5 | VirtualKeyCode::Space => return skip_turn(&mut gs.ecs),
// Show inventory
VirtualKeyCode::I => return RunState::ShowInventory,
// Show item drop screen
VirtualKeyCode::D => return RunState::ShowDropItem,
// Save and Quit
VirtualKeyCode::Escape => return RunState::SaveGame,
// Level changes // Level changes
VirtualKeyCode::Period => { VirtualKeyCode::Period => {
@ -194,12 +185,15 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
} }
} }
// Skip Turn // Item management
VirtualKeyCode::Numpad5 | VirtualKeyCode::Space => return skip_turn(&mut gs.ecs), VirtualKeyCode::G => get_item(&mut gs.ecs),
VirtualKeyCode::I => return RunState::ShowInventory,
// Remove item VirtualKeyCode::D => return RunState::ShowDropItem,
VirtualKeyCode::R => return RunState::ShowRemoveItem, VirtualKeyCode::R => return RunState::ShowRemoveItem,
// Save and Quit
VirtualKeyCode::Escape => return RunState::SaveGame,
_ => return RunState::AwaitingInput, _ => return RunState::AwaitingInput,
}, },
} }

View File

@ -74,7 +74,8 @@ pub fn save_game(ecs: &mut World) {
Equipped, Equipped,
MeleePowerBonus, MeleePowerBonus,
DefenseBonus, DefenseBonus,
WantsToRemoveItem WantsToRemoveItem,
ParticleLifetime
); );
} }
@ -155,7 +156,8 @@ pub fn load_game(ecs: &mut World) {
Equipped, Equipped,
MeleePowerBonus, MeleePowerBonus,
DefenseBonus, DefenseBonus,
WantsToRemoveItem WantsToRemoveItem,
ParticleLifetime
); );
} }