diff --git a/src/ai/visible_ai_system.rs b/src/ai/visible_ai_system.rs index 2e7ce14..019b8eb 100644 --- a/src/ai/visible_ai_system.rs +++ b/src/ai/visible_ai_system.rs @@ -1,9 +1,11 @@ +use ::rltk::RandomNumberGenerator; use ::specs::prelude::*; use crate::components::{ - Chasing, Faction, MyTurn, Position, Viewshed, WantsToApproach, WantsToFlee, + Chasing, Faction, MyTurn, Name, Position, SpecialAbilities, SpellTemplate, Viewshed, + WantsToApproach, WantsToCastSpell, WantsToFlee, }; -use crate::raws::{self, Reaction, RAWS}; +use crate::raws::{self, find_spell_entity_by_name, Reaction, RAWS}; use crate::{spatial, Map}; pub struct VisibleAI {} @@ -21,9 +23,15 @@ impl<'a> System<'a> for VisibleAI { ReadExpect<'a, Entity>, ReadStorage<'a, Viewshed>, WriteStorage<'a, Chasing>, + ReadStorage<'a, SpecialAbilities>, + WriteExpect<'a, RandomNumberGenerator>, + WriteStorage<'a, WantsToCastSpell>, + ReadStorage<'a, Name>, + ReadStorage<'a, SpellTemplate>, ); fn run(&mut self, data: Self::SystemData) { + use ::rltk::{DistanceAlg, Point}; let ( turns, factions, @@ -35,6 +43,11 @@ impl<'a> System<'a> for VisibleAI { player, viewsheds, mut chasing, + abilities, + mut rng, + mut casting, + names, + spells, ) = data; for (entity, _turn, my_faction, pos, viewshed) in @@ -55,18 +68,56 @@ impl<'a> System<'a> for VisibleAI { for reaction in reactions.iter() { match reaction.1 { Reaction::Attack => { - want_approach - .insert( - entity, - WantsToApproach { - idx: reaction.0 as i32, - }, - ) - .expect("Unable to insert intent to approach"); - chasing - .insert(entity, Chasing { target: reaction.2 }) - .expect("Unable to insert intent to chase"); - done = true; + if let Some(abilities) = abilities.get(entity) { + let range = DistanceAlg::Pythagoras.distance2d( + Point::from(*pos), + Point::new( + reaction.0 as i32 % map.width, + reaction.0 as i32 / map.width, + ), + ); + for ability in abilities.abilities.iter() { + if range >= ability.min_range + && range <= ability.range + && rng.roll_dice(1, 100) >= (ability.chance * 100.0) as i32 + { + casting + .insert( + entity, + WantsToCastSpell { + spell: find_spell_entity_by_name( + &ability.spell, + &names, + &spells, + &entities, + ) + .unwrap(), + target: Some(Point::new( + reaction.0 as i32 % map.width, + reaction.0 as i32 / map.width, + )), + }, + ) + .expect("Unable to insert intent to cast spell"); + done = true; + } + } + } + + if !done { + want_approach + .insert( + entity, + WantsToApproach { + idx: reaction.0 as i32, + }, + ) + .expect("Unable to insert intent to approach"); + chasing + .insert(entity, Chasing { target: reaction.2 }) + .expect("Unable to insert intent to chase"); + done = true; + } } Reaction::Flee => { flee.push(reaction.0); diff --git a/src/components.rs b/src/components.rs index 14e0ab6..08af218 100644 --- a/src/components.rs +++ b/src/components.rs @@ -445,3 +445,16 @@ pub struct Slow { pub struct DamageOverTime { pub damage: i32, } + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SpecialAbility { + pub spell: String, + pub chance: f32, + pub range: f32, + pub min_range: f32, +} + +#[derive(Component, Debug, Default, Serialize, Deserialize, Clone)] +pub struct SpecialAbilities { + pub abilities: Vec, +} diff --git a/src/main.rs b/src/main.rs index c335adb..f6e48fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,6 +140,7 @@ fn init_state() -> State { Slow, SpawnParticleBurst, SpawnParticleLine, + SpecialAbilities, SpellTemplate, StatusEffect, TeachesSpell, diff --git a/src/raws/mob_structs.rs b/src/raws/mob_structs.rs index 3d743f4..aeb8a2e 100644 --- a/src/raws/mob_structs.rs +++ b/src/raws/mob_structs.rs @@ -24,6 +24,15 @@ pub struct Mob { pub faction: Option, pub gold: Option, pub vendor: Option>, + pub abilities: Option>, +} + +#[derive(Deserialize, Debug)] +pub struct MobAbility { + pub spell: String, + pub chance: f32, + pub range: f32, + pub min_range: f32, } #[derive(Deserialize, Debug)] diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index f28163b..2dea6ac 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -647,6 +647,20 @@ pub fn spawn_named_mob( }) } + if let Some(ability_list) = &mob_template.abilities { + let mut a = SpecialAbilities::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? @@ -821,3 +835,18 @@ pub fn find_spell_entity(ecs: &World, name: &str) -> Option { None } + +pub fn find_spell_entity_by_name( + name: &str, + names: &ReadStorage, + spell_templates: &ReadStorage, + entities: &Entities, +) -> Option { + for (entity, sname, _template) in (entities, names, spell_templates).join() { + if name == sname.name { + return Some(entity); + } + } + + None +} diff --git a/src/saveload_system.rs b/src/saveload_system.rs index f526d45..727f105 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -118,6 +118,7 @@ pub fn save_game(ecs: &mut World) { Slow, SpawnParticleBurst, SpawnParticleLine, + SpecialAbilities, SpellTemplate, StatusEffect, TeachesSpell, @@ -248,6 +249,7 @@ pub fn load_game(ecs: &mut World) { Skills, SpawnParticleBurst, SpawnParticleLine, + SpecialAbilities, SpellTemplate, StatusEffect, TeachesSpell,