ChaseAI{}, FleeAI{}, removal of monster/bystander tags

This commit is contained in:
Llywelwyn 2023-08-15 20:04:21 +01:00
parent 91607fb9ce
commit 198486df1d
13 changed files with 234 additions and 187 deletions

View file

@ -9,7 +9,7 @@
}, },
{ {
"id": "neutral", "id": "neutral",
"responses": { "default": "ignore" } "responses": { "default": "ignore", "hostile": "flee", "mindless": "flee", "carnivore": "flee" }
}, },
{ {
"id": "hostile", "id": "hostile",
@ -21,6 +21,6 @@
}, },
{ {
"id": "carnivore", "id": "carnivore",
"responses": { "default": "ignore", "herbivores": "attack", "player": "attack" } "responses": { "default": "ignore", "herbivores": "attack", "player": "attack", "neutral": "attack" }
} }
] ]

View file

@ -1,63 +0,0 @@
use crate::{Bystander, EntityMoved, Map, Position, TakingTurn, Viewshed};
use specs::prelude::*;
pub struct BystanderAI {}
impl<'a> System<'a> for BystanderAI {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteExpect<'a, Map>,
Entities<'a>,
WriteStorage<'a, Viewshed>,
ReadStorage<'a, Bystander>,
WriteStorage<'a, Position>,
WriteStorage<'a, EntityMoved>,
WriteExpect<'a, rltk::RandomNumberGenerator>,
ReadStorage<'a, TakingTurn>,
);
fn run(&mut self, data: Self::SystemData) {
let (mut map, entities, mut viewshed, bystander, mut position, mut entity_moved, mut rng, turns) = data;
for (entity, mut viewshed, _bystander, mut pos, _turn) in
(&entities, &mut viewshed, &bystander, &mut position, &turns).join()
{
if try_move_randomly(&mut pos, &mut rng, &mut map, &mut viewshed) {
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
}
}
}
}
pub fn try_move_randomly(
pos: &mut Position,
rng: &mut rltk::RandomNumberGenerator,
map: &mut Map,
viewshed: &mut Viewshed,
) -> bool {
// Try to move randomly
let mut x = pos.x;
let mut y = pos.y;
let move_roll = rng.roll_dice(1, 8);
match move_roll {
1 => x -= 1,
2 => x += 1,
3 => y -= 1,
4 => y += 1,
_ => {}
}
if x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1 {
let dest_idx = map.xy_idx(x, y);
if !map.blocked[dest_idx] {
let idx = map.xy_idx(pos.x, pos.y);
map.blocked[idx] = false;
pos.x = x;
pos.y = y;
map.blocked[dest_idx] = true;
viewshed.dirty = true;
return true;
}
}
return false;
}

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

@ -0,0 +1,85 @@
use crate::{Chasing, EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed};
use rltk::prelude::*;
use specs::prelude::*;
use std::collections::HashMap;
// If the target is beyond this distance, they're no longer being detected,
// so stop following them. This is essentially a combined value of the sound
// the target might be making, noise, light, etc., anything they could do to
// be detected. As those constituent systems are developed, this value should
// be changed to being a result of some calculations between them.
const MAX_CHASE_DISTANCE: usize = 15;
pub struct ChaseAI {}
impl<'a> System<'a> for ChaseAI {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteStorage<'a, TakingTurn>,
WriteStorage<'a, Chasing>,
WriteStorage<'a, Position>,
WriteExpect<'a, Map>,
WriteStorage<'a, Viewshed>,
WriteStorage<'a, Telepath>,
WriteStorage<'a, EntityMoved>,
Entities<'a>,
);
fn run(&mut self, data: Self::SystemData) {
let (mut turns, mut chasing, mut positions, mut map, mut viewsheds, mut telepaths, mut entity_moved, entities) =
data;
let mut targets: HashMap<Entity, (i32, i32)> = HashMap::new();
let mut end_chase: Vec<Entity> = Vec::new();
// For every chasing entity with a turn, look for a valid target position,
// and if found, store that position in a temporary HashMap. This gets around
// needing to read Position twice - that would cause borrowchecker issues.
// If there's no valid target found, remove the chasing component.
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();
// Iterate over everyone who is *still* chasing, and path to the target
// stored in the HashMap. If successful, follow the path. If not, remove
// the chasing component.
let mut turn_done: Vec<Entity> = Vec::new();
for (entity, _turn, mut pos, _chase, mut viewshed) in
(&entities, &turns, &mut positions, &chasing, &mut viewsheds).join()
{
turn_done.push(entity);
let target_pos = targets[&entity];
let path = a_star_search(
map.xy_idx(pos.x, pos.y) as i32,
map.xy_idx(target_pos.0, target_pos.1) as i32,
&mut *map,
);
if path.success && path.steps.len() > 1 && path.steps.len() < MAX_CHASE_DISTANCE {
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("Failed to insert EntityMoved");
idx = map.xy_idx(pos.x, pos.y);
map.blocked[idx] = true;
viewshed.dirty = true;
if let Some(is_telepath) = telepaths.get_mut(entity) {
is_telepath.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

@ -20,10 +20,23 @@ impl<'a> System<'a> for EnergySystem {
WriteExpect<'a, RunState>, WriteExpect<'a, RunState>,
ReadExpect<'a, Entity>, ReadExpect<'a, Entity>,
ReadStorage<'a, Name>, ReadStorage<'a, Name>,
ReadExpect<'a, Point>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let (clock, mut energies, burdens, positions, mut turns, entities, mut rng, mut runstate, player, names) = data; let (
clock,
mut energies,
burdens,
positions,
mut turns,
entities,
mut rng,
mut runstate,
player,
names,
player_pos,
) = data;
// If not ticking, do nothing. // If not ticking, do nothing.
if *runstate != RunState::Ticking { if *runstate != RunState::Ticking {
return; return;
@ -44,7 +57,7 @@ impl<'a> System<'a> for EnergySystem {
} }
} }
// EVERYTHING ELSE // EVERYTHING ELSE
for (entity, energy, _pos) in (&entities, &mut energies, &positions).join() { for (entity, energy, pos) in (&entities, &mut energies, &positions).join() {
let burden_modifier = if let Some(burden) = burdens.get(entity) { let burden_modifier = if let Some(burden) = burdens.get(entity) {
match burden.level { match burden.level {
BurdenLevel::Burdened => 0.75, BurdenLevel::Burdened => 0.75,
@ -76,17 +89,25 @@ impl<'a> System<'a> for EnergySystem {
// has enough energy, they take a turn and decrement their energy // has enough energy, they take a turn and decrement their energy
// by TURN_COST. If the current entity is the player, await input. // by TURN_COST. If the current entity is the player, await input.
if energy.current >= TURN_COST { if energy.current >= TURN_COST {
turns.insert(entity, TakingTurn {}).expect("Unable to insert turn."); let mut my_turn = true;
energy.current -= TURN_COST;
if LOG_TICKS {
let name = if let Some(name) = names.get(entity) { &name.name } else { "Unknown entity" };
console::log(format!(
"ENERGY SYSTEM: {} granted a turn. [leftover energy: {}].",
name, energy.current
));
}
if entity == *player { if entity == *player {
*runstate = RunState::AwaitingInput; *runstate = RunState::AwaitingInput;
} else {
let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, Point::new(pos.x, pos.y));
if distance > 20.0 {
my_turn = false;
}
}
if my_turn {
turns.insert(entity, TakingTurn {}).expect("Unable to insert turn.");
energy.current -= TURN_COST;
if LOG_TICKS {
let name = if let Some(name) = names.get(entity) { &name.name } else { "Unknown entity" };
console::log(format!(
"ENERGY SYSTEM: {} granted a turn. [leftover energy: {}].",
name, energy.current
));
}
} }
} }
} }

59
src/ai/flee_ai_system.rs Normal file
View file

@ -0,0 +1,59 @@
use crate::{EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed, WantsToFlee};
use rltk::prelude::*;
use specs::prelude::*;
pub struct FleeAI {}
impl<'a> System<'a> for FleeAI {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteStorage<'a, TakingTurn>,
WriteStorage<'a, WantsToFlee>,
WriteStorage<'a, Position>,
WriteExpect<'a, Map>,
WriteStorage<'a, Viewshed>,
WriteStorage<'a, Telepath>,
WriteStorage<'a, EntityMoved>,
Entities<'a>,
);
fn run(&mut self, data: Self::SystemData) {
let (
mut turns,
mut wants_to_flee,
mut positions,
mut map,
mut viewsheds,
mut telepaths,
mut entity_moved,
entities,
) = data;
let mut turn_done: Vec<Entity> = Vec::new();
for (entity, _turn, mut pos, fleeing, mut viewshed) in
(&entities, &turns, &mut positions, &wants_to_flee, &mut viewsheds).join()
{
turn_done.push(entity);
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, &fleeing.indices, &*map, 100.0);
let flee_target = DijkstraMap::find_highest_exit(&flee_map, my_idx, &*map);
if let Some(flee_target) = flee_target {
if !map.blocked[flee_target as usize] {
map.blocked[my_idx] = false;
map.blocked[flee_target as usize] = true;
viewshed.dirty = true;
if let Some(is_telepath) = telepaths.get_mut(entity) {
is_telepath.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 EntityMoved");
}
}
}
wants_to_flee.clear();
for done in turn_done.iter() {
turns.remove(*done);
}
}
}

View file

@ -8,13 +8,13 @@ mod regen_system;
pub use regen_system::RegenSystem; pub use regen_system::RegenSystem;
mod encumbrance_system; mod encumbrance_system;
pub use encumbrance_system::{EncumbranceSystem, CARRY_CAPACITY_PER_STRENGTH}; pub use encumbrance_system::{EncumbranceSystem, CARRY_CAPACITY_PER_STRENGTH};
mod bystander_ai_system;
pub use bystander_ai_system::BystanderAI;
mod monster_ai_system;
pub use monster_ai_system::MonsterAI;
mod adjacent_ai_system; mod adjacent_ai_system;
pub use adjacent_ai_system::AdjacentAI; pub use adjacent_ai_system::AdjacentAI;
mod visible_ai_system; mod visible_ai_system;
pub use visible_ai_system::VisibleAI; pub use visible_ai_system::VisibleAI;
mod approach_ai_system; mod approach_ai_system;
pub use approach_ai_system::ApproachAI; pub use approach_ai_system::ApproachAI;
mod chase_ai_system;
pub use chase_ai_system::ChaseAI;
mod flee_ai_system;
pub use flee_ai_system::FleeAI;

View file

@ -1,70 +0,0 @@
use super::bystander_ai_system::try_move_randomly;
use crate::{EntityMoved, Map, Monster, Position, TakingTurn, Viewshed, WantsToMelee};
use rltk::Point;
use specs::prelude::*;
pub struct MonsterAI {}
impl<'a> System<'a> for MonsterAI {
#[allow(clippy::type_complexity)]
type SystemData = (
WriteExpect<'a, Map>,
WriteExpect<'a, rltk::RandomNumberGenerator>,
ReadExpect<'a, Point>,
ReadExpect<'a, Entity>,
Entities<'a>,
WriteStorage<'a, Viewshed>,
ReadStorage<'a, Monster>,
WriteStorage<'a, Position>,
WriteStorage<'a, WantsToMelee>,
WriteStorage<'a, EntityMoved>,
ReadStorage<'a, TakingTurn>,
);
fn run(&mut self, data: Self::SystemData) {
let (
mut map,
mut rng,
player_pos,
player_entity,
entities,
mut viewshed,
monster,
mut position,
mut wants_to_melee,
mut entity_moved,
turns,
) = data;
for (entity, mut viewshed, _monster, mut pos, _turn) in
(&entities, &mut viewshed, &monster, &mut position, &turns).join()
{
let distance = rltk::DistanceAlg::Pythagoras.distance2d(Point::new(pos.x, pos.y), *player_pos);
if distance < 1.5 {
wants_to_melee
.insert(entity, WantsToMelee { target: *player_entity })
.expect("Unable to insert attack.");
} else if viewshed.visible_tiles.contains(&*player_pos) {
// If the player is visible, but the path is obstructed, this will currently search
// the entire map (i.e. Will do a huge ASTAR to find an alternate route), and the
// mob will follow that path until it leaves vision, then lose sight of the player
// and stop.
let path = rltk::a_star_search(map.xy_idx(pos.x, pos.y), map.xy_idx(player_pos.x, player_pos.y), &*map);
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;
idx = map.xy_idx(pos.x, pos.y);
map.blocked[idx] = true;
viewshed.dirty = true;
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
}
} else {
if try_move_randomly(&mut pos, &mut rng, &mut map, &mut viewshed) {
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
}
}
}
}
}

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
raws::Reaction, Faction, Map, Mind, Position, TakingTurn, Telepath, Viewshed, WantsToApproach, WantsToFlee, raws::Reaction, Chasing, Faction, Map, Mind, Position, TakingTurn, Telepath, Viewshed, WantsToApproach, WantsToFlee,
}; };
use specs::prelude::*; use specs::prelude::*;
use std::collections::HashSet; use std::collections::HashSet;
@ -20,6 +20,7 @@ impl<'a> System<'a> for VisibleAI {
ReadStorage<'a, Viewshed>, ReadStorage<'a, Viewshed>,
ReadStorage<'a, Telepath>, ReadStorage<'a, Telepath>,
ReadStorage<'a, Mind>, ReadStorage<'a, Mind>,
WriteStorage<'a, Chasing>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -35,6 +36,7 @@ impl<'a> System<'a> for VisibleAI {
viewsheds, viewsheds,
telepaths, telepaths,
minds, minds,
mut chasing,
) = data; ) = data;
for (entity, _turn, faction, pos, viewshed) in (&entities, &turns, &factions, &positions, &viewsheds).join() { for (entity, _turn, faction, pos, viewshed) in (&entities, &turns, &factions, &positions, &viewsheds).join() {
@ -42,7 +44,7 @@ impl<'a> System<'a> for VisibleAI {
continue; continue;
} }
let this_idx = map.xy_idx(pos.x, pos.y); let this_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(); let mut flee: Vec<usize> = Vec::new();
let mut idxs: HashSet<usize> = HashSet::new(); let mut idxs: HashSet<usize> = HashSet::new();
for visible_tile in viewshed.visible_tiles.iter() { for visible_tile in viewshed.visible_tiles.iter() {
@ -70,6 +72,7 @@ impl<'a> System<'a> for VisibleAI {
wants_to_approach wants_to_approach
.insert(entity, WantsToApproach { idx: reaction.0 as i32 }) .insert(entity, WantsToApproach { idx: reaction.0 as i32 })
.expect("Error inserting WantsToApproach"); .expect("Error inserting WantsToApproach");
chasing.insert(entity, Chasing { target: reaction.2 }).expect("Unable to insert Chasing");
done = true; done = true;
} }
Reaction::Flee => { Reaction::Flee => {
@ -90,7 +93,7 @@ fn evaluate(
map: &Map, map: &Map,
factions: &ReadStorage<Faction>, factions: &ReadStorage<Faction>,
this_faction: &str, this_faction: &str,
reactions: &mut Vec<(usize, Reaction)>, reactions: &mut Vec<(usize, Reaction, Entity)>,
minds: Option<&ReadStorage<Mind>>, minds: Option<&ReadStorage<Mind>>,
) { ) {
for other_entity in map.tile_content[idx].iter() { for other_entity in map.tile_content[idx].iter() {
@ -105,6 +108,7 @@ fn evaluate(
reactions.push(( reactions.push((
idx, idx,
crate::raws::faction_reaction(this_faction, &faction.name, &crate::raws::RAWS.lock().unwrap()), crate::raws::faction_reaction(this_faction, &faction.name, &crate::raws::RAWS.lock().unwrap()),
*other_entity,
)); ));
} }
} }

View file

@ -54,9 +54,6 @@ pub struct Faction {
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Prop {} pub struct Prop {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Monster {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct LootTable { pub struct LootTable {
pub table: String, pub table: String,
@ -75,9 +72,6 @@ pub struct Clock {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct TakingTurn {} pub struct TakingTurn {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Bystander {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Quips { pub struct Quips {
pub available: Vec<String>, pub available: Vec<String>,
@ -383,6 +377,11 @@ pub struct WantsToFlee {
pub indices: Vec<usize>, // Dijkstra pub indices: Vec<usize>, // Dijkstra
} }
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Chasing {
pub target: Entity,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Consumable {} pub struct Consumable {}

View file

@ -96,11 +96,6 @@ impl State {
let mut encumbrance_system = ai::EncumbranceSystem {}; let mut encumbrance_system = ai::EncumbranceSystem {};
let mut turn_status_system = ai::TurnStatusSystem {}; let mut turn_status_system = ai::TurnStatusSystem {};
let mut quip_system = ai::QuipSystem {}; let mut quip_system = ai::QuipSystem {};
let mut adjacent_ai = ai::AdjacentAI {};
let mut visible_ai = ai::VisibleAI {};
let mut approach_ai = ai::ApproachAI {};
let mut mob = ai::MonsterAI {};
let mut bystanders = ai::BystanderAI {};
let mut trigger_system = trigger_system::TriggerSystem {}; let mut trigger_system = trigger_system::TriggerSystem {};
let mut melee_system = MeleeCombatSystem {}; let mut melee_system = MeleeCombatSystem {};
let mut damage_system = DamageSystem {}; let mut damage_system = DamageSystem {};
@ -119,11 +114,7 @@ impl State {
energy.run_now(&self.ecs); energy.run_now(&self.ecs);
turn_status_system.run_now(&self.ecs); turn_status_system.run_now(&self.ecs);
quip_system.run_now(&self.ecs); quip_system.run_now(&self.ecs);
adjacent_ai.run_now(&self.ecs); self.run_ai();
visible_ai.run_now(&self.ecs);
approach_ai.run_now(&self.ecs);
mob.run_now(&self.ecs);
bystanders.run_now(&self.ecs);
trigger_system.run_now(&self.ecs); trigger_system.run_now(&self.ecs);
inventory_system.run_now(&self.ecs); inventory_system.run_now(&self.ecs);
item_use_system.run_now(&self.ecs); item_use_system.run_now(&self.ecs);
@ -138,6 +129,19 @@ impl State {
self.ecs.maintain(); self.ecs.maintain();
} }
fn run_ai(&mut self) {
let mut adjacent_ai = ai::AdjacentAI {};
let mut visible_ai = ai::VisibleAI {};
let mut approach_ai = ai::ApproachAI {};
let mut flee_ai = ai::FleeAI {};
let mut chase_ai = ai::ChaseAI {};
adjacent_ai.run_now(&self.ecs);
visible_ai.run_now(&self.ecs);
approach_ai.run_now(&self.ecs);
flee_ai.run_now(&self.ecs);
chase_ai.run_now(&self.ecs);
}
fn run_map_index(&mut self) { fn run_map_index(&mut self) {
let mut mapindex = MapIndexingSystem {}; let mut mapindex = MapIndexingSystem {};
mapindex.run_now(&self.ecs); mapindex.run_now(&self.ecs);
@ -523,10 +527,9 @@ fn main() -> rltk::BError {
gs.ecs.register::<Burden>(); gs.ecs.register::<Burden>();
gs.ecs.register::<Prop>(); gs.ecs.register::<Prop>();
gs.ecs.register::<Player>(); gs.ecs.register::<Player>();
gs.ecs.register::<Chasing>();
gs.ecs.register::<Faction>(); gs.ecs.register::<Faction>();
gs.ecs.register::<Clock>(); gs.ecs.register::<Clock>();
gs.ecs.register::<Monster>();
gs.ecs.register::<Bystander>();
gs.ecs.register::<Quips>(); gs.ecs.register::<Quips>();
gs.ecs.register::<Mind>(); gs.ecs.register::<Mind>();
gs.ecs.register::<Viewshed>(); gs.ecs.register::<Viewshed>();

View file

@ -1,6 +1,6 @@
use super::{ use super::{
gamelog, gui::get_item_display_name, Attributes, BlocksTile, BlocksVisibility, Bystander, Door, EntityMoved, gamelog, gui::get_item_display_name, raws::Reaction, Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved,
Hidden, HungerClock, HungerState, Item, Map, Monster, Name, ParticleBuilder, Player, Pools, Position, Renderable, Faction, Hidden, HungerClock, HungerState, Item, Map, Name, ParticleBuilder, Player, Pools, Position, Renderable,
RunState, State, SufferDamage, Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem, RunState, State, SufferDamage, Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem,
}; };
use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode}; use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode};
@ -275,7 +275,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
let mut viewsheds = ecs.write_storage::<Viewshed>(); let mut viewsheds = ecs.write_storage::<Viewshed>();
let mut telepaths = ecs.write_storage::<Telepath>(); let mut telepaths = ecs.write_storage::<Telepath>();
let mut entity_moved = ecs.write_storage::<EntityMoved>(); let mut entity_moved = ecs.write_storage::<EntityMoved>();
let friendlies = ecs.read_storage::<Bystander>(); let factions = ecs.read_storage::<Faction>();
let pools = ecs.read_storage::<Pools>(); let pools = ecs.read_storage::<Pools>();
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
@ -296,8 +296,17 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y);
for potential_target in map.tile_content[destination_idx].iter() { for potential_target in map.tile_content[destination_idx].iter() {
let friendly = friendlies.get(*potential_target); let mut hostile = true;
if friendly.is_some() { if pools.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 {
swap_entities.push((*potential_target, pos.x, pos.y)); swap_entities.push((*potential_target, pos.x, pos.y));
pos.x = min(map.width - 1, max(0, pos.x + delta_x)); pos.x = min(map.width - 1, max(0, pos.x + delta_x));
pos.y = min(map.height - 1, max(0, pos.y + delta_y)); pos.y = min(map.height - 1, max(0, pos.y + delta_y));
@ -523,23 +532,27 @@ pub fn try_previous_level(ecs: &mut World) -> bool {
fn skip_turn(ecs: &mut World) -> bool { fn skip_turn(ecs: &mut World) -> bool {
let player_entity = ecs.fetch::<Entity>(); let player_entity = ecs.fetch::<Entity>();
let mut viewsheds = ecs.write_storage::<Viewshed>(); let mut viewsheds = ecs.write_storage::<Viewshed>();
let monsters = ecs.read_storage::<Monster>();
let worldmap_resource = ecs.fetch::<Map>(); let worldmap_resource = ecs.fetch::<Map>();
let hunger_clocks = ecs.read_storage::<HungerClock>(); let hunger_clocks = ecs.read_storage::<HungerClock>();
// Default to being able to heal by waiting. // Default to being able to heal by waiting.
let mut can_heal = true; let mut can_heal = true;
let factions = ecs.read_storage::<Faction>();
// Check viewshed for monsters nearby. If we can see a monster, we can't heal. // Check viewshed for monsters nearby. If we can see a monster, we can't heal.
let viewshed = viewsheds.get_mut(*player_entity).unwrap(); let viewshed = viewsheds.get_mut(*player_entity).unwrap();
for tile in viewshed.visible_tiles.iter() { for tile in viewshed.visible_tiles.iter() {
let idx = worldmap_resource.xy_idx(tile.x, tile.y); let idx = worldmap_resource.xy_idx(tile.x, tile.y);
for entity_id in worldmap_resource.tile_content[idx].iter() { for entity_id in worldmap_resource.tile_content[idx].iter() {
let mob = monsters.get(*entity_id); let faction = factions.get(*entity_id);
match mob { match faction {
None => {} None => {}
Some(_) => { Some(faction) => {
can_heal = false; let reaction =
crate::raws::faction_reaction(&faction.name, "player", &crate::raws::RAWS.lock().unwrap());
if reaction == Reaction::Attack {
can_heal = false;
}
} }
} }
} }

View file

@ -295,8 +295,6 @@ pub fn spawn_named_mob(
for flag in flags.iter() { for flag in flags.iter() {
match flag.as_str() { match flag.as_str() {
"BLOCKS_TILE" => eb = eb.with(BlocksTile {}), "BLOCKS_TILE" => eb = eb.with(BlocksTile {}),
"BYSTANDER" => eb = eb.with(Bystander {}),
"MONSTER" => eb = eb.with(Monster {}),
"MINDLESS" => { "MINDLESS" => {
eb = eb.with(Faction { name: "mindless".to_string() }); eb = eb.with(Faction { name: "mindless".to_string() });
has_faction = true; has_faction = true;

View file

@ -56,7 +56,7 @@ pub fn save_game(ecs: &mut World) {
BlocksTile, BlocksTile,
BlocksVisibility, BlocksVisibility,
Burden, Burden,
Bystander, Chasing,
Clock, Clock,
Confusion, Confusion,
Consumable, Consumable,
@ -83,7 +83,6 @@ pub fn save_game(ecs: &mut World) {
MagicMapper, MagicMapper,
MeleeWeapon, MeleeWeapon,
Mind, Mind,
Monster,
MultiAttack, MultiAttack,
NaturalAttacks, NaturalAttacks,
Name, Name,
@ -174,7 +173,7 @@ pub fn load_game(ecs: &mut World) {
BlocksTile, BlocksTile,
BlocksVisibility, BlocksVisibility,
Burden, Burden,
Bystander, Chasing,
Clock, Clock,
Confusion, Confusion,
Consumable, Consumable,
@ -201,7 +200,6 @@ pub fn load_game(ecs: &mut World) {
MagicMapper, MagicMapper,
MeleeWeapon, MeleeWeapon,
Mind, Mind,
Monster,
MultiAttack, MultiAttack,
NaturalAttacks, NaturalAttacks,
Name, Name,