basic particles

This commit is contained in:
Llywelwyn 2023-07-08 01:17:21 +01:00
parent a79669d55c
commit f76b705fe6
6 changed files with 146 additions and 5 deletions

View file

@ -97,3 +97,8 @@ pub struct WantsToUseItem {
#[derive(Component, Debug)]
pub struct Consumable {}
#[derive(Component, Clone)]
pub struct ParticleLifetime {
pub lifetime_ms: f32,
}

View file

@ -1,6 +1,6 @@
use super::{
gamelog::GameLog, CombatStats, Consumable, InBackpack, Name, Position, ProvidesHealing, WantsToDropItem,
WantsToPickupItem, WantsToUseItem,
gamelog::GameLog, CombatStats, Consumable, InBackpack, Name, ParticleBuilder, Position, ProvidesHealing,
WantsToDropItem, WantsToPickupItem, WantsToUseItem, DEFAULT_PARTICLE_LIFETIME,
};
use specs::prelude::*;
@ -45,10 +45,23 @@ impl<'a> System<'a> for ItemUseSystem {
ReadStorage<'a, Consumable>,
ReadStorage<'a, ProvidesHealing>,
WriteStorage<'a, CombatStats>,
WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>,
);
fn run(&mut self, data: Self::SystemData) {
let (player_entity, mut gamelog, entities, mut wants_use, names, consumables, healing, mut combat_stats) = data;
let (
player_entity,
mut gamelog,
entities,
mut wants_use,
names,
consumables,
healing,
mut combat_stats,
mut particle_builder,
positions,
) = data;
for (entity, use_item, stats) in (&entities, &wants_use, &mut combat_stats).join() {
let item_heals = healing.get(use_item.item);
@ -56,6 +69,17 @@ impl<'a> System<'a> for ItemUseSystem {
None => {}
Some(healer) => {
stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
let pos = positions.get(entity);
if let Some(pos) = pos {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::GREEN),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('♥'),
DEFAULT_PARTICLE_LIFETIME,
);
}
if entity == *player_entity {
gamelog.entries.push(format!(
"You quaff the {}, and heal {} hp.",

View file

@ -24,6 +24,8 @@ mod melee_combat_system;
use melee_combat_system::MeleeCombatSystem;
mod inventory_system;
use inventory_system::*;
mod particle_system;
use particle_system::{ParticleBuilder, DEFAULT_PARTICLE_LIFETIME};
// Embedded resources for use in wasm build
rltk::embedded_resource!(TERMINAL8X8, "../resources/terminal8x8.jpg");
@ -62,6 +64,8 @@ impl State {
item_use_system.run_now(&self.ecs);
let mut drop_system = ItemDropSystem {};
drop_system.run_now(&self.ecs);
let mut particle_system = particle_system::ParticleSpawnSystem {};
particle_system.run_now(&self.ecs);
self.ecs.maintain();
}
}
@ -70,6 +74,7 @@ impl GameState for State {
fn tick(&mut self, ctx: &mut Rltk) {
// Clear screen
ctx.cls();
particle_system::cull_dead_particles(&mut self.ecs, ctx);
// Draw map and ui
draw_map(&self.ecs, ctx);
@ -186,6 +191,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<WantsToDropItem>();
gs.ecs.register::<WantsToUseItem>();
gs.ecs.register::<Consumable>();
gs.ecs.register::<ParticleLifetime>();
let map = Map::new_map_rooms_and_corridors();
let (player_x, player_y) = map.rooms[0].centre();
@ -202,6 +208,7 @@ fn main() -> rltk::BError {
gs.ecs.insert(player_entity);
gs.ecs.insert(gamelog::GameLog { entries: vec!["Here's your welcome message.".to_string()] });
gs.ecs.insert(RunState::PreRun);
gs.ecs.insert(particle_system::ParticleBuilder::new());
rltk::main_loop(context, gs)
}

View file

@ -1,4 +1,4 @@
use super::{gamelog::GameLog, CombatStats, Name, SufferDamage, WantsToMelee};
use super::{gamelog::GameLog, CombatStats, Name, ParticleBuilder, Position, SufferDamage, WantsToMelee};
use specs::prelude::*;
pub struct MeleeCombatSystem {}
@ -11,10 +11,21 @@ impl<'a> System<'a> for MeleeCombatSystem {
ReadStorage<'a, Name>,
ReadStorage<'a, CombatStats>,
WriteStorage<'a, SufferDamage>,
WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>,
);
fn run(&mut self, data: Self::SystemData) {
let (entities, mut log, mut wants_melee, names, combat_stats, mut inflict_damage) = data;
let (
entities,
mut log,
mut wants_melee,
names,
combat_stats,
mut inflict_damage,
mut particle_builder,
positions,
) = data;
for (_entity, wants_melee, name, stats) in (&entities, &wants_melee, &names, &combat_stats).join() {
if stats.hp <= 0 {
@ -26,6 +37,17 @@ impl<'a> System<'a> for MeleeCombatSystem {
}
let target_name = names.get(wants_melee.target).unwrap();
let pos = positions.get(wants_melee.target);
if let Some(pos) = pos {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::ORANGE),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
150.0,
);
}
let damage = i32::max(0, stats.power - target_stats.defence);
if damage == 0 {

View file

@ -42,6 +42,10 @@ impl<'a> System<'a> for MonsterAI {
.insert(entity, WantsToMelee { target: *player_entity })
.expect("Unable to insert attack.");
} else if viewshed.visible_tiles.contains(&*player_pos) {
// If the player is visible, but the path is obstructed, this will currently search
// the entire map (i.e. Will do a huge ASTAR to find an alternate route), and the
// mob will follow that path until it leaves vision, then lose sight of the player
// and stop.
let path = rltk::a_star_search(map.xy_idx(pos.x, pos.y), map.xy_idx(player_pos.x, player_pos.y), &*map);
if path.success && path.steps.len() > 1 {
let mut idx = map.xy_idx(pos.x, pos.y);

79
src/particle_system.rs Normal file
View file

@ -0,0 +1,79 @@
use super::{ParticleLifetime, Position, Renderable, Rltk};
use rltk::RGB;
use specs::prelude::*;
pub const DEFAULT_PARTICLE_LIFETIME: f32 = 150.0;
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("Particle will not die");
}
}
struct ParticleRequest {
x: i32,
y: i32,
fg: RGB,
bg: RGB,
glyph: rltk::FontCharType,
lifetime: f32,
}
pub struct ParticleBuilder {
requests: Vec<ParticleRequest>,
}
impl ParticleBuilder {
#[allow(clippy::new_without_default)]
pub fn new() -> ParticleBuilder {
ParticleBuilder { requests: Vec::new() }
}
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("Could not insert position");
renderables
.insert(
p,
Renderable { fg: new_particle.fg, bg: new_particle.bg, glyph: new_particle.glyph, render_order: 0 },
)
.expect("Could not insert renderables");
particles
.insert(p, ParticleLifetime { lifetime_ms: new_particle.lifetime })
.expect("Could not insert lifetime");
}
particle_builder.requests.clear();
}
}