Add carnivore/herbivore ai, completing section 5.10

This commit is contained in:
Timothy Warren 2022-01-05 11:46:39 -05:00
parent 6e29b31978
commit c787ccfd25
4 changed files with 279 additions and 120 deletions

143
src/animal_ai_system.rs Normal file
View File

@ -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<usize> = 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<usize> = 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");
}
}
}
}
}
}

View File

@ -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<SerializeMe>,
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<SerializeMe>,
SingleActivation,
Skills,
SufferDamage,
Vendor,
Viewshed,
WantsToDropItem,
WantsToMelee,
WantsToPickupItem,
WantsToRemoveItem,
WantsToUseItem,
Wearable,
);
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());

View File

@ -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 {}),
_ => {}
};

View File

@ -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,
);
}