diff --git a/src/ai.rs b/src/ai.rs index 52deb3e..a893db2 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -1,13 +1,17 @@ -mod animal_ai_system; -mod bystander_ai_system; +mod adjacent_ai_system; +mod approach_ai_system; +mod default_move_system; +mod flee_ai_system; mod initiative_system; -mod monster_ai_system; mod quipping; mod turn_status; +mod visible_ai_system; -pub use animal_ai_system::AnimalAI; -pub use bystander_ai_system::BystanderAI; +pub use adjacent_ai_system::AdjacentAI; +pub use approach_ai_system::ApproachAI; +pub use default_move_system::DefaultMoveAI; +pub use flee_ai_system::FleeAI; pub use initiative_system::InitiativeSystem; -pub use monster_ai_system::MonsterAI; pub use quipping::QuipSystem; pub use turn_status::TurnStatusSystem; +pub use visible_ai_system::VisibleAI; diff --git a/src/ai/adjacent_ai_system.rs b/src/ai/adjacent_ai_system.rs new file mode 100644 index 0000000..6a0ec23 --- /dev/null +++ b/src/ai/adjacent_ai_system.rs @@ -0,0 +1,136 @@ +use ::specs::prelude::*; + +use crate::components::{Faction, MyTurn, Position, WantsToMelee}; +use crate::raws::Reaction; +use crate::Map; + +pub struct AdjacentAI {} + +impl<'a> System<'a> for AdjacentAI { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteStorage<'a, MyTurn>, + ReadStorage<'a, Faction>, + ReadStorage<'a, Position>, + ReadExpect<'a, Map>, + WriteStorage<'a, WantsToMelee>, + Entities<'a>, + ReadExpect<'a, Entity>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut turns, factions, positions, map, mut want_melee, entities, player) = data; + + let mut turn_done: Vec = Vec::new(); + for (entity, _turn, my_faction, pos) in (&entities, &turns, &factions, &positions).join() { + if entity != *player { + let mut reactions: Vec<(Entity, Reaction)> = Vec::new(); + let idx = map.xy_idx(pos.x, pos.y); + let w = map.width; + let h = map.height; + + // Add possible reactions to adjacents for each direction + if pos.x > 0 { + evaluate(idx - 1, &map, &factions, &my_faction.name, &mut reactions); + } + if pos.x < w - 1 { + evaluate(idx + 1, &map, &factions, &my_faction.name, &mut reactions); + } + if pos.y > 0 { + evaluate( + idx - w as usize, + &map, + &factions, + &my_faction.name, + &mut reactions, + ); + } + if pos.y < h - 1 { + evaluate( + idx + w as usize, + &map, + &factions, + &my_faction.name, + &mut reactions, + ); + } + if pos.y > 0 && pos.x > 0 { + evaluate( + (idx - w as usize) - 1, + &map, + &factions, + &my_faction.name, + &mut reactions, + ); + } + if pos.y > 0 && pos.x < w - 1 { + evaluate( + (idx - w as usize) + 1, + &map, + &factions, + &my_faction.name, + &mut reactions, + ); + } + if pos.y < h - 1 && pos.x > 0 { + evaluate( + (idx + w as usize) - 1, + &map, + &factions, + &my_faction.name, + &mut reactions, + ); + } + if pos.y < h - 1 && pos.x < w - 1 { + evaluate( + (idx + w as usize) + 1, + &map, + &factions, + &my_faction.name, + &mut reactions, + ); + } + + let mut done = false; + for reaction in reactions.iter() { + if let Reaction::Attack = reaction.1 { + want_melee + .insert(entity, WantsToMelee { target: reaction.0 }) + .expect("Failed to insert intention to attack"); + done = true; + } + } + + if done { + turn_done.push(entity); + } + } + } + + // Remove turn marker for those that are done + for done in turn_done.iter() { + turns.remove(*done); + } + } +} + +fn evaluate( + idx: usize, + map: &Map, + factions: &ReadStorage, + my_faction: &str, + reactions: &mut Vec<(Entity, Reaction)>, +) { + for other_entity in map.tile_content[idx].iter() { + if let Some(faction) = factions.get(*other_entity) { + reactions.push(( + *other_entity, + crate::raws::faction_reaction( + my_faction, + &faction.name, + &crate::raws::RAWS.lock().unwrap(), + ), + )) + } + } +} diff --git a/src/ai/animal_ai_system.rs b/src/ai/animal_ai_system.rs deleted file mode 100644 index 6af932c..0000000 --- a/src/ai/animal_ai_system.rs +++ /dev/null @@ -1,141 +0,0 @@ -use ::rltk::{DijkstraMap, DistanceAlg, Point}; -use ::specs::prelude::*; - -use crate::components::{ - Carnivore, EntityMoved, Herbivore, Item, MyTurn, Position, Viewshed, WantsToMelee, -}; -use crate::{Map, RunState}; - -pub struct AnimalAI {} - -impl<'a> System<'a> for AnimalAI { - #[allow(clippy::type_complexity)] - type SystemData = ( - WriteExpect<'a, Map>, - ReadExpect<'a, Entity>, - ReadExpect<'a, RunState>, - Entities<'a>, - WriteStorage<'a, Viewshed>, - ReadStorage<'a, Herbivore>, - ReadStorage<'a, Carnivore>, - ReadStorage<'a, Item>, - WriteStorage<'a, WantsToMelee>, - WriteStorage<'a, EntityMoved>, - WriteStorage<'a, Position>, - ReadStorage<'a, MyTurn>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut map, - player_entity, - runstate, - entities, - mut viewshed, - herbivore, - carnivore, - item, - mut wants_to_melee, - mut entity_moved, - mut position, - turns, - ) = data; - - // Herbivores run away a lot - for (entity, mut viewshed, _herbivore, mut pos, _turn) in - (&entities, &mut viewshed, &herbivore, &mut position, &turns).join() - { - let mut run_away_from: Vec = Vec::new(); - for other_tile in viewshed.visible_tiles.iter() { - let view_idx = map.xy_idx(other_tile.x, other_tile.y); - for other_entity in map.tile_content[view_idx].iter() { - // They don't run away from items - if item.get(*other_entity).is_none() { - run_away_from.push(view_idx); - } - } - } - - if !run_away_from.is_empty() { - let my_idx = map.xy_idx(pos.x, pos.y); - map.populate_blocked(); - let flee_map = DijkstraMap::new( - map.width as usize, - map.height as usize, - &run_away_from, - &*map, - 100.0, - ); - if let Some(flee_target) = DijkstraMap::find_highest_exit(&flee_map, my_idx, &*map) - { - if !map.blocked[flee_target] { - map.blocked[my_idx] = false; - map.blocked[flee_target] = true; - viewshed.dirty = true; - pos.x = flee_target as i32 % map.width; - pos.y = flee_target as i32 / map.width; - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to insert entity moved flag component"); - } - } - } - } - - // Carnivores just want to eat everything - for (entity, mut viewshed, _carnivore, mut pos, _turn) in - (&entities, &mut viewshed, &carnivore, &mut position, &turns).join() - { - let mut run_towards: Vec = Vec::new(); - let mut attacked = false; - for other_tile in viewshed.visible_tiles.iter() { - let view_idx = map.xy_idx(other_tile.x, other_tile.y); - for other_entity in map.tile_content[view_idx].iter() { - if herbivore.get(*other_entity).is_some() || *other_entity == *player_entity { - let distance = DistanceAlg::Pythagoras - .distance2d(Point::new(pos.x, pos.y), *other_tile); - if distance < 1.5 { - wants_to_melee - .insert( - entity, - WantsToMelee { - target: *other_entity, - }, - ) - .expect("Unable to insert attack"); - - attacked = true; - } else { - run_towards.push(view_idx); - } - } - } - } - - if !run_towards.is_empty() && !attacked { - let my_idx = map.xy_idx(pos.x, pos.y); - map.populate_blocked(); - let chase_map = DijkstraMap::new( - map.width as usize, - map.height as usize, - &run_towards, - &*map, - 100.0, - ); - if let Some(chase_target) = DijkstraMap::find_lowest_exit(&chase_map, my_idx, &*map) - { - if !map.blocked[chase_target] { - map.blocked[my_idx] = false; - map.blocked[chase_target] = true; - viewshed.dirty = true; - pos.x = chase_target as i32 % map.width; - pos.y = chase_target as i32 / map.width; - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to insert entity moved flag component"); - } - } - } - } - } -} diff --git a/src/ai/approach_ai_system.rs b/src/ai/approach_ai_system.rs new file mode 100644 index 0000000..c3f3cc1 --- /dev/null +++ b/src/ai/approach_ai_system.rs @@ -0,0 +1,72 @@ +use ::rltk::a_star_search; +use ::specs::prelude::*; + +use crate::components::{EntityMoved, MyTurn, Position, Viewshed, WantsToApproach}; +use crate::Map; + +pub struct ApproachAI {} + +impl<'a> System<'a> for ApproachAI { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteStorage<'a, MyTurn>, + WriteStorage<'a, WantsToApproach>, + WriteStorage<'a, Position>, + WriteExpect<'a, Map>, + WriteStorage<'a, Viewshed>, + WriteStorage<'a, EntityMoved>, + Entities<'a>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + mut turns, + mut want_approach, + mut positions, + mut map, + mut viewsheds, + mut entity_moved, + entities, + ) = data; + + let mut turn_done: Vec = Vec::new(); + for (entity, mut pos, approach, mut viewshed, _myturn) in ( + &entities, + &mut positions, + &want_approach, + &mut viewsheds, + &turns, + ) + .join() + { + turn_done.push(entity); + + let path = a_star_search( + map.xy_idx(pos.x, pos.y) as i32, + map.xy_idx(approach.idx % map.width, approach.idx / map.width) as i32, + &*map, + ); + + if path.success && path.steps.len() > 1 { + let mut idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = false; + pos.x = path.steps[1] as i32 % map.width; + pos.y = path.steps[1] as i32 / map.width; + entity_moved + .insert(entity, EntityMoved {}) + .expect("Unable to insert moved marker"); + + idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = true; + viewshed.dirty = true; + } + } + + want_approach.clear(); + + // Remove turn marker for those that are done + for done in turn_done.iter() { + turns.remove(*done); + } + } +} diff --git a/src/ai/bystander_ai_system.rs b/src/ai/bystander_ai_system.rs deleted file mode 100644 index 74c50b4..0000000 --- a/src/ai/bystander_ai_system.rs +++ /dev/null @@ -1,67 +0,0 @@ -use ::rltk::RandomNumberGenerator; -use specs::prelude::*; - -use crate::components::{Bystander, EntityMoved, MyTurn, Position, Viewshed}; -use crate::Map; - -pub struct BystanderAI {} - -impl<'a> System<'a> for BystanderAI { - #[allow(clippy::type_complexity)] - type SystemData = ( - WriteExpect<'a, Map>, - Entities<'a>, - WriteStorage<'a, Viewshed>, - ReadStorage<'a, Bystander>, - WriteStorage<'a, Position>, - WriteStorage<'a, EntityMoved>, - WriteExpect<'a, RandomNumberGenerator>, - ReadStorage<'a, MyTurn>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut map, - entities, - mut viewshed, - bystander, - mut position, - mut entity_moved, - mut rng, - turns, - ) = data; - - for (entity, mut viewshed, _bystander, mut pos, _turn) in - (&entities, &mut viewshed, &bystander, &mut position, &turns).join() - { - // Try to move randomly - let mut x = pos.x; - let mut y = pos.y; - match rng.roll_dice(1, 5) { - 1 => x -= 1, - 2 => x += 1, - 3 => y -= 1, - 4 => y += 1, - _ => {} - } - - if x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1 { - let dest_idx = map.xy_idx(x, y); - - if !map.blocked[dest_idx] { - let idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = false; - - pos.x = x; - pos.y = y; - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to insert moved flag component"); - - map.blocked[dest_idx] = true; - viewshed.dirty = true; - } - } - } - } -} diff --git a/src/ai/default_move_system.rs b/src/ai/default_move_system.rs new file mode 100644 index 0000000..7e8f4ab --- /dev/null +++ b/src/ai/default_move_system.rs @@ -0,0 +1,83 @@ +use ::rltk::RandomNumberGenerator; +use ::specs::prelude::*; + +use crate::components::{EntityMoved, MoveMode, Movement, MyTurn, Position, Viewshed}; +use crate::Map; + +pub struct DefaultMoveAI {} + +impl<'a> System<'a> for DefaultMoveAI { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteStorage<'a, MyTurn>, + ReadStorage<'a, MoveMode>, + WriteStorage<'a, Position>, + WriteExpect<'a, Map>, + WriteStorage<'a, Viewshed>, + WriteStorage<'a, EntityMoved>, + WriteExpect<'a, RandomNumberGenerator>, + Entities<'a>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + mut turns, + move_mode, + mut positions, + mut map, + mut viewsheds, + mut entity_moved, + mut rng, + entities, + ) = data; + + let mut turn_done: Vec = Vec::new(); + for (entity, mut pos, mode, mut viewshed, _myturn) in ( + &entities, + &mut positions, + &move_mode, + &mut viewsheds, + &turns, + ) + .join() + { + turn_done.push(entity); + + match mode.mode { + Movement::Static => {} + Movement::Random => { + let mut x = pos.x; + let mut y = pos.y; + + match rng.roll_dice(1, 5) { + 1 => x -= 1, + 2 => x += 1, + 3 => y -= 1, + 4 => y += 1, + _ => {} + } + + if x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1 { + let dest_idx = map.xy_idx(x, y); + if !map.blocked[dest_idx] { + let idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = false; + pos.x = x; + pos.y = y; + entity_moved + .insert(entity, EntityMoved {}) + .expect("Unable to insert movement marker"); + map.blocked[dest_idx] = true; + viewshed.dirty = true; + } + } + } + } + } + + // Remove turn marker for those that are done + for done in turn_done.iter() { + turns.remove(*done); + } + } +} diff --git a/src/ai/flee_ai_system.rs b/src/ai/flee_ai_system.rs new file mode 100644 index 0000000..54fde06 --- /dev/null +++ b/src/ai/flee_ai_system.rs @@ -0,0 +1,69 @@ +use ::rltk::DijkstraMap; +use ::specs::prelude::*; + +use crate::components::{EntityMoved, MyTurn, Position, Viewshed, WantsToFlee}; +use crate::Map; + +pub struct FleeAI {} + +impl<'a> System<'a> for FleeAI { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteStorage<'a, MyTurn>, + WriteStorage<'a, WantsToFlee>, + WriteStorage<'a, Position>, + WriteExpect<'a, Map>, + WriteStorage<'a, Viewshed>, + WriteStorage<'a, EntityMoved>, + Entities<'a>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + mut turns, + mut want_flee, + mut positions, + mut map, + mut viewsheds, + mut entity_moved, + entities, + ) = data; + + let mut turn_done: Vec = Vec::new(); + for (entity, mut pos, flee, mut viewshed, _myturn) in ( + &entities, + &mut positions, + &want_flee, + &mut viewsheds, + &turns, + ) + .join() + { + turn_done.push(entity); + + let my_idx = map.xy_idx(pos.x, pos.y); + map.populate_blocked(); + + let flee_map = DijkstraMap::new(map.width, map.height, &flee.indices, &*map, 100.0); + if let Some(flee_target) = DijkstraMap::find_highest_exit(&flee_map, my_idx, &*map) { + if !map.blocked[flee_target] { + map.blocked[my_idx] = false; + map.blocked[flee_target] = true; + viewshed.dirty = true; + pos.x = flee_target as i32 % map.width; + pos.y = flee_target as i32 / map.width; + entity_moved + .insert(entity, EntityMoved {}) + .expect("Unable to insert intent to flee"); + } + } + } + + want_flee.clear(); + + // Remove turn marker for those that are done + for done in turn_done.iter() { + turns.remove(*done); + } + } +} diff --git a/src/ai/monster_ai_system.rs b/src/ai/monster_ai_system.rs deleted file mode 100644 index 2f8d07d..0000000 --- a/src/ai/monster_ai_system.rs +++ /dev/null @@ -1,79 +0,0 @@ -use ::rltk::Point; -use ::specs::prelude::*; - -use crate::components::{EntityMoved, Monster, MyTurn, Position, Viewshed, WantsToMelee}; -use crate::Map; - -pub struct MonsterAI {} - -impl<'a> System<'a> for MonsterAI { - #[allow(clippy::type_complexity)] - type SystemData = ( - WriteExpect<'a, Map>, - ReadExpect<'a, Point>, - ReadExpect<'a, Entity>, - Entities<'a>, - WriteStorage<'a, Viewshed>, - ReadStorage<'a, Monster>, - WriteStorage<'a, Position>, - WriteStorage<'a, WantsToMelee>, - WriteStorage<'a, EntityMoved>, - ReadStorage<'a, MyTurn>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut map, - player_pos, - player_entity, - entities, - mut viewshed, - monster, - mut position, - mut wants_to_melee, - mut entity_moved, - turns, - ) = data; - - for (entity, mut viewshed, _monster, mut pos, _turn) in - (&entities, &mut viewshed, &monster, &mut position, &turns).join() - { - let distance = - rltk::DistanceAlg::Pythagoras.distance2d(Point::new(pos.x, pos.y), *player_pos); - if distance < 1.5 { - // Attack goes here - wants_to_melee - .insert( - entity, - WantsToMelee { - target: *player_entity, - }, - ) - .expect("Unable to insert attack"); - } else if viewshed.visible_tiles.contains(&*player_pos) { - // The path to the player - let path = rltk::a_star_search( - map.xy_idx(pos.x, pos.y) as i32, - map.xy_idx(player_pos.x, player_pos.y) as i32, - &*map, - ); - - if path.success && path.steps.len() > 1 { - let mut idx = map.xy_idx(pos.x, pos.y); - - map.blocked[idx] = false; - pos.x = path.steps[1] as i32 % map.width; - pos.y = path.steps[1] as i32 / map.width; - - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to add EntityMoved flag to monster"); - - idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = true; - viewshed.dirty = true; - } - } - } - } -} diff --git a/src/ai/visible_ai_system.rs b/src/ai/visible_ai_system.rs new file mode 100644 index 0000000..f27f077 --- /dev/null +++ b/src/ai/visible_ai_system.rs @@ -0,0 +1,100 @@ +use ::specs::prelude::*; + +use crate::components::{Faction, MyTurn, Position, Viewshed, WantsToApproach, WantsToFlee}; +use crate::raws::Reaction; +use crate::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>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + turns, + factions, + positions, + map, + mut want_approach, + mut want_flee, + entities, + player, + viewshed, + ) = data; + + for (entity, _turn, my_faction, pos, viewshed) in + (&entities, &turns, &factions, &positions, &viewshed).join() + { + if entity != *player { + let my_idx = map.xy_idx(pos.x, pos.y); + let mut reactions: Vec<(usize, Reaction)> = 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 => { + want_approach + .insert( + entity, + WantsToApproach { + idx: reaction.0 as i32, + }, + ) + .expect("Unable to insert intent to approach"); + 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)>, +) { + for other_entity in map.tile_content[idx].iter() { + if let Some(faction) = factions.get(*other_entity) { + reactions.push(( + idx, + crate::raws::faction_reaction( + my_faction, + &faction.name, + &crate::raws::RAWS.lock().unwrap(), + ), + )); + } + } +} diff --git a/src/components.rs b/src/components.rs index 746024d..e1b0afa 100644 --- a/src/components.rs +++ b/src/components.rs @@ -296,6 +296,34 @@ pub struct Initiative { pub current: i32, } +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Faction { + pub name: String, +} + +impl Faction { + pub fn from(s: S) -> Self { + Faction { + name: s.to_string(), + } + } +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct WantsToApproach { + pub idx: i32, +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct WantsToFlee { + pub indices: Vec, +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct MoveMode { + pub mode: Movement, +} + // Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an // Entity. diff --git a/src/components/enums.rs b/src/components/enums.rs index aaf4d74..ee524ce 100644 --- a/src/components/enums.rs +++ b/src/components/enums.rs @@ -22,6 +22,12 @@ pub enum HungerState { Starving, } +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] +pub enum Movement { + Static, + Random, +} + #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] pub enum Skill { Melee, diff --git a/src/hunger_system.rs b/src/hunger_system.rs index 4b48e8e..bd6b363 100644 --- a/src/hunger_system.rs +++ b/src/hunger_system.rs @@ -2,7 +2,6 @@ use ::specs::prelude::*; use crate::components::{HungerClock, HungerState, MyTurn, SufferDamage}; use crate::game_log::GameLog; -use crate::RunState; pub struct HungerSystem {} @@ -12,22 +11,13 @@ impl<'a> System<'a> for HungerSystem { Entities<'a>, WriteStorage<'a, HungerClock>, ReadExpect<'a, Entity>, // The player - ReadExpect<'a, RunState>, WriteStorage<'a, SufferDamage>, WriteExpect<'a, GameLog>, ReadStorage<'a, MyTurn>, ); fn run(&mut self, data: Self::SystemData) { - let ( - entities, - mut hunger_clock, - player_entity, - runstate, - mut inflict_damage, - mut log, - turns, - ) = data; + let (entities, mut hunger_clock, player_entity, mut inflict_damage, mut log, turns) = data; for (entity, mut clock, _myturn) in (&entities, &mut hunger_clock, &turns).join() { clock.duration -= 1; diff --git a/src/main.rs b/src/main.rs index efbe96b..40ee52b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,14 +121,20 @@ impl State { let mut quipper = ai::QuipSystem {}; quipper.run_now(&self.ecs); - let mut mob = ai::MonsterAI {}; - mob.run_now(&self.ecs); + let mut adjacent = ai::AdjacentAI {}; + adjacent.run_now(&self.ecs); - let mut animal = ai::AnimalAI {}; - animal.run_now(&self.ecs); + let mut visible = ai::VisibleAI {}; + visible.run_now(&self.ecs); - let mut bystander = ai::BystanderAI {}; - bystander.run_now(&self.ecs); + let mut approach = ai::ApproachAI {}; + approach.run_now(&self.ecs); + + let mut flee = ai::FleeAI {}; + flee.run_now(&self.ecs); + + let mut defaultmove = ai::DefaultMoveAI {}; + defaultmove.run_now(&self.ecs); let mut triggers = TriggerSystem {}; triggers.run_now(&self.ecs); @@ -464,6 +470,7 @@ fn main() -> ::rltk::BError { EntryTrigger, Equippable, Equipped, + Faction, Herbivore, Hidden, HungerClock, @@ -476,6 +483,7 @@ fn main() -> ::rltk::BError { MagicMapper, MeleeWeapon, Monster, + MoveMode, MyTurn, Name, NaturalAttackDefense, @@ -496,7 +504,9 @@ fn main() -> ::rltk::BError { SufferDamage, Vendor, Viewshed, + WantsToApproach, WantsToDropItem, + WantsToFlee, WantsToMelee, WantsToPickupItem, WantsToRemoveItem, diff --git a/src/raws.rs b/src/raws.rs index bd8e29b..34032ed 100644 --- a/src/raws.rs +++ b/src/raws.rs @@ -1,3 +1,4 @@ +mod faction_structs; mod item_structs; mod loot_structs; mod mob_structs; @@ -9,6 +10,7 @@ use std::sync::Mutex; use ::rltk::{embedded_resource, link_resource}; use ::serde::Deserialize; +pub use faction_structs::*; use item_structs::*; use loot_structs::*; use mob_structs::*; @@ -23,6 +25,7 @@ pub struct Raws { pub props: Vec, pub spawn_table: Vec, pub loot_tables: Vec, + pub faction_table: Vec, } embedded_resource!(RAW_FILE, "../raws/spawns.json"); diff --git a/src/raws/faction_structs.rs b/src/raws/faction_structs.rs new file mode 100644 index 0000000..d157b6d --- /dev/null +++ b/src/raws/faction_structs.rs @@ -0,0 +1,16 @@ +use std::collections::HashMap; + +use ::serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct FactionInfo { + pub name: String, + pub responses: HashMap, +} + +#[derive(PartialEq, Eq, Hash, Copy, Clone)] +pub enum Reaction { + Ignore, + Attack, + Flee, +} diff --git a/src/raws/mob_structs.rs b/src/raws/mob_structs.rs index 0ede0a0..e1517b8 100644 --- a/src/raws/mob_structs.rs +++ b/src/raws/mob_structs.rs @@ -10,7 +10,7 @@ pub struct Mob { pub renderable: Option, pub blocks_tile: bool, pub vision_range: i32, - pub ai: String, + pub movement: String, pub quips: Option>, pub attributes: MobAttributes, pub skills: Option>, @@ -21,6 +21,7 @@ pub struct Mob { pub natural: Option, pub loot_table: Option, pub light: Option, + pub faction: Option, } #[derive(Deserialize, Debug)] diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 6d390da..440094b 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -8,7 +8,7 @@ use ::specs::saveload::{MarkedBuilder, SimpleMarker}; use crate::components::*; use crate::gamesystem::{mana_at_level, npc_hp}; use crate::random_table::RandomTable; -use crate::raws::Raws; +use crate::raws::{Raws, Reaction}; pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) { lazy_static! { @@ -45,6 +45,7 @@ pub struct RawMaster { mob_index: HashMap, prop_index: HashMap, loot_index: HashMap, + faction_index: HashMap>, } impl RawMaster { @@ -56,11 +57,13 @@ impl RawMaster { props: Vec::new(), spawn_table: Vec::new(), loot_tables: Vec::new(), + faction_table: Vec::new(), }, item_index: HashMap::new(), mob_index: HashMap::new(), prop_index: HashMap::new(), loot_index: HashMap::new(), + faction_index: HashMap::new(), } } @@ -111,9 +114,41 @@ impl RawMaster { for (i, loot) in self.raws.loot_tables.iter().enumerate() { self.loot_index.insert(loot.name.clone(), i); } + + for faction in self.raws.faction_table.iter() { + let mut reactions: HashMap = HashMap::new(); + for other in faction.responses.iter() { + reactions.insert( + other.0.clone(), + match other.1.as_str() { + "ignore" => Reaction::Ignore, + "flee" => Reaction::Flee, + _ => Reaction::Attack, + }, + ); + } + self.faction_index.insert(faction.name.clone(), reactions); + } } } +#[inline(always)] +pub fn faction_reaction(my_faction: &str, their_faction: &str, raws: &RawMaster) -> Reaction { + if raws.faction_index.contains_key(my_faction) { + let mf = &raws.faction_index[my_faction]; + + return if mf.contains_key(their_faction) { + mf[their_faction] + } else if mf.contains_key("Default") { + mf["Default"] + } else { + Reaction::Ignore + }; + } + + Reaction::Ignore +} + fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot { if !raws.item_index.contains_key(tag) { panic!("Trying to equip an unknown item: {}", tag); @@ -303,13 +338,13 @@ pub fn spawn_named_mob( eb = eb.with(Name::from(&mob_template.name)); - match mob_template.ai.as_ref() { - "melee" => eb = eb.with(Monster {}), - "bystander" => eb = eb.with(Bystander {}), - "vendor" => eb = eb.with(Vendor {}), - "carnivore" => eb = eb.with(Carnivore {}), - "herbivore" => eb = eb.with(Herbivore {}), - _ => {} + eb = match mob_template.movement.as_ref() { + "random" => eb.with(MoveMode { + mode: Movement::Random, + }), + _ => eb.with(MoveMode { + mode: Movement::Static, + }), }; // Quips @@ -430,6 +465,12 @@ pub fn spawn_named_mob( }); } + if let Some(faction) = &mob_template.faction { + eb = eb.with(Faction::from(faction)); + } else { + eb = eb.with(Faction::from("Mindless")); + } + let new_mob = eb.build(); // Are they weilding anything? diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 73801fc..7ed9cea 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -74,6 +74,7 @@ pub fn save_game(ecs: &mut World) { EntryTrigger, Equippable, Equipped, + Faction, Herbivore, Hidden, HungerClock, @@ -86,6 +87,7 @@ pub fn save_game(ecs: &mut World) { MagicMapper, MeleeWeapon, Monster, + MoveMode, MyTurn, Name, NaturalAttackDefense, @@ -105,7 +107,9 @@ pub fn save_game(ecs: &mut World) { SufferDamage, Vendor, Viewshed, + WantsToApproach, WantsToDropItem, + WantsToFlee, WantsToMelee, WantsToPickupItem, WantsToRemoveItem, @@ -181,6 +185,7 @@ pub fn load_game(ecs: &mut World) { EntryTrigger, Equippable, Equipped, + Faction, Herbivore, Hidden, HungerClock, @@ -193,6 +198,7 @@ pub fn load_game(ecs: &mut World) { MagicMapper, MeleeWeapon, Monster, + MoveMode, MyTurn, Name, NaturalAttackDefense, @@ -212,7 +218,9 @@ pub fn load_game(ecs: &mut World) { SufferDamage, Vendor, Viewshed, + WantsToApproach, WantsToDropItem, + WantsToFlee, WantsToMelee, WantsToPickupItem, WantsToRemoveItem, diff --git a/src/spawner.rs b/src/spawner.rs index e6700f9..061e4c9 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -55,6 +55,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { range: 8, }) .with(Initiative { current: 0 }) + .with(Faction::from("Player")) .marked::>() .build();