use ::rltk::prelude::*; use ::specs::prelude::*; use crate::components::{ Chasing, Equipped, Faction, MyTurn, Name, Position, SpecialAbilities, SpellTemplate, Viewshed, WantsToApproach, WantsToCastSpell, WantsToFlee, WantsToShoot, Weapon, }; use crate::raws::{self, find_spell_entity_by_name, Reaction, RAWS}; use crate::rng::roll_dice; use crate::{spatial, Map}; pub struct VisibleAI {} impl<'a> System<'a> for VisibleAI { #[allow(clippy::type_complexity)] type SystemData = ( ReadStorage<'a, MyTurn>, ReadStorage<'a, Faction>, ReadStorage<'a, Position>, ReadExpect<'a, Map>, WriteStorage<'a, WantsToApproach>, WriteStorage<'a, WantsToFlee>, Entities<'a>, ReadExpect<'a, Entity>, ReadStorage<'a, Viewshed>, WriteStorage<'a, Chasing>, ReadStorage<'a, SpecialAbilities>, WriteStorage<'a, WantsToCastSpell>, ReadStorage<'a, Name>, ReadStorage<'a, SpellTemplate>, ReadStorage<'a, Equipped>, ReadStorage<'a, Weapon>, WriteStorage<'a, WantsToShoot>, ); fn run(&mut self, data: Self::SystemData) { let ( turns, factions, positions, map, mut want_approach, mut want_flee, entities, player, viewsheds, mut chasing, abilities, mut casting, names, spells, equipped, weapons, mut wants_shoot, ) = data; for (entity, _turn, my_faction, pos, viewshed) in (&entities, &turns, &factions, &positions, &viewsheds).join() { if entity != *player { let my_idx = map.xy_idx(pos.x, pos.y); let mut reactions: Vec<(usize, Reaction, Entity)> = Vec::new(); let mut flee: Vec = Vec::new(); for visible_tile in viewshed.visible_tiles.iter() { let idx = map.xy_idx(visible_tile.x, visible_tile.y); if my_idx != idx { evaluate(idx, &map, &factions, &my_faction.name, &mut reactions); } } let mut done = false; for reaction in reactions.iter() { match reaction.1 { Reaction::Attack => { let range = DistanceAlg::Pythagoras.distance2d( Point::from(*pos), Point::new( reaction.0 as i32 % map.width, reaction.0 as i32 / map.width, ), ); // First, see if we are attempting to cast a spell if let Some(abilities) = abilities.get(entity) { for ability in abilities.abilities.iter() { if range >= ability.min_range && range <= ability.range && 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; } } } // Check if there are targets if a ranged weapon is wielded if !done { for (weapon, equip) in (&weapons, &equipped).join() { if let Some(wrange) = weapon.range { if equip.owner == entity && wrange >= range as i32 { #[cfg(feature = "debug")] console::log(format!( "Owner found. Ranges: {}/{}. Inserting shoot", wrange, range )); wants_shoot .insert(entity, WantsToShoot { target: reaction.2 }) .expect("Unable to insert intent to shoot"); done = true; } } } } // The target is not in range (yet), so approach/chase the target 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); } _ => {} } } if !done && !flee.is_empty() { want_flee .insert(entity, WantsToFlee { indices: flee }) .expect("Unable to insert intent to flee"); } } } } } fn evaluate( idx: usize, _map: &Map, factions: &ReadStorage, my_faction: &str, reactions: &mut Vec<(usize, Reaction, Entity)>, ) { spatial::for_each_tile_content(idx, |other_entity| { if let Some(faction) = factions.get(other_entity) { reactions.push(( idx, raws::faction_reaction(my_faction, &faction.name, &RAWS.lock().unwrap()), other_entity, )); } }); }