diff --git a/raws/items.json b/raws/items.json index 6807ad1..566c2bc 100644 --- a/raws/items.json +++ b/raws/items.json @@ -46,7 +46,7 @@ "weight": 0.5, "value": 50, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], - "effects": { "ranged": "12", "damage": "3d4+3" }, + "effects": { "particle_line": "*;#00b7ff;75.0;#f4fc83;100.0", "ranged": "12", "damage": "3d4+3" }, "magic": { "class": "uncommon", "naming": "scroll" } }, { @@ -56,7 +56,7 @@ "weight": 0.5, "value": 100, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], - "effects": { "ranged": "10", "damage": "4d6", "aoe": "2" }, + "effects": { "particle": "*;#FFA500;200.0", "ranged": "10", "damage": "4d6", "aoe": "2" }, "magic": { "class": "uncommon", "naming": "scroll" } }, { diff --git a/src/components.rs b/src/components.rs index 9436f25..1374254 100644 --- a/src/components.rs +++ b/src/components.rs @@ -410,6 +410,22 @@ pub struct Charges { pub max_uses: i32, } +#[derive(Component, Serialize, Deserialize, Clone)] +pub struct SpawnParticleLine { + pub glyph: rltk::FontCharType, + pub colour: RGB, + pub lifetime_ms: f32, + pub trail_colour: RGB, + pub trail_lifetime_ms: f32, +} + +#[derive(Component, Serialize, Deserialize, Clone)] +pub struct SpawnParticleBurst { + pub glyph: rltk::FontCharType, + pub colour: RGB, + pub lifetime_ms: f32, +} + #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Destructible {} diff --git a/src/effects/targeting.rs b/src/effects/targeting.rs index 65d56bd..4d3ed85 100644 --- a/src/effects/targeting.rs +++ b/src/effects/targeting.rs @@ -1,4 +1,5 @@ -use crate::{Map, Position}; +use crate::{Equipped, InBackpack, Map, Position}; +use rltk::prelude::*; use specs::prelude::*; pub fn entity_position(ecs: &World, target: Entity) -> Option { @@ -18,3 +19,27 @@ pub fn aoe_tiles(map: &Map, target: rltk::Point, radius: i32) -> Vec { } result } + +pub fn find_item_position(ecs: &World, target: Entity) -> Option { + let positions = ecs.read_storage::(); + let map = ecs.fetch::(); + // Does it have a position? + if let Some(pos) = positions.get(target) { + return Some(map.xy_idx(pos.x, pos.y) as i32); + } + // If not, is it carried? + if let Some(carried) = ecs.read_storage::().get(target) { + if let Some(pos) = positions.get(carried.owner) { + return Some(map.xy_idx(pos.x, pos.y) as i32); + } + } + // Is it equipped? + if let Some(carried) = ecs.read_storage::().get(target) { + if let Some(pos) = positions.get(carried.owner) { + return Some(map.xy_idx(pos.x, pos.y) as i32); + } + } + // Out of luck: give up + console::log("DEBUGINFO: Failed to find item position"); + None +} diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index 434de78..7f268b0 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -1,12 +1,12 @@ -use super::{add_effect, get_noncursed, spatial, EffectType, Entity, Targets, World}; +use super::{add_effect, get_noncursed, spatial, targeting, EffectType, Entity, Targets, World}; use crate::{ gamelog, gui::item_colour_ecs, gui::obfuscate_name_ecs, gui::renderable_colour, Beatitude, Charges, Confusion, - Consumable, Destructible, Hidden, InflictsDamage, Item, MagicMapper, Player, Prop, ProvidesHealing, - ProvidesNutrition, RandomNumberGenerator, Renderable, RunState, SingleActivation, BUC, + Consumable, Destructible, Hidden, InflictsDamage, Item, MagicMapper, Map, Player, Prop, ProvidesHealing, + ProvidesNutrition, RandomNumberGenerator, Renderable, RunState, SingleActivation, SpawnParticleBurst, + SpawnParticleLine, BUC, }; use rltk::prelude::*; use specs::prelude::*; - pub fn item_trigger(source: Option, item: Entity, target: &Targets, ecs: &mut World) { // Check if the item has charges, etc. if let Some(has_charges) = ecs.write_storage::().get_mut(item) { @@ -62,6 +62,41 @@ fn event_trigger(source: Option, entity: Entity, target: &Targets, ecs: let logger = gamelog::Logger::new(); let mut did_something = false; + // Simple particle spawn + if let Some(part) = ecs.read_storage::().get(entity) { + 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::().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, magic_mapped) = handle_magic_mapper(ecs, &mut event, logger); let (logger, healed) = handle_healing(ecs, &mut event, logger); @@ -227,3 +262,36 @@ fn get_entity_targets(target: &Targets) -> Vec { } return entities; } + +fn spawn_line_particles(ecs: &World, start: i32, end: i32, part: &SpawnParticleLine) { + let map = ecs.fetch::(); + 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) }, + ); + } + } +} diff --git a/src/main.rs b/src/main.rs index ba2d3cf..7dbc4eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -610,6 +610,8 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.register::>(); gs.ecs.register::(); gs.ecs.register::(); diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index a2ac921..a0d0f2a 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -29,6 +29,8 @@ macro_rules! apply_effects { "ac" => $eb = $eb.with(ArmourClassBonus { amount: effect.1.parse::().unwrap() }), "magicmapper" => $eb = $eb.with(MagicMapper {}), "digger" => $eb = $eb.with(Digger {}), + "particle_line" => $eb = $eb.with(parse_particle_line(&effect.1)), + "particle" => $eb = $eb.with(parse_particle(&effect.1)), _ => console::log(format!("Warning: effect {} not implemented.", effect_name)), } } @@ -875,3 +877,23 @@ fn get_ancestry_string(ancestry: Ancestry) -> &'static str { Ancestry::NULL => return "NULL", } } + +fn parse_particle_line(n: &str) -> SpawnParticleLine { + let tokens: Vec<_> = n.split(';').collect(); + SpawnParticleLine { + glyph: to_cp437(tokens[0].chars().next().unwrap()), + colour: RGB::from_hex(tokens[1]).expect("Invalid RGB"), + lifetime_ms: tokens[2].parse::().unwrap(), + trail_colour: RGB::from_hex(tokens[3]).expect("Invalid trail RGB"), + trail_lifetime_ms: tokens[4].parse::().unwrap(), + } +} + +fn parse_particle(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"), + lifetime_ms: tokens[2].parse::().unwrap(), + } +} diff --git a/src/saveload_system.rs b/src/saveload_system.rs index c019c4f..3dc9cd4 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -103,6 +103,8 @@ pub fn save_game(ecs: &mut World) { Renderable, SingleActivation, Skills, + SpawnParticleBurst, + SpawnParticleLine, TakingTurn, Telepath, Viewshed, @@ -222,6 +224,8 @@ pub fn load_game(ecs: &mut World) { Renderable, SingleActivation, Skills, + SpawnParticleBurst, + SpawnParticleLine, TakingTurn, Telepath, Viewshed,