diff --git a/src/components.rs b/src/components.rs index 665bc62..0fa4583 100644 --- a/src/components.rs +++ b/src/components.rs @@ -521,3 +521,8 @@ pub struct TileSize { pub x: i32, pub y: i32, } + +#[derive(Component, Debug, Default, Serialize, Deserialize, Clone)] +pub struct OnDeath { + pub abilities: Vec, +} diff --git a/src/components/tags.rs b/src/components/tags.rs index 6f0eebd..0d692e3 100644 --- a/src/components/tags.rs +++ b/src/components/tags.rs @@ -51,3 +51,6 @@ pub struct ProvidesIdentification {} #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Confusion {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct AlwaysTargetsSelf {} diff --git a/src/damage_system.rs b/src/damage_system.rs index 72f481c..a009553 100644 --- a/src/damage_system.rs +++ b/src/damage_system.rs @@ -1,10 +1,13 @@ use ::rltk::RandomNumberGenerator; use ::specs::prelude::*; -use crate::components::{Equipped, InBackpack, LootTable, Name, Player, Pools, Position}; +use crate::components::{ + AreaOfEffect, Equipped, InBackpack, LootTable, Name, OnDeath, Player, Pools, Position, +}; +use crate::effects::*; use crate::game_log::GameLog; use crate::raws::{self, SpawnType, RAWS}; -use crate::RunState; +use crate::{Map, RunState}; pub fn delete_the_dead(ecs: &mut World) { let mut dead: Vec = Vec::new(); @@ -101,6 +104,42 @@ pub fn delete_the_dead(ecs: &mut World) { } } + // Fire death events + for victim in dead.iter() { + let death_effects = ecs.read_storage::(); + if let Some(death_effect) = death_effects.get(*victim) { + let mut rng = ecs.fetch_mut::(); + for effect in death_effect.abilities.iter() { + if rng.roll_dice(1, 100) <= (effect.chance * 100.0) as i32 { + let map = ecs.fetch::(); + if let Some(pos) = ecs.read_storage::().get(*victim) { + let spell_entity = + crate::raws::find_spell_entity(ecs, &effect.spell).unwrap(); + let tile_idx = map.xy_idx(pos.x, pos.y); + let target = if let Some(aoe) = + ecs.read_storage::().get(spell_entity) + { + Targets::Tiles { + tiles: aoe_tiles(&map, rltk::Point::new(pos.x, pos.y), aoe.radius), + } + } else { + Targets::Tile { + tile_idx: tile_idx as i32, + } + }; + add_effect( + None, + EffectType::SpellUse { + spell: crate::raws::find_spell_entity(ecs, &effect.spell).unwrap(), + }, + target, + ); + } + } + } + } + } + for victim in dead { ecs.delete_entity(victim) .expect("Unable to delete the dead"); diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index 418c123..fa27c81 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -1,15 +1,17 @@ +use ::rltk::Point; use ::specs::prelude::*; use super::{add_effect, EffectType, Targets}; use crate::components::{ - AttributeBonus, Confusion, Consumable, DamageOverTime, Duration, Hidden, InflictsDamage, - MagicMapper, Name, Pools, ProvidesFood, ProvidesHealing, ProvidesIdentification, ProvidesMana, - ProvidesRemoveCurse, SingleActivation, Slow, SpawnParticleBurst, SpawnParticleLine, - SpellTemplate, TeachesSpell, TeleportTo, TownPortal, + AlwaysTargetsSelf, AreaOfEffect, AttributeBonus, Confusion, Consumable, DamageOverTime, + Duration, Hidden, InflictsDamage, KnownSpell, KnownSpells, MagicMapper, Name, Pools, Position, + ProvidesFood, ProvidesHealing, ProvidesIdentification, ProvidesMana, ProvidesRemoveCurse, + SingleActivation, Slow, SpawnParticleBurst, SpawnParticleLine, SpellTemplate, TeachesSpell, + TeleportTo, TownPortal, }; -use crate::effects::{entity_position, targeting}; +use crate::effects::{aoe_tiles, entity_position, targeting}; use crate::raws::find_spell_entity; -use crate::{colors, GameLog, KnownSpell, KnownSpells, Map, RunState}; +use crate::{colors, GameLog, Map, RunState}; pub fn item_trigger(creator: Option, item: Entity, targets: &Targets, ecs: &mut World) { // Check charges @@ -44,6 +46,8 @@ pub fn item_trigger(creator: Option, item: Entity, targets: &Targets, ec } pub fn spell_trigger(creator: Option, spell: Entity, targets: &Targets, ecs: &mut World) { + let mut targeting = targets.clone(); + let mut self_destruct = false; if let Some(template) = ecs.read_storage::().get(spell) { let mut pools = ecs.write_storage::(); if let Some(caster) = creator { @@ -52,10 +56,33 @@ pub fn spell_trigger(creator: Option, spell: Entity, targets: &Targets, pool.mana.current -= template.mana_cost; } } + + // Handle self-targeting override + if ecs.read_storage::().get(spell).is_some() { + if let Some(pos) = ecs.read_storage::().get(caster) { + let map = ecs.fetch::(); + targeting = if let Some(aoe) = ecs.read_storage::().get(spell) { + Targets::Tiles { + tiles: aoe_tiles(&map, Point::new(pos.x, pos.y), aoe.radius), + } + } else { + Targets::Tile { + tile_idx: map.xy_idx(pos.x, pos.y) as i32, + } + } + } + } + } + if let Some(_destruct) = ecs.read_storage::().get(spell) { + self_destruct = true; } } - - event_trigger(creator, spell, targets, ecs); + event_trigger(creator, spell, &targeting, ecs); + if self_destruct && creator.is_some() { + ecs.entities() + .delete(creator.unwrap()) + .expect("Unable to delete owner"); + } } pub fn trigger(creator: Option, trigger: Entity, targets: &Targets, ecs: &mut World) { @@ -320,7 +347,7 @@ fn event_trigger( } fn spawn_line_particles(ecs: &World, start: i32, end: i32, part: &SpawnParticleLine) { - use ::rltk::{LineAlg, Point}; + use ::rltk::LineAlg; let map = ecs.fetch::(); let start_pt = Point::new(start % map.width, end / map.width); diff --git a/src/main.rs b/src/main.rs index 1f42e8b..d10566e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,6 +81,7 @@ fn init_state() -> State { register!( state <- + AlwaysTargetsSelf, ApplyMove, ApplyTeleport, AreaOfEffect, @@ -120,6 +121,7 @@ fn init_state() -> State { Name, NaturalAttackDefense, ObfuscatedName, + OnDeath, OtherLevelPosition, ParticleLifetime, Player, diff --git a/src/raws/mob_structs.rs b/src/raws/mob_structs.rs index aeb8a2e..d162a2a 100644 --- a/src/raws/mob_structs.rs +++ b/src/raws/mob_structs.rs @@ -25,6 +25,7 @@ pub struct Mob { pub gold: Option, pub vendor: Option>, pub abilities: Option>, + pub on_death: Option>, } #[derive(Deserialize, Debug)] diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 0223845..66b8f80 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -337,6 +337,7 @@ macro_rules! apply_effects { damage: effect.1.parse::().unwrap(), }) } + "target_self" => $eb = $eb.with(AlwaysTargetsSelf {}), _ => { console::log(format!( "WARNING: consumable effect '{}' not implemented.", @@ -667,6 +668,20 @@ pub fn spawn_named_mob( eb = eb.with(a); } + if let Some(ability_list) = &mob_template.on_death { + let mut a = OnDeath::default(); + for ability in ability_list.iter() { + a.abilities.push(SpecialAbility { + chance: ability.chance, + spell: ability.spell.clone(), + range: ability.range, + min_range: ability.min_range, + }); + } + + eb = eb.with(a); + } + let new_mob = eb.build(); // Are they wielding anything? diff --git a/src/saveload_system.rs b/src/saveload_system.rs index d303bec..dbd19b7 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -60,6 +60,7 @@ pub fn save_game(ecs: &mut World) { ecs, serializer, data, + AlwaysTargetsSelf, ApplyMove, ApplyTeleport, AreaOfEffect, @@ -99,6 +100,7 @@ pub fn save_game(ecs: &mut World) { Name, NaturalAttackDefense, ObfuscatedName, + OnDeath, OtherLevelPosition, ParticleLifetime, Player, @@ -192,6 +194,7 @@ pub fn load_game(ecs: &mut World) { ecs, de, d, + AlwaysTargetsSelf, ApplyMove, ApplyTeleport, AreaOfEffect, @@ -231,6 +234,7 @@ pub fn load_game(ecs: &mut World) { Name, NaturalAttackDefense, ObfuscatedName, + OnDeath, OtherLevelPosition, ParticleLifetime, Player,