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 initiative_system;
mod monster_ai_system;
mod quipping;
mod turn_status;
pub use animal_ai_system::AnimalAI;
pub use bystander_ai_system::BystanderAI;
pub use initiative_system::InitiativeSystem;
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 crate::components::{Bystander, EntityMoved, MyTurn, Name, Position, Quips, Viewshed};
use crate::game_log::GameLog;
use crate::{Map, RunState};
use crate::components::{Bystander, EntityMoved, MyTurn, Position, Viewshed};
use crate::Map;
pub struct BystanderAI {}
@ -11,62 +10,30 @@ impl<'a> System<'a> for BystanderAI {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteExpect<'a, Map>,
ReadExpect<'a, RunState>,
Entities<'a>,
WriteStorage<'a, Viewshed>,
ReadStorage<'a, Bystander>,
WriteStorage<'a, Position>,
WriteStorage<'a, EntityMoved>,
WriteExpect<'a, RandomNumberGenerator>,
ReadExpect<'a, Point>,
WriteExpect<'a, GameLog>,
WriteStorage<'a, Quips>,
ReadStorage<'a, Name>,
ReadStorage<'a, MyTurn>,
);
fn run(&mut self, data: Self::SystemData) {
let (
mut map,
runstate,
entities,
mut viewshed,
bystander,
mut position,
mut entity_moved,
mut rng,
player_pos,
mut gamelog,
mut quips,
names,
turns,
) = data;
for (entity, mut viewshed, _bystander, mut pos, _turn) in
(&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
let mut x = pos.x;
let mut y = pos.y;

View File

@ -1,9 +1,8 @@
use ::rltk::{Point, RGB};
use ::rltk::Point;
use ::specs::prelude::*;
use crate::components::{Confusion, Monster, MyTurn, Position, Viewshed, WantsToMelee};
use crate::particle_system::ParticleBuilder;
use crate::{EntityMoved, Map, RunState};
use crate::components::{EntityMoved, Monster, MyTurn, Position, Viewshed, WantsToMelee};
use crate::Map;
pub struct MonsterAI {}
@ -13,14 +12,11 @@ impl<'a> System<'a> for MonsterAI {
WriteExpect<'a, Map>,
ReadExpect<'a, Point>,
ReadExpect<'a, Entity>,
ReadExpect<'a, RunState>,
Entities<'a>,
WriteStorage<'a, Viewshed>,
ReadStorage<'a, Monster>,
WriteStorage<'a, Position>,
WriteStorage<'a, WantsToMelee>,
WriteStorage<'a, Confusion>,
WriteExpect<'a, ParticleBuilder>,
WriteStorage<'a, EntityMoved>,
ReadStorage<'a, MyTurn>,
);
@ -30,14 +26,11 @@ impl<'a> System<'a> for MonsterAI {
mut map,
player_pos,
player_entity,
runstate,
entities,
mut viewshed,
monster,
mut position,
mut wants_to_melee,
mut confused,
mut particle_builder,
mut entity_moved,
turns,
) = data;
@ -45,62 +38,40 @@ impl<'a> System<'a> for MonsterAI {
for (entity, mut viewshed, _monster, mut pos, _turn) in
(&entities, &mut viewshed, &monster, &mut position, &turns).join()
{
let mut can_act = true;
if let Some(i_am_confused) = confused.get_mut(entity) {
i_am_confused.turns -= 1;
if i_am_confused.turns < 1 {
confused.remove(entity);
}
can_act = false;
particle_builder.request(
pos.x,
pos.y,
RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK),
rltk::to_cp437('?'),
200.0,
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 can_act {
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);
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;
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");
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;
}
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 {};
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 {};
mob.run_now(&self.ecs);
@ -517,5 +523,5 @@ fn main() -> ::rltk::BError {
gs.generate_world_map(1, 0);
rltk::main_loop(context, gs)
::rltk::main_loop(context, gs)
}