From c787ccfd25ad24944b8281d55c784d18998881f7 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Wed, 5 Jan 2022 11:46:39 -0500 Subject: [PATCH] Add carnivore/herbivore ai, completing section 5.10 --- src/animal_ai_system.rs | 143 ++++++++++++++++++++++++++++++++++++ src/main.rs | 98 +++++++++++++------------ src/raws/rawmaster.rs | 2 + src/saveload_system.rs | 156 ++++++++++++++++++++-------------------- 4 files changed, 279 insertions(+), 120 deletions(-) create mode 100644 src/animal_ai_system.rs diff --git a/src/animal_ai_system.rs b/src/animal_ai_system.rs new file mode 100644 index 0000000..40cf4d7 --- /dev/null +++ b/src/animal_ai_system.rs @@ -0,0 +1,143 @@ +use ::rltk::{DijkstraMap, DistanceAlg, Point}; +use ::specs::prelude::*; + +use super::{Map, RunState}; +use crate::components::{ + Carnivore, EntityMoved, Herbivore, Item, Position, Viewshed, WantsToMelee, +}; + +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>, + ); + + 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, + ) = data; + + if *runstate != RunState::MonsterTurn { + return; + } + + // Herbivores run away a lot + for (entity, mut viewshed, _herbivore, mut pos) in + (&entities, &mut viewshed, &herbivore, &mut position).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) in + (&entities, &mut viewshed, &carnivore, &mut position).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/main.rs b/src/main.rs index d9764d7..f534f13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod animal_ai_system; mod bystander_ai_system; pub mod camera; mod components; @@ -29,17 +30,21 @@ extern crate lazy_static; use ::rltk::{GameState, Point, RandomNumberGenerator, Rltk}; use ::specs::prelude::*; use ::specs::saveload::{SimpleMarker, SimpleMarkerAllocator}; +use animal_ai_system::AnimalAI; use bystander_ai_system::BystanderAI; use components::*; use damage_system::DamageSystem; pub use game_log::GameLog; +use hunger_system::HungerSystem; use inventory_system::{ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem}; pub use map::*; use map_indexing_system::MapIndexingSystem; use melee_combat_system::MeleeCombatSystem; use monster_ai_system::MonsterAI; +use particle_system::ParticleSpawnSystem; use player::*; pub use rect::Rect; +use trigger_system::TriggerSystem; use visibility_system::VisibilitySystem; /// Cut down on the amount of syntax to register components @@ -108,15 +113,18 @@ impl State { let mut mob = MonsterAI {}; mob.run_now(&self.ecs); - let mut triggers = trigger_system::TriggerSystem {}; - triggers.run_now(&self.ecs); - let mut mapindex = MapIndexingSystem {}; mapindex.run_now(&self.ecs); + let mut animal = AnimalAI {}; + animal.run_now(&self.ecs); + let mut bystander = BystanderAI {}; bystander.run_now(&self.ecs); + let mut triggers = TriggerSystem {}; + triggers.run_now(&self.ecs); + let mut melee = MeleeCombatSystem {}; melee.run_now(&self.ecs); @@ -135,10 +143,10 @@ impl State { let mut item_remove = ItemRemoveSystem {}; item_remove.run_now(&self.ecs); - let mut hunger = hunger_system::HungerSystem {}; + let mut hunger = HungerSystem {}; hunger.run_now(&self.ecs); - let mut particles = particle_system::ParticleSpawnSystem {}; + let mut particles = ParticleSpawnSystem {}; particles.run_now(&self.ecs); self.ecs.maintain(); @@ -495,51 +503,53 @@ fn main() -> ::rltk::BError { register!( gs <- - Position, - Renderable, - Player, - Viewshed, - Monster, - Name, - BlocksTile, - WantsToMelee, - SufferDamage, - Item, - ProvidesHealing, - InBackpack, - WantsToPickupItem, - WantsToUseItem, - WantsToDropItem, - Consumable, - Ranged, - InflictsDamage, AreaOfEffect, + Attributes, + BlocksTile, + BlocksVisibility, + Bystander, + Carnivore, Confusion, - SimpleMarker, - SerializationHelper, + Consumable, + Door, + EntityMoved, + EntryTrigger, Equippable, Equipped, - MeleeWeapon, - Wearable, - WantsToRemoveItem, - ParticleLifetime, - HungerClock, - ProvidesFood, - MagicMapper, + Herbivore, Hidden, - EntryTrigger, - EntityMoved, - SingleActivation, - BlocksVisibility, - Door, - Bystander, - Vendor, - Quips, - Attributes, - Skills, - Pools, - NaturalAttackDefense, + HungerClock, + InBackpack, + InflictsDamage, + Item, LootTable, + MagicMapper, + MeleeWeapon, + Monster, + Name, + NaturalAttackDefense, + ParticleLifetime, + Player, + Pools, + Position, + ProvidesFood, + ProvidesHealing, + Quips, + Ranged, + Renderable, + SerializationHelper, + SimpleMarker, + SingleActivation, + Skills, + SufferDamage, + Vendor, + Viewshed, + WantsToDropItem, + WantsToMelee, + WantsToPickupItem, + WantsToRemoveItem, + WantsToUseItem, + Wearable, ); gs.ecs.insert(SimpleMarkerAllocator::::new()); diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index a313e50..840b90f 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -304,6 +304,8 @@ pub fn spawn_named_mob( "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 {}), _ => {} }; diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 2b1bcd6..4d50705 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -52,50 +52,52 @@ pub fn save_game(ecs: &mut World) { ecs, serializer, data, - Position, - Renderable, - Player, - Viewshed, - Monster, - Name, - BlocksTile, - SufferDamage, - WantsToMelee, - Item, - Consumable, - Ranged, - InflictsDamage, AreaOfEffect, + Attributes, + BlocksTile, + BlocksVisibility, + Bystander, + Carnivore, Confusion, - ProvidesHealing, - InBackpack, - WantsToPickupItem, - WantsToUseItem, - WantsToDropItem, - SerializationHelper, + Consumable, + Door, + EntityMoved, + EntryTrigger, Equippable, Equipped, - MeleeWeapon, - Wearable, - WantsToRemoveItem, - ParticleLifetime, - HungerClock, - ProvidesFood, - MagicMapper, + Herbivore, Hidden, - EntryTrigger, - EntityMoved, - SingleActivation, - BlocksVisibility, - Door, - Bystander, - Vendor, - Quips, - Attributes, - Skills, - Pools, - NaturalAttackDefense, + HungerClock, + InBackpack, + InflictsDamage, + Item, LootTable, + MagicMapper, + MeleeWeapon, + Monster, + Name, + NaturalAttackDefense, + ParticleLifetime, + Player, + Pools, + Position, + ProvidesFood, + ProvidesHealing, + Quips, + Ranged, + Renderable, + SerializationHelper, + SingleActivation, + Skills, + SufferDamage, + Vendor, + Viewshed, + WantsToDropItem, + WantsToMelee, + WantsToPickupItem, + WantsToRemoveItem, + WantsToUseItem, + Wearable, ); } @@ -150,50 +152,52 @@ pub fn load_game(ecs: &mut World) { ecs, de, d, - Position, - Renderable, - Player, - Viewshed, - Monster, - Name, - BlocksTile, - SufferDamage, - WantsToMelee, - Item, - Consumable, - Ranged, - InflictsDamage, AreaOfEffect, + Attributes, + BlocksTile, + BlocksVisibility, + Bystander, + Carnivore, Confusion, - ProvidesHealing, - InBackpack, - WantsToPickupItem, - WantsToUseItem, - WantsToDropItem, - SerializationHelper, + Consumable, + Door, + EntityMoved, + EntryTrigger, Equippable, Equipped, - MeleeWeapon, - Wearable, - WantsToRemoveItem, - ParticleLifetime, - HungerClock, - ProvidesFood, - MagicMapper, + Herbivore, Hidden, - EntryTrigger, - EntityMoved, - SingleActivation, - BlocksVisibility, - Door, - Bystander, - Vendor, - Quips, - Attributes, - Skills, - Pools, - NaturalAttackDefense, + HungerClock, + InBackpack, + InflictsDamage, + Item, LootTable, + MagicMapper, + MeleeWeapon, + Monster, + Name, + NaturalAttackDefense, + ParticleLifetime, + Player, + Pools, + Position, + ProvidesFood, + ProvidesHealing, + Quips, + Ranged, + Renderable, + SerializationHelper, + SingleActivation, + Skills, + SufferDamage, + Vendor, + Viewshed, + WantsToDropItem, + WantsToMelee, + WantsToPickupItem, + WantsToRemoveItem, + WantsToUseItem, + Wearable, ); }