1
0
Fork 0

Complete section 5.14, having refactored basically every system

This commit is contained in:
Timothy Warren 2022-01-11 15:35:59 -05:00
parent 148e448d78
commit eb29e28ec6
12 changed files with 245 additions and 91 deletions

View File

@ -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;

85
src/ai/chase_ai_system.rs Normal file
View File

@ -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<Entity, (i32, i32)> = HashMap::new();
let mut end_chase: Vec<Entity> = 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<Entity> = 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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@ pub enum HungerState {
pub enum Movement {
Static,
Random,
RandomWaypoint { path: Option<Vec<usize>> },
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]

View File

@ -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 {}

View File

@ -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>() {
RunState::AwaitingInput => RunState::AwaitingInput,
RunState::MagicMapReveal { .. } => RunState::MagicMapReveal { row: 0 },
_ => RunState::Ticking,
};
newrunstate = match *self.ecs.fetch::<RunState>() {
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,

View File

@ -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::<Position>();
let players = ecs.read_storage::<Player>();
let mut viewsheds = ecs.write_storage::<Viewshed>();
let entities = ecs.entities();
let combat_stats = ecs.read_storage::<Pools>();
let combat_stats = ecs.read_storage::<Attributes>();
let map = ecs.fetch::<Map>();
let mut wants_to_melee = ecs.write_storage::<WantsToMelee>();
let mut entity_moved = ecs.write_storage::<EntityMoved>();
@ -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::<BlocksVisibility>();
let mut blocks_movement = ecs.write_storage::<BlocksTile>();
let mut renderables = ecs.write_storage::<Renderable>();
let bystanders = ecs.read_storage::<Bystander>();
let vendors = ecs.read_storage::<Vendor>();
let factions = ecs.read_storage::<Faction>();
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::<Point>();
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::<Point>();
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::<Entity>();
let viewshed_components = ecs.read_storage::<Viewshed>();
let monsters = ecs.read_storage::<Monster>();
let factions = ecs.read_storage::<Faction>();
let worldmap_resource = ecs.fetch::<Map>();
@ -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;
}
}
}
}

View File

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

View File

@ -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,