fancy particle effects

This commit is contained in:
Llywelwyn 2023-08-21 22:43:19 +01:00
parent 5f23822f62
commit 366c5d6543
8 changed files with 189 additions and 79 deletions

View file

@ -66,7 +66,12 @@
"weight": 0.5, "weight": 0.5,
"value": 200, "value": 200,
"flags": ["CONSUMABLE", "DESTRUCTIBLE"], "flags": ["CONSUMABLE", "DESTRUCTIBLE"],
"effects": { "particle": "*;#FFA500;200.0", "ranged": "10", "damage": "8d6", "aoe": "3" }, "effects": {
"particle_burst": "*;#FFA500;#000000;600.0;#ff9595;75.0",
"ranged": "10",
"damage": "8d6",
"aoe": "3"
},
"magic": { "class": "rare", "naming": "scroll" } "magic": { "class": "rare", "naming": "scroll" }
}, },
{ {

View file

@ -420,12 +420,22 @@ pub struct SpawnParticleLine {
} }
#[derive(Component, Serialize, Deserialize, Clone)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct SpawnParticleBurst { pub struct SpawnParticleSimple {
pub glyph: rltk::FontCharType, pub glyph: rltk::FontCharType,
pub colour: RGB, pub colour: RGB,
pub lifetime_ms: f32, pub lifetime_ms: f32,
} }
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct SpawnParticleBurst {
pub glyph: rltk::FontCharType,
pub colour: RGB,
pub lerp: RGB,
pub lifetime_ms: f32,
pub trail_colour: RGB,
pub trail_lifetime_ms: f32,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Destructible {} pub struct Destructible {}

View file

@ -1,5 +1,6 @@
use super::{EffectSpawner, EffectType}; use super::{add_effect, targeting, EffectSpawner, EffectType, Targets};
use crate::{Map, ParticleBuilder}; use crate::{Map, ParticleBuilder, SpawnParticleBurst, SpawnParticleLine, SpawnParticleSimple};
use rltk::prelude::*;
use specs::prelude::*; use specs::prelude::*;
pub fn particle_to_tile(ecs: &mut World, target: i32, effect: &EffectSpawner) { pub fn particle_to_tile(ecs: &mut World, target: i32, effect: &EffectSpawner) {
@ -13,3 +14,148 @@ pub fn particle_to_tile(ecs: &mut World, target: i32, effect: &EffectSpawner) {
} }
} }
} }
pub fn handle_simple_particles(ecs: &World, entity: Entity, target: &Targets) {
if let Some(part) = ecs.read_storage::<SpawnParticleSimple>().get(entity) {
add_effect(
None,
EffectType::Particle {
glyph: part.glyph,
fg: part.colour,
bg: RGB::named(BLACK),
lifespan: part.lifetime_ms,
delay: 0.0,
},
target.clone(),
);
}
}
pub fn handle_burst_particles(ecs: &World, entity: Entity, target: &Targets) {
if let Some(part) = ecs.read_storage::<SpawnParticleBurst>().get(entity) {
if let Some(start_pos) = targeting::find_item_position(ecs, entity) {
let end_pos: i32 = get_centre(ecs, target);
spawn_line_particles(
ecs,
start_pos,
end_pos,
&SpawnParticleLine {
glyph: part.glyph,
colour: part.colour,
trail_colour: part.colour,
lifetime_ms: part.trail_lifetime_ms, // 75.0 is good here.
trail_lifetime_ms: part.trail_lifetime_ms + 25.0,
},
);
let map = ecs.fetch::<Map>();
let line = line2d(
LineAlg::Bresenham,
Point::new(start_pos % map.width, start_pos / map.width),
Point::new(end_pos % map.width, end_pos / map.width),
);
let burst_delay = line.len() as f32 * 75.0;
for i in 0..10 {
add_effect(
None,
EffectType::Particle {
glyph: part.glyph,
fg: part.colour.lerp(part.lerp, i as f32 * 0.1),
bg: RGB::named(BLACK),
lifespan: part.lifetime_ms / 10.0, // ~50-80 is good here.
delay: burst_delay + (i as f32 * part.lifetime_ms / 10.0),
},
target.clone(),
);
}
}
}
}
fn get_centre(ecs: &World, target: &Targets) -> i32 {
match target {
Targets::Tile { target } => return *target as i32,
Targets::TileList { targets } => {
let map = ecs.fetch::<Map>();
let (mut count, mut sum_x, mut sum_y) = (0, 0, 0);
for target in targets {
sum_x += *target as i32 % map.width;
sum_y += *target as i32 / map.width;
count += 1;
}
let (mean_x, mean_y) = (sum_x / count, sum_y / count);
let centre = map.xy_idx(mean_x, mean_y);
return centre as i32;
}
Targets::Entity { target } => return targeting::entity_position(ecs, *target).unwrap() as i32,
Targets::EntityList { targets } => {
let map = ecs.fetch::<Map>();
let (mut count, mut sum_x, mut sum_y) = (0, 0, 0);
for target in targets {
if let Some(pos) = targeting::entity_position(ecs, *target) {
sum_x += pos as i32 % map.width;
sum_y += pos as i32 / map.width;
count += 1;
}
}
let (mean_x, mean_y) = (sum_x / count, sum_y / count);
let centre = map.xy_idx(mean_x, mean_y);
return centre as i32;
}
}
}
pub fn handle_line_particles(ecs: &World, entity: Entity, target: &Targets) {
if let Some(part) = ecs.read_storage::<SpawnParticleLine>().get(entity) {
if let Some(start_pos) = targeting::find_item_position(ecs, entity) {
match target {
Targets::Tile { target } => spawn_line_particles(ecs, start_pos, *target as i32, part),
Targets::TileList { targets } => {
targets.iter().for_each(|target| spawn_line_particles(ecs, start_pos, *target as i32, part))
}
Targets::Entity { target } => {
if let Some(end_pos) = targeting::entity_position(ecs, *target) {
spawn_line_particles(ecs, start_pos, end_pos as i32, part);
}
}
Targets::EntityList { targets } => targets.iter().for_each(|target| {
if let Some(end_pos) = targeting::entity_position(ecs, *target) {
spawn_line_particles(ecs, start_pos, end_pos as i32, part);
}
}),
}
}
}
}
fn spawn_line_particles(ecs: &World, start: i32, end: i32, part: &SpawnParticleLine) {
let map = ecs.fetch::<Map>();
let start_pt = Point::new(start % map.width, start / map.width);
let end_pt = Point::new(end % map.width, end / map.width);
let line = line2d(LineAlg::Bresenham, start_pt, end_pt);
for (i, pt) in line.iter().enumerate() {
add_effect(
None,
EffectType::Particle {
glyph: part.glyph,
fg: part.colour,
bg: RGB::named(BLACK),
lifespan: part.lifetime_ms,
delay: i as f32 * part.lifetime_ms,
},
Targets::Tile { target: map.xy_idx(pt.x, pt.y) },
);
if i > 0 {
add_effect(
None,
EffectType::Particle {
glyph: to_cp437('-'),
fg: part.trail_colour,
bg: RGB::named(BLACK),
lifespan: part.trail_lifetime_ms,
delay: i as f32 * part.lifetime_ms,
},
Targets::Tile { target: map.xy_idx(line[i - 1].x, line[i - 1].y) },
);
}
}
}

View file

@ -1,9 +1,8 @@
use super::{add_effect, get_noncursed, spatial, targeting, EffectType, Entity, Targets, World}; use super::{add_effect, get_noncursed, particles, spatial, targeting, EffectType, Entity, Targets, World};
use crate::{ use crate::{
gamelog, gui::item_colour_ecs, gui::obfuscate_name_ecs, gui::renderable_colour, Beatitude, Charges, Confusion, gamelog, gui::item_colour_ecs, gui::obfuscate_name_ecs, gui::renderable_colour, Beatitude, Charges, Confusion,
Consumable, Destructible, Hidden, InflictsDamage, Item, MagicMapper, Map, Player, Prop, ProvidesHealing, Consumable, Destructible, Hidden, InflictsDamage, Item, MagicMapper, Player, Prop, ProvidesHealing,
ProvidesNutrition, RandomNumberGenerator, Renderable, RunState, SingleActivation, SpawnParticleBurst, ProvidesNutrition, RandomNumberGenerator, Renderable, RunState, SingleActivation, BUC,
SpawnParticleLine, BUC,
}; };
use rltk::prelude::*; use rltk::prelude::*;
use specs::prelude::*; use specs::prelude::*;
@ -62,41 +61,8 @@ fn event_trigger(source: Option<Entity>, entity: Entity, target: &Targets, ecs:
let logger = gamelog::Logger::new(); let logger = gamelog::Logger::new();
let mut did_something = false; let mut did_something = false;
// Simple particle spawn particles::handle_burst_particles(ecs, entity, &target);
if let Some(part) = ecs.read_storage::<SpawnParticleBurst>().get(entity) { particles::handle_line_particles(ecs, entity, &target);
add_effect(
event.source,
EffectType::Particle {
glyph: part.glyph,
fg: part.colour,
bg: RGB::named(BLACK),
lifespan: part.lifetime_ms,
delay: 0.0,
},
event.target.clone(),
);
}
// Line particle spawn
if let Some(part) = ecs.read_storage::<SpawnParticleLine>().get(entity) {
if let Some(start_pos) = targeting::find_item_position(ecs, entity) {
match target {
Targets::Tile { target } => spawn_line_particles(ecs, start_pos, *target as i32, part),
Targets::TileList { targets } => {
targets.iter().for_each(|target| spawn_line_particles(ecs, start_pos, *target as i32, part))
}
Targets::Entity { target } => {
if let Some(end_pos) = targeting::entity_position(ecs, *target) {
spawn_line_particles(ecs, start_pos, end_pos as i32, part);
}
}
Targets::EntityList { targets } => targets.iter().for_each(|target| {
if let Some(end_pos) = targeting::entity_position(ecs, *target) {
spawn_line_particles(ecs, start_pos, end_pos as i32, part);
}
}),
}
}
}
let (logger, restored_nutrition) = handle_restore_nutrition(ecs, &mut event, logger); let (logger, restored_nutrition) = handle_restore_nutrition(ecs, &mut event, logger);
let (logger, magic_mapped) = handle_magic_mapper(ecs, &mut event, logger); let (logger, magic_mapped) = handle_magic_mapper(ecs, &mut event, logger);
let (logger, healed) = handle_healing(ecs, &mut event, logger); let (logger, healed) = handle_healing(ecs, &mut event, logger);
@ -262,36 +228,3 @@ fn get_entity_targets(target: &Targets) -> Vec<Entity> {
} }
return entities; return entities;
} }
fn spawn_line_particles(ecs: &World, start: i32, end: i32, part: &SpawnParticleLine) {
let map = ecs.fetch::<Map>();
let start_pt = Point::new(start % map.width, start / map.width);
let end_pt = Point::new(end % map.width, end / map.width);
let line = line2d(LineAlg::Bresenham, start_pt, end_pt);
for (i, pt) in line.iter().enumerate() {
add_effect(
None,
EffectType::Particle {
glyph: part.glyph,
fg: part.colour,
bg: RGB::named(BLACK),
lifespan: part.lifetime_ms,
delay: i as f32 * part.lifetime_ms,
},
Targets::Tile { target: map.xy_idx(pt.x, pt.y) },
);
if i > 0 {
add_effect(
None,
EffectType::Particle {
glyph: to_cp437('-'),
fg: part.trail_colour,
bg: RGB::named(BLACK),
lifespan: part.trail_lifetime_ms,
delay: i as f32 * part.lifetime_ms,
},
Targets::Tile { target: map.xy_idx(line[i - 1].x, line[i - 1].y) },
);
}
}
}

View file

@ -338,7 +338,7 @@ fn get_starting_inventory(class: Class, rng: &mut RandomNumberGenerator) -> (Vec
Class::Rogue => { Class::Rogue => {
starting_food = "1d2+2"; starting_food = "1d2+2";
equipped = vec!["equip_rapier".to_string(), "equip_body_weakleather".to_string()]; equipped = vec!["equip_rapier".to_string(), "equip_body_weakleather".to_string()];
carried = vec!["equip_dagger".to_string(), "equip_dagger".to_string()]; carried = vec!["equip_dagger".to_string(), "equip_dagger".to_string(), "scroll_fireball".to_string()];
} }
Class::Wizard => { Class::Wizard => {
starting_food = "1d2+1"; starting_food = "1d2+1";

View file

@ -618,6 +618,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<EntityMoved>(); gs.ecs.register::<EntityMoved>();
gs.ecs.register::<MultiAttack>(); gs.ecs.register::<MultiAttack>();
gs.ecs.register::<ParticleLifetime>(); gs.ecs.register::<ParticleLifetime>();
gs.ecs.register::<SpawnParticleSimple>();
gs.ecs.register::<SpawnParticleBurst>(); gs.ecs.register::<SpawnParticleBurst>();
gs.ecs.register::<SpawnParticleLine>(); gs.ecs.register::<SpawnParticleLine>();
gs.ecs.register::<SimpleMarker<SerializeMe>>(); gs.ecs.register::<SimpleMarker<SerializeMe>>();

View file

@ -30,6 +30,7 @@ macro_rules! apply_effects {
"magicmapper" => $eb = $eb.with(MagicMapper {}), "magicmapper" => $eb = $eb.with(MagicMapper {}),
"digger" => $eb = $eb.with(Digger {}), "digger" => $eb = $eb.with(Digger {}),
"particle_line" => $eb = $eb.with(parse_particle_line(&effect.1)), "particle_line" => $eb = $eb.with(parse_particle_line(&effect.1)),
"particle_burst" => $eb = $eb.with(parse_particle_burst(&effect.1)),
"particle" => $eb = $eb.with(parse_particle(&effect.1)), "particle" => $eb = $eb.with(parse_particle(&effect.1)),
_ => console::log(format!("Warning: effect {} not implemented.", effect_name)), _ => console::log(format!("Warning: effect {} not implemented.", effect_name)),
} }
@ -889,11 +890,23 @@ fn parse_particle_line(n: &str) -> SpawnParticleLine {
} }
} }
fn parse_particle(n: &str) -> SpawnParticleBurst { fn parse_particle(n: &str) -> SpawnParticleSimple {
let tokens: Vec<_> = n.split(';').collect(); let tokens: Vec<_> = n.split(';').collect();
SpawnParticleBurst { SpawnParticleSimple {
glyph: to_cp437(tokens[0].chars().next().unwrap()), glyph: to_cp437(tokens[0].chars().next().unwrap()),
colour: RGB::from_hex(tokens[1]).expect("Invalid RGB"), colour: RGB::from_hex(tokens[1]).expect("Invalid RGB"),
lifetime_ms: tokens[2].parse::<f32>().unwrap(), lifetime_ms: tokens[2].parse::<f32>().unwrap(),
} }
} }
fn parse_particle_burst(n: &str) -> SpawnParticleBurst {
let tokens: Vec<_> = n.split(';').collect();
SpawnParticleBurst {
glyph: to_cp437(tokens[0].chars().next().unwrap()),
colour: RGB::from_hex(tokens[1]).expect("Invalid RGB"),
lerp: RGB::from_hex(tokens[2]).expect("Invalid LERP RGB"),
lifetime_ms: tokens[3].parse::<f32>().unwrap(),
trail_colour: RGB::from_hex(tokens[4]).expect("Invalid trail RGB"),
trail_lifetime_ms: tokens[5].parse::<f32>().unwrap(),
}
}

View file

@ -105,6 +105,7 @@ pub fn save_game(ecs: &mut World) {
Skills, Skills,
SpawnParticleBurst, SpawnParticleBurst,
SpawnParticleLine, SpawnParticleLine,
SpawnParticleSimple,
TakingTurn, TakingTurn,
Telepath, Telepath,
Viewshed, Viewshed,
@ -226,6 +227,7 @@ pub fn load_game(ecs: &mut World) {
Skills, Skills,
SpawnParticleBurst, SpawnParticleBurst,
SpawnParticleLine, SpawnParticleLine,
SpawnParticleSimple,
TakingTurn, TakingTurn,
Telepath, Telepath,
Viewshed, Viewshed,