diff --git a/src/ai.rs b/src/ai.rs index a893db2..164827a 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -1,5 +1,6 @@ mod adjacent_ai_system; mod approach_ai_system; +mod chase_ai_system; mod default_move_system; mod flee_ai_system; mod initiative_system; @@ -9,6 +10,7 @@ mod visible_ai_system; pub use adjacent_ai_system::AdjacentAI; pub use approach_ai_system::ApproachAI; +pub use chase_ai_system::ChaseAI; pub use default_move_system::DefaultMoveAI; pub use flee_ai_system::FleeAI; pub use initiative_system::InitiativeSystem; diff --git a/src/ai/chase_ai_system.rs b/src/ai/chase_ai_system.rs new file mode 100644 index 0000000..935b954 --- /dev/null +++ b/src/ai/chase_ai_system.rs @@ -0,0 +1,85 @@ +use std::collections::HashMap; + +use ::specs::prelude::*; + +use crate::components::{Chasing, EntityMoved, MyTurn, Position, Viewshed}; +use crate::Map; + +pub struct ChaseAI {} + +impl<'a> System<'a> for ChaseAI { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteStorage<'a, MyTurn>, + WriteStorage<'a, Chasing>, + 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 chasing, + mut positions, + mut map, + mut viewsheds, + mut entity_moved, + entities, + ) = data; + + let mut targets: HashMap = HashMap::new(); + let mut end_chase: Vec = Vec::new(); + for (entity, _turn, chasing) in (&entities, &turns, &chasing).join() { + if let Some(target_pos) = positions.get(chasing.target) { + targets.insert(entity, (target_pos.x, target_pos.y)); + } else { + end_chase.push(entity); + } + } + + for done in end_chase.iter() { + chasing.remove(*done); + } + end_chase.clear(); + + let mut turn_done: Vec = Vec::new(); + for (entity, mut pos, _chase, mut viewshed, _myturn) in + (&entities, &mut positions, &chasing, &mut viewsheds, &turns).join() + { + turn_done.push(entity); + let target_pos = targets[&entity]; + let path = ::rltk::a_star_search( + map.xy_idx(pos.x, pos.y) as i32, + map.xy_idx(target_pos.0, target_pos.1) as i32, + &*map, + ); + + if path.success && path.steps.len() > 1 && path.steps.len() < 15 { + 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 movement marker"); + + idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = true; + viewshed.dirty = true; + turn_done.push(entity); + } else { + end_chase.push(entity); + } + } + + for done in end_chase.iter() { + chasing.remove(*done); + } + for done in turn_done.iter() { + turns.remove(*done); + } + } +} diff --git a/src/ai/default_move_system.rs b/src/ai/default_move_system.rs index 7e8f4ab..ac61920 100644 --- a/src/ai/default_move_system.rs +++ b/src/ai/default_move_system.rs @@ -2,7 +2,7 @@ use ::rltk::RandomNumberGenerator; use ::specs::prelude::*; use crate::components::{EntityMoved, MoveMode, Movement, MyTurn, Position, Viewshed}; -use crate::Map; +use crate::{tile_walkable, Map}; pub struct DefaultMoveAI {} @@ -10,7 +10,7 @@ impl<'a> System<'a> for DefaultMoveAI { #[allow(clippy::type_complexity)] type SystemData = ( WriteStorage<'a, MyTurn>, - ReadStorage<'a, MoveMode>, + WriteStorage<'a, MoveMode>, WriteStorage<'a, Position>, WriteExpect<'a, Map>, WriteStorage<'a, Viewshed>, @@ -22,7 +22,7 @@ impl<'a> System<'a> for DefaultMoveAI { fn run(&mut self, data: Self::SystemData) { let ( mut turns, - move_mode, + mut move_mode, mut positions, mut map, mut viewsheds, @@ -32,10 +32,10 @@ impl<'a> System<'a> for DefaultMoveAI { ) = data; let mut turn_done: Vec = Vec::new(); - for (entity, mut pos, mode, mut viewshed, _myturn) in ( + for (entity, mut pos, mut mode, mut viewshed, _myturn) in ( &entities, &mut positions, - &move_mode, + &mut move_mode, &mut viewsheds, &turns, ) @@ -43,7 +43,7 @@ impl<'a> System<'a> for DefaultMoveAI { { turn_done.push(entity); - match mode.mode { + match &mut mode.mode { Movement::Static => {} Movement::Random => { let mut x = pos.x; @@ -72,6 +72,46 @@ impl<'a> System<'a> for DefaultMoveAI { } } } + Movement::RandomWaypoint { path } => { + if let Some(path) = path { + // We have a target - go there + let mut idx = map.xy_idx(pos.x, pos.y); + if path.len() > 1 { + if !map.blocked[path[1]] { + map.blocked[idx] = false; + pos.x = (path[1] as i32 % map.width) as i32; + pos.y = (path[1] as i32 / map.width) as i32; + entity_moved + .insert(entity, EntityMoved {}) + .expect("Unable to insert movement marker"); + + idx = map.xy_idx(pos.x, pos.y); + map.blocked[idx] = true; + viewshed.dirty = true; + path.remove(0); // Remove the first step in the path + } + } else { + // Otherwise we wait a turn to see if the path clears up + mode.mode = Movement::RandomWaypoint { path: None }; + } + } else { + let target_x = rng.roll_dice(1, map.width - 2); + let target_y = rng.roll_dice(1, map.height - 2); + let idx = map.xy_idx(target_x, target_y); + if tile_walkable(map.tiles[idx]) { + let path = ::rltk::a_star_search( + map.xy_idx(pos.x, pos.y) as i32, + map.xy_idx(target_x, target_y) as i32, + &*map, + ); + if path.success && path.steps.len() > 1 { + mode.mode = Movement::RandomWaypoint { + path: Some(path.steps), + }; + } + } + } + } } } diff --git a/src/ai/initiative_system.rs b/src/ai/initiative_system.rs index e0ff4e4..8efa42d 100644 --- a/src/ai/initiative_system.rs +++ b/src/ai/initiative_system.rs @@ -1,4 +1,4 @@ -use ::rltk::RandomNumberGenerator; +use ::rltk::{DistanceAlg, Point, RandomNumberGenerator}; use ::specs::prelude::*; use crate::components::{Attributes, Initiative, MyTurn, Position}; @@ -17,6 +17,7 @@ impl<'a> System<'a> for InitiativeSystem { ReadStorage<'a, Attributes>, WriteExpect<'a, RunState>, ReadExpect<'a, Entity>, + ReadExpect<'a, Point>, ); fn run(&mut self, data: Self::SystemData) { @@ -29,23 +30,21 @@ impl<'a> System<'a> for InitiativeSystem { attributes, mut runstate, player, + player_pos, ) = data; if *runstate != RunState::Ticking { return; - } // We'll be adding Ticking in a moment + } // Clear any remaining MyTurn we left by mistake turns.clear(); // Roll initiative - for (entity, initiative, _pos) in (&entities, &mut initiatives, &positions).join() { + for (entity, initiative, pos) in (&entities, &mut initiatives, &positions).join() { initiative.current -= 1; if initiative.current < 1 { - // It's my turn - turns - .insert(entity, MyTurn {}) - .expect("Unable to insert turn"); + let mut myturn = true; // Re-roll initiative.current = 6 + rng.roll_dice(1, 6); @@ -60,6 +59,19 @@ impl<'a> System<'a> for InitiativeSystem { // if its the player, we want to go to an AwaitingInput state if entity == *player { *runstate = RunState::AwaitingInput; + } else { + let distance = + DistanceAlg::Pythagoras.distance2d(*player_pos, Point::from(*pos)); + if distance > 20.0 { + myturn = false; + } + } + + // It's my turn + if myturn { + turns + .insert(entity, MyTurn {}) + .expect("Unable to insert turn"); } } } diff --git a/src/ai/visible_ai_system.rs b/src/ai/visible_ai_system.rs index f27f077..aef546b 100644 --- a/src/ai/visible_ai_system.rs +++ b/src/ai/visible_ai_system.rs @@ -1,6 +1,8 @@ use ::specs::prelude::*; -use crate::components::{Faction, MyTurn, Position, Viewshed, WantsToApproach, WantsToFlee}; +use crate::components::{ + Chasing, Faction, MyTurn, Position, Viewshed, WantsToApproach, WantsToFlee, +}; use crate::raws::Reaction; use crate::Map; @@ -18,6 +20,7 @@ impl<'a> System<'a> for VisibleAI { Entities<'a>, ReadExpect<'a, Entity>, ReadStorage<'a, Viewshed>, + WriteStorage<'a, Chasing>, ); fn run(&mut self, data: Self::SystemData) { @@ -30,15 +33,16 @@ impl<'a> System<'a> for VisibleAI { mut want_flee, entities, player, - viewshed, + viewsheds, + mut chasing, ) = data; for (entity, _turn, my_faction, pos, viewshed) in - (&entities, &turns, &factions, &positions, &viewshed).join() + (&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)> = Vec::new(); + 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); @@ -59,6 +63,9 @@ impl<'a> System<'a> for VisibleAI { }, ) .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 => { @@ -83,7 +90,7 @@ fn evaluate( map: &Map, factions: &ReadStorage, my_faction: &str, - reactions: &mut Vec<(usize, Reaction)>, + reactions: &mut Vec<(usize, Reaction, Entity)>, ) { for other_entity in map.tile_content[idx].iter() { if let Some(faction) = factions.get(*other_entity) { @@ -94,6 +101,7 @@ fn evaluate( &faction.name, &crate::raws::RAWS.lock().unwrap(), ), + *other_entity, )); } } diff --git a/src/components.rs b/src/components.rs index e1b0afa..32ac0fe 100644 --- a/src/components.rs +++ b/src/components.rs @@ -324,6 +324,11 @@ pub struct MoveMode { pub mode: Movement, } +#[derive(Component, Debug, ConvertSaveload, Clone)] +pub struct Chasing { + pub target: Entity, +} + // Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an // Entity. @@ -337,5 +342,5 @@ pub struct SerializationHelper { #[derive(Component, Serialize, Deserialize, Clone)] pub struct DMSerializationHelper { - pub map: super::map::MasterDungeonMap, + pub map: crate::map::MasterDungeonMap, } diff --git a/src/components/enums.rs b/src/components/enums.rs index ee524ce..67469cd 100644 --- a/src/components/enums.rs +++ b/src/components/enums.rs @@ -26,6 +26,7 @@ pub enum HungerState { pub enum Movement { Static, Random, + RandomWaypoint { path: Option> }, } #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] diff --git a/src/components/simple.rs b/src/components/simple.rs index 9f0b38d..ea58651 100644 --- a/src/components/simple.rs +++ b/src/components/simple.rs @@ -5,9 +5,6 @@ use ::specs_derive::*; #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Player {} -#[derive(Component, Debug, Serialize, Deserialize, Clone)] -pub struct Monster {} - #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Item {} @@ -38,17 +35,5 @@ pub struct SingleActivation {} #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct BlocksVisibility {} -#[derive(Component, Debug, Serialize, Deserialize, Clone)] -pub struct Bystander {} - -#[derive(Component, Debug, Serialize, Deserialize, Clone)] -pub struct Vendor {} - -#[derive(Component, Debug, Serialize, Deserialize, Clone)] -pub struct Carnivore {} - -#[derive(Component, Debug, Serialize, Deserialize, Clone)] -pub struct Herbivore {} - #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct MyTurn {} diff --git a/src/main.rs b/src/main.rs index 40ee52b..2ab5cc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,6 +133,9 @@ impl State { let mut flee = ai::FleeAI {}; flee.run_now(&self.ecs); + let mut chase = ai::ChaseAI {}; + chase.run_now(&self.ecs); + let mut defaultmove = ai::DefaultMoveAI {}; defaultmove.run_now(&self.ecs); @@ -218,14 +221,16 @@ impl GameState for State { newrunstate = player_input(self, ctx); } RunState::Ticking => { - self.run_systems(); - self.ecs.maintain(); + while newrunstate == RunState::Ticking { + self.run_systems(); + self.ecs.maintain(); - newrunstate = match *self.ecs.fetch::() { - RunState::AwaitingInput => RunState::AwaitingInput, - RunState::MagicMapReveal { .. } => RunState::MagicMapReveal { row: 0 }, - _ => RunState::Ticking, - }; + newrunstate = match *self.ecs.fetch::() { + RunState::AwaitingInput => RunState::AwaitingInput, + RunState::MagicMapReveal { .. } => RunState::MagicMapReveal { row: 0 }, + _ => RunState::Ticking, + }; + } } RunState::ShowInventory => { let result = gui::show_inventory(self, ctx); @@ -460,8 +465,7 @@ fn main() -> ::rltk::BError { Attributes, BlocksTile, BlocksVisibility, - Bystander, - Carnivore, + Chasing, Confusion, Consumable, Door, @@ -471,7 +475,6 @@ fn main() -> ::rltk::BError { Equippable, Equipped, Faction, - Herbivore, Hidden, HungerClock, InBackpack, @@ -482,7 +485,6 @@ fn main() -> ::rltk::BError { LootTable, MagicMapper, MeleeWeapon, - Monster, MoveMode, MyTurn, Name, @@ -502,7 +504,6 @@ fn main() -> ::rltk::BError { SingleActivation, Skills, SufferDamage, - Vendor, Viewshed, WantsToApproach, WantsToDropItem, diff --git a/src/player.rs b/src/player.rs index 159a487..ead955e 100644 --- a/src/player.rs +++ b/src/player.rs @@ -4,18 +4,19 @@ use ::rltk::{Point, Rltk, VirtualKeyCode}; use ::specs::prelude::*; use crate::components::{ - BlocksTile, BlocksVisibility, Door, EntityMoved, HungerClock, HungerState, Item, Monster, - Player, Pools, Position, Renderable, Vendor, Viewshed, WantsToMelee, WantsToPickupItem, + Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved, Faction, HungerClock, HungerState, + Item, Player, Pools, Position, Renderable, Viewshed, WantsToMelee, WantsToPickupItem, }; use crate::game_log::GameLog; -use crate::{Bystander, Map, RunState, State, TileType}; +use crate::raws::Reaction; +use crate::{Map, RunState, State, TileType}; pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState { let mut positions = ecs.write_storage::(); let players = ecs.read_storage::(); let mut viewsheds = ecs.write_storage::(); let entities = ecs.entities(); - let combat_stats = ecs.read_storage::(); + let combat_stats = ecs.read_storage::(); let map = ecs.fetch::(); let mut wants_to_melee = ecs.write_storage::(); let mut entity_moved = ecs.write_storage::(); @@ -23,8 +24,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState let mut blocks_visibility = ecs.write_storage::(); let mut blocks_movement = ecs.write_storage::(); let mut renderables = ecs.write_storage::(); - let bystanders = ecs.read_storage::(); - let vendors = ecs.read_storage::(); + let factions = ecs.read_storage::(); let mut result = RunState::AwaitingInput; let mut swap_entities: Vec<(Entity, i32, i32)> = Vec::new(); @@ -42,9 +42,20 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); for potential_target in map.tile_content[destination_idx].iter() { - let bystander = bystanders.get(*potential_target); - let vendor = vendors.get(*potential_target); - if bystander.is_some() || vendor.is_some() { + let mut hostile = true; + if combat_stats.get(*potential_target).is_some() { + if let Some(faction) = factions.get(*potential_target) { + let reaction = crate::raws::faction_reaction( + &faction.name, + "Player", + &crate::raws::RAWS.lock().unwrap(), + ); + if reaction != Reaction::Attack { + hostile = false; + } + } + } + if !hostile { // Note that we want to move the bystander swap_entities.push((*potential_target, pos.x, pos.y)); @@ -53,34 +64,36 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState pos.y = min(map.height - 1, max(0, pos.y + delta_y)); entity_moved .insert(entity, EntityMoved {}) - .expect("Unable to insert moved entity marker"); + .expect("Unable to insert marker"); viewshed.dirty = true; let mut ppos = ecs.write_resource::(); ppos.x = pos.x; ppos.y = pos.y; - result = RunState::Ticking; - } else if combat_stats.get(*potential_target).is_some() { - wants_to_melee - .insert( - entity, - WantsToMelee { - target: *potential_target, - }, - ) - .expect("Add target failed"); + } else { + let target = combat_stats.get(*potential_target); + if let Some(_target) = target { + wants_to_melee + .insert( + entity, + WantsToMelee { + target: *potential_target, + }, + ) + .expect("Add target failed"); - return RunState::Ticking; + return RunState::Ticking; + } } - - if let Some(door) = doors.get_mut(*potential_target) { + let door = doors.get_mut(*potential_target); + if let Some(door) = door { door.open = true; blocks_visibility.remove(*potential_target); blocks_movement.remove(*potential_target); - let glyph = renderables.get_mut(*potential_target).unwrap(); glyph.glyph = rltk::to_cp437('/'); viewshed.dirty = true; + result = RunState::Ticking; } } @@ -95,18 +108,18 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState let mut ppos = ecs.write_resource::(); ppos.x = pos.x; ppos.y = pos.y; - - // Change levels by running onto a set of stairs - result = match map.tiles[destination_idx] { - TileType::DownStairs => RunState::NextLevel, - TileType::UpStairs => RunState::PreviousLevel, - _ => RunState::Ticking, + result = RunState::Ticking; + match map.tiles[destination_idx] { + TileType::DownStairs => result = RunState::NextLevel, + TileType::UpStairs => result = RunState::PreviousLevel, + _ => {} } } } for m in swap_entities.iter() { - if let Some(their_pos) = positions.get_mut(m.0) { + let their_pos = positions.get_mut(m.0); + if let Some(their_pos) = their_pos { their_pos.x = m.1; their_pos.y = m.2; } @@ -184,7 +197,7 @@ fn get_item(ecs: &mut World) { fn skip_turn(ecs: &mut World) -> RunState { let player_entity = ecs.fetch::(); let viewshed_components = ecs.read_storage::(); - let monsters = ecs.read_storage::(); + let factions = ecs.read_storage::(); let worldmap_resource = ecs.fetch::(); @@ -193,10 +206,17 @@ fn skip_turn(ecs: &mut World) -> RunState { for tile in viewshed.visible_tiles.iter() { let idx = worldmap_resource.xy_idx(tile.x, tile.y); for entity_id in worldmap_resource.tile_content[idx].iter() { - match monsters.get(*entity_id) { + match factions.get(*entity_id) { None => {} - Some(_) => { - can_heal = false; + Some(faction) => { + let reaction = crate::raws::faction_reaction( + &faction.name, + "Player", + &crate::raws::RAWS.lock().unwrap(), + ); + if reaction == Reaction::Attack { + can_heal = false; + } } } } diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 440094b..582c0b2 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -342,6 +342,9 @@ pub fn spawn_named_mob( "random" => eb.with(MoveMode { mode: Movement::Random, }), + "random_waypoint" => eb.with(MoveMode { + mode: Movement::RandomWaypoint { path: None }, + }), _ => eb.with(MoveMode { mode: Movement::Static, }), diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 7ed9cea..60840c2 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -64,8 +64,7 @@ pub fn save_game(ecs: &mut World) { Attributes, BlocksTile, BlocksVisibility, - Bystander, - Carnivore, + Chasing, Confusion, Consumable, Door, @@ -75,7 +74,6 @@ pub fn save_game(ecs: &mut World) { Equippable, Equipped, Faction, - Herbivore, Hidden, HungerClock, InBackpack, @@ -86,7 +84,6 @@ pub fn save_game(ecs: &mut World) { LootTable, MagicMapper, MeleeWeapon, - Monster, MoveMode, MyTurn, Name, @@ -105,7 +102,6 @@ pub fn save_game(ecs: &mut World) { SingleActivation, Skills, SufferDamage, - Vendor, Viewshed, WantsToApproach, WantsToDropItem, @@ -175,8 +171,7 @@ pub fn load_game(ecs: &mut World) { Attributes, BlocksTile, BlocksVisibility, - Bystander, - Carnivore, + Chasing, Confusion, Consumable, Door, @@ -186,7 +181,6 @@ pub fn load_game(ecs: &mut World) { Equippable, Equipped, Faction, - Herbivore, Hidden, HungerClock, InBackpack, @@ -197,7 +191,6 @@ pub fn load_game(ecs: &mut World) { LootTable, MagicMapper, MeleeWeapon, - Monster, MoveMode, MyTurn, Name, @@ -216,7 +209,6 @@ pub fn load_game(ecs: &mut World) { SingleActivation, Skills, SufferDamage, - Vendor, Viewshed, WantsToApproach, WantsToDropItem,