Refactor systems to be more generic

This commit is contained in:
Timothy Warren 2022-01-11 10:30:32 -05:00
parent f0ac291e6a
commit 754118a209
6 changed files with 135 additions and 98 deletions

View File

@ -2,8 +2,12 @@ mod animal_ai_system;
mod bystander_ai_system; mod bystander_ai_system;
mod initiative_system; mod initiative_system;
mod monster_ai_system; mod monster_ai_system;
mod quipping;
mod turn_status;
pub use animal_ai_system::AnimalAI; pub use animal_ai_system::AnimalAI;
pub use bystander_ai_system::BystanderAI; pub use bystander_ai_system::BystanderAI;
pub use initiative_system::InitiativeSystem; pub use initiative_system::InitiativeSystem;
pub use monster_ai_system::MonsterAI; pub use monster_ai_system::MonsterAI;
pub use quipping::QuipSystem;
pub use turn_status::TurnStatusSystem;

View File

@ -1,9 +1,8 @@
use ::rltk::{Point, RandomNumberGenerator}; use ::rltk::RandomNumberGenerator;
use specs::prelude::*; use specs::prelude::*;
use crate::components::{Bystander, EntityMoved, MyTurn, Name, Position, Quips, Viewshed}; use crate::components::{Bystander, EntityMoved, MyTurn, Position, Viewshed};
use crate::game_log::GameLog; use crate::Map;
use crate::{Map, RunState};
pub struct BystanderAI {} pub struct BystanderAI {}
@ -11,62 +10,30 @@ impl<'a> System<'a> for BystanderAI {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
WriteExpect<'a, Map>, WriteExpect<'a, Map>,
ReadExpect<'a, RunState>,
Entities<'a>, Entities<'a>,
WriteStorage<'a, Viewshed>, WriteStorage<'a, Viewshed>,
ReadStorage<'a, Bystander>, ReadStorage<'a, Bystander>,
WriteStorage<'a, Position>, WriteStorage<'a, Position>,
WriteStorage<'a, EntityMoved>, WriteStorage<'a, EntityMoved>,
WriteExpect<'a, RandomNumberGenerator>, WriteExpect<'a, RandomNumberGenerator>,
ReadExpect<'a, Point>,
WriteExpect<'a, GameLog>,
WriteStorage<'a, Quips>,
ReadStorage<'a, Name>,
ReadStorage<'a, MyTurn>, ReadStorage<'a, MyTurn>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let ( let (
mut map, mut map,
runstate,
entities, entities,
mut viewshed, mut viewshed,
bystander, bystander,
mut position, mut position,
mut entity_moved, mut entity_moved,
mut rng, mut rng,
player_pos,
mut gamelog,
mut quips,
names,
turns, turns,
) = data; ) = data;
for (entity, mut viewshed, _bystander, mut pos, _turn) in for (entity, mut viewshed, _bystander, mut pos, _turn) in
(&entities, &mut viewshed, &bystander, &mut position, &turns).join() (&entities, &mut viewshed, &bystander, &mut position, &turns).join()
{ {
// Possibly quip
if let Some(quip) = quips.get_mut(entity) {
if !quip.available.is_empty()
&& viewshed.visible_tiles.contains(&player_pos)
&& rng.roll_dice(1, 6) == 1
{
let name = names.get(entity);
let quip_index = if quip.available.len() == 1 {
0
} else {
(rng.roll_dice(1, quip.available.len() as i32) - 1) as usize
};
gamelog.append(format!(
"{} says \"{}\"",
name.unwrap().name,
quip.available[quip_index]
));
quip.available.remove(quip_index);
}
}
// Try to move randomly // Try to move randomly
let mut x = pos.x; let mut x = pos.x;
let mut y = pos.y; let mut y = pos.y;

View File

@ -1,9 +1,8 @@
use ::rltk::{Point, RGB}; use ::rltk::Point;
use ::specs::prelude::*; use ::specs::prelude::*;
use crate::components::{Confusion, Monster, MyTurn, Position, Viewshed, WantsToMelee}; use crate::components::{EntityMoved, Monster, MyTurn, Position, Viewshed, WantsToMelee};
use crate::particle_system::ParticleBuilder; use crate::Map;
use crate::{EntityMoved, Map, RunState};
pub struct MonsterAI {} pub struct MonsterAI {}
@ -13,14 +12,11 @@ impl<'a> System<'a> for MonsterAI {
WriteExpect<'a, Map>, WriteExpect<'a, Map>,
ReadExpect<'a, Point>, ReadExpect<'a, Point>,
ReadExpect<'a, Entity>, ReadExpect<'a, Entity>,
ReadExpect<'a, RunState>,
Entities<'a>, Entities<'a>,
WriteStorage<'a, Viewshed>, WriteStorage<'a, Viewshed>,
ReadStorage<'a, Monster>, ReadStorage<'a, Monster>,
WriteStorage<'a, Position>, WriteStorage<'a, Position>,
WriteStorage<'a, WantsToMelee>, WriteStorage<'a, WantsToMelee>,
WriteStorage<'a, Confusion>,
WriteExpect<'a, ParticleBuilder>,
WriteStorage<'a, EntityMoved>, WriteStorage<'a, EntityMoved>,
ReadStorage<'a, MyTurn>, ReadStorage<'a, MyTurn>,
); );
@ -30,14 +26,11 @@ impl<'a> System<'a> for MonsterAI {
mut map, mut map,
player_pos, player_pos,
player_entity, player_entity,
runstate,
entities, entities,
mut viewshed, mut viewshed,
monster, monster,
mut position, mut position,
mut wants_to_melee, mut wants_to_melee,
mut confused,
mut particle_builder,
mut entity_moved, mut entity_moved,
turns, turns,
) = data; ) = data;
@ -45,62 +38,40 @@ impl<'a> System<'a> for MonsterAI {
for (entity, mut viewshed, _monster, mut pos, _turn) in for (entity, mut viewshed, _monster, mut pos, _turn) in
(&entities, &mut viewshed, &monster, &mut position, &turns).join() (&entities, &mut viewshed, &monster, &mut position, &turns).join()
{ {
let mut can_act = true; let distance =
rltk::DistanceAlg::Pythagoras.distance2d(Point::new(pos.x, pos.y), *player_pos);
if let Some(i_am_confused) = confused.get_mut(entity) { if distance < 1.5 {
i_am_confused.turns -= 1; // Attack goes here
if i_am_confused.turns < 1 { wants_to_melee
confused.remove(entity); .insert(
} entity,
WantsToMelee {
can_act = false; target: *player_entity,
},
particle_builder.request( )
pos.x, .expect("Unable to insert attack");
pos.y, } else if viewshed.visible_tiles.contains(&*player_pos) {
RGB::named(rltk::MAGENTA), // The path to the player
RGB::named(rltk::BLACK), let path = rltk::a_star_search(
rltk::to_cp437('?'), map.xy_idx(pos.x, pos.y) as i32,
200.0, map.xy_idx(player_pos.x, player_pos.y) as i32,
&*map,
); );
}
if can_act { if path.success && path.steps.len() > 1 {
let distance = let mut idx = map.xy_idx(pos.x, pos.y);
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 { map.blocked[idx] = false;
let mut idx = map.xy_idx(pos.x, pos.y); pos.x = path.steps[1] as i32 % map.width;
pos.y = path.steps[1] as i32 / map.width;
map.blocked[idx] = false; entity_moved
pos.x = path.steps[1] as i32 % map.width; .insert(entity, EntityMoved {})
pos.y = path.steps[1] as i32 / map.width; .expect("Unable to add EntityMoved flag to monster");
entity_moved idx = map.xy_idx(pos.x, pos.y);
.insert(entity, EntityMoved {}) map.blocked[idx] = true;
.expect("Unable to add EntityMoved flag to monster"); viewshed.dirty = true;
idx = map.xy_idx(pos.x, pos.y);
map.blocked[idx] = true;
viewshed.dirty = true;
}
} }
} }
} }

44
src/ai/quipping.rs Normal file
View File

@ -0,0 +1,44 @@
use ::rltk::{Point, RandomNumberGenerator};
use ::specs::prelude::*;
use crate::components::{MyTurn, Name, Quips, Viewshed};
use crate::game_log::GameLog;
pub struct QuipSystem {}
impl<'a> System<'a> for QuipSystem {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteExpect<'a, GameLog>,
WriteStorage<'a, Quips>,
ReadStorage<'a, Name>,
ReadStorage<'a, MyTurn>,
ReadExpect<'a, Point>,
ReadStorage<'a, Viewshed>,
WriteExpect<'a, RandomNumberGenerator>,
);
fn run(&mut self, data: Self::SystemData) {
let (mut gamelog, mut quips, names, turns, player_pos, viewsheds, mut rng) = data;
for (quip, name, viewshed, _turn) in (&mut quips, &names, &viewsheds, &turns).join() {
if !quip.available.is_empty()
&& viewshed.visible_tiles.contains(&player_pos)
&& rng.roll_dice(1, 6) == 1
{
let quip_index = if quip.available.len() == 1 {
0
} else {
(rng.roll_dice(1, quip.available.len() as i32) - 1) as usize
};
gamelog.append(format!(
"{} says \"{}\"",
name.name, quip.available[quip_index]
));
quip.available.remove(quip_index);
}
}
}
}

45
src/ai/turn_status.rs Normal file
View File

@ -0,0 +1,45 @@
use ::specs::prelude::*;
use crate::components::{Confusion, MyTurn};
use crate::RunState;
pub struct TurnStatusSystem {}
impl<'a> System<'a> for TurnStatusSystem {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteStorage<'a, MyTurn>,
WriteStorage<'a, Confusion>,
Entities<'a>,
ReadExpect<'a, RunState>,
);
fn run(&mut self, data: Self::SystemData) {
let (mut turns, mut confusion, entities, runstate) = data;
if *runstate != RunState::Ticking {
return;
}
let mut not_my_turn: Vec<Entity> = Vec::new();
let mut not_confused: Vec<Entity> = Vec::new();
for (entity, _turn, confused) in (&entities, &mut turns, &mut confusion).join() {
confused.turns -= 1;
if confused.turns < 1 {
not_confused.push(entity)
} else {
not_my_turn.push(entity);
}
}
for e in not_my_turn {
turns.remove(e);
}
for e in not_confused {
confusion.remove(e);
}
}
}

View File

@ -115,6 +115,12 @@ impl State {
let mut initiative = ai::InitiativeSystem {}; let mut initiative = ai::InitiativeSystem {};
initiative.run_now(&self.ecs); initiative.run_now(&self.ecs);
let mut turnstatus = ai::TurnStatusSystem {};
turnstatus.run_now(&self.ecs);
let mut quipper = ai::QuipSystem {};
quipper.run_now(&self.ecs);
let mut mob = ai::MonsterAI {}; let mut mob = ai::MonsterAI {};
mob.run_now(&self.ecs); mob.run_now(&self.ecs);
@ -517,5 +523,5 @@ fn main() -> ::rltk::BError {
gs.generate_world_map(1, 0); gs.generate_world_map(1, 0);
rltk::main_loop(context, gs) ::rltk::main_loop(context, gs)
} }