2020-01-23 14:10:49 +00:00
|
|
|
use crate::terrain::TerrainGrid;
|
2020-01-24 21:24:57 +00:00
|
|
|
use crate::{
|
2020-01-25 12:27:36 +00:00
|
|
|
comp::{self, Agent, Alignment, CharacterState, Controller, MountState, Pos, Stats},
|
2020-01-25 02:15:15 +00:00
|
|
|
state::Time,
|
2020-01-25 12:27:36 +00:00
|
|
|
sync::UidAllocator,
|
2020-01-24 21:24:57 +00:00
|
|
|
};
|
|
|
|
use rand::{seq::SliceRandom, thread_rng, Rng};
|
|
|
|
use specs::{
|
|
|
|
saveload::{Marker, MarkerAllocator},
|
|
|
|
Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
|
|
|
|
};
|
2019-04-16 21:06:33 +00:00
|
|
|
use vek::*;
|
|
|
|
|
2019-06-09 19:33:20 +00:00
|
|
|
/// This system will allow NPCs to modify their controller
|
2019-04-16 21:06:33 +00:00
|
|
|
pub struct Sys;
|
|
|
|
impl<'a> System<'a> for Sys {
|
|
|
|
type SystemData = (
|
2020-01-24 21:24:57 +00:00
|
|
|
Read<'a, UidAllocator>,
|
2020-01-25 02:15:15 +00:00
|
|
|
Read<'a, Time>,
|
2019-05-25 21:13:38 +00:00
|
|
|
Entities<'a>,
|
2019-04-16 21:06:33 +00:00
|
|
|
ReadStorage<'a, Pos>,
|
2019-08-02 20:25:33 +00:00
|
|
|
ReadStorage<'a, Stats>,
|
2019-08-23 10:11:37 +00:00
|
|
|
ReadStorage<'a, CharacterState>,
|
2019-12-11 05:28:45 +00:00
|
|
|
ReadExpect<'a, TerrainGrid>,
|
2020-01-24 21:24:57 +00:00
|
|
|
ReadStorage<'a, Alignment>,
|
2019-08-02 20:25:33 +00:00
|
|
|
WriteStorage<'a, Agent>,
|
2019-06-09 14:20:20 +00:00
|
|
|
WriteStorage<'a, Controller>,
|
2019-09-09 19:11:40 +00:00
|
|
|
ReadStorage<'a, MountState>,
|
2019-04-16 21:06:33 +00:00
|
|
|
);
|
|
|
|
|
2019-08-04 08:21:29 +00:00
|
|
|
fn run(
|
|
|
|
&mut self,
|
2019-12-11 05:28:45 +00:00
|
|
|
(
|
2020-01-24 21:24:57 +00:00
|
|
|
uid_allocator,
|
2020-01-25 02:15:15 +00:00
|
|
|
time,
|
2019-12-11 05:28:45 +00:00
|
|
|
entities,
|
|
|
|
positions,
|
|
|
|
stats,
|
|
|
|
character_states,
|
|
|
|
terrain,
|
2020-01-24 21:24:57 +00:00
|
|
|
alignments,
|
2019-12-11 05:28:45 +00:00
|
|
|
mut agents,
|
|
|
|
mut controllers,
|
|
|
|
mount_states,
|
|
|
|
): Self::SystemData,
|
2019-08-04 08:21:29 +00:00
|
|
|
) {
|
2020-01-24 21:24:57 +00:00
|
|
|
for (entity, pos, alignment, agent, controller, mount_state) in (
|
2019-09-09 19:11:40 +00:00
|
|
|
&entities,
|
|
|
|
&positions,
|
2020-01-24 21:24:57 +00:00
|
|
|
alignments.maybe(),
|
2019-09-09 19:11:40 +00:00
|
|
|
&mut agents,
|
|
|
|
&mut controllers,
|
|
|
|
mount_states.maybe(),
|
|
|
|
)
|
|
|
|
.join()
|
2019-05-25 21:13:38 +00:00
|
|
|
{
|
2019-09-09 19:11:40 +00:00
|
|
|
// Skip mounted entities
|
|
|
|
if mount_state
|
|
|
|
.map(|ms| {
|
|
|
|
if let MountState::Unmounted = ms {
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
controller.reset();
|
|
|
|
|
2019-12-01 23:40:05 +00:00
|
|
|
let mut inputs = &mut controller.inputs;
|
2019-10-15 04:06:14 +00:00
|
|
|
|
2020-01-24 21:24:57 +00:00
|
|
|
const PET_DIST: f32 = 12.0;
|
2020-01-25 02:15:15 +00:00
|
|
|
const PATROL_DIST: f32 = 32.0;
|
|
|
|
const SIGHT_DIST: f32 = 24.0;
|
2020-01-24 21:24:57 +00:00
|
|
|
const MIN_ATTACK_DIST: f32 = 3.25;
|
2020-01-25 02:15:15 +00:00
|
|
|
const CHASE_TIME_MIN: f64 = 4.0;
|
2020-01-24 21:24:57 +00:00
|
|
|
|
|
|
|
let mut chase_tgt = None;
|
|
|
|
let mut choose_target = false;
|
2020-01-25 02:15:15 +00:00
|
|
|
let mut new_target = None;
|
2020-01-24 21:24:57 +00:00
|
|
|
|
2020-01-25 02:15:15 +00:00
|
|
|
if let Some((target, aggro_time)) = agent.target {
|
2020-01-24 21:24:57 +00:00
|
|
|
// Chase / attack target
|
|
|
|
if let (Some(tgt_pos), stats) = (positions.get(target), stats.get(target)) {
|
|
|
|
if stats.map(|s| s.is_dead).unwrap_or(false) {
|
|
|
|
// Don't target dead entities
|
|
|
|
choose_target = true;
|
2020-01-25 02:15:15 +00:00
|
|
|
} else if pos.0.distance(tgt_pos.0) < SIGHT_DIST
|
|
|
|
|| (time.0 - aggro_time) < CHASE_TIME_MIN
|
|
|
|
{
|
|
|
|
chase_tgt = Some((tgt_pos.0, 1.5, true))
|
2020-01-24 21:24:57 +00:00
|
|
|
} else {
|
|
|
|
// Lose sight of enemies
|
|
|
|
choose_target = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
choose_target = true;
|
|
|
|
}
|
2020-01-25 02:15:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return to owner
|
|
|
|
if let Some(owner) = agent.owner {
|
2020-01-24 21:24:57 +00:00
|
|
|
if let Some(tgt_pos) = positions.get(owner) {
|
2020-01-25 02:15:15 +00:00
|
|
|
if pos.0.distance(tgt_pos.0) > PET_DIST {
|
2020-01-24 21:24:57 +00:00
|
|
|
// Follow owner
|
|
|
|
chase_tgt = Some((tgt_pos.0, 6.0, false));
|
2020-01-25 02:15:15 +00:00
|
|
|
} else if agent.target.is_none() {
|
2020-01-24 21:24:57 +00:00
|
|
|
choose_target = thread_rng().gen::<f32>() < 0.02;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.owner = None;
|
|
|
|
}
|
|
|
|
} else if let Some(patrol_origin) = agent.patrol_origin {
|
2020-01-25 02:15:15 +00:00
|
|
|
if pos.0.distance(patrol_origin) > PATROL_DIST {
|
2020-01-24 21:24:57 +00:00
|
|
|
// Return to patrol origin
|
|
|
|
chase_tgt = Some((patrol_origin, 64.0, false));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
choose_target = thread_rng().gen::<f32>() < 0.05;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attack a target that's attacking us
|
|
|
|
if let Some(stats) = stats.get(entity) {
|
|
|
|
match stats.health.last_change.1.cause {
|
|
|
|
comp::HealthSource::Attack { by } => {
|
|
|
|
if agent.target.is_none() {
|
2020-01-25 02:15:15 +00:00
|
|
|
new_target = uid_allocator.retrieve_entity_internal(by.id());
|
2020-01-24 21:24:57 +00:00
|
|
|
} else if thread_rng().gen::<f32>() < 0.005 {
|
2020-01-25 02:15:15 +00:00
|
|
|
new_target = uid_allocator.retrieve_entity_internal(by.id());
|
2020-01-24 21:24:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-25 02:15:15 +00:00
|
|
|
// Choose a new target
|
2020-01-24 21:24:57 +00:00
|
|
|
if choose_target {
|
|
|
|
// Search for new targets
|
|
|
|
let entities = (&entities, &positions, &stats, alignments.maybe())
|
|
|
|
.join()
|
|
|
|
.filter(|(e, e_pos, e_stats, e_alignment)| {
|
|
|
|
(e_pos.0 - pos.0).magnitude() < SIGHT_DIST
|
|
|
|
&& *e != entity
|
|
|
|
&& !e_stats.is_dead
|
|
|
|
&& alignment
|
|
|
|
.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b)))
|
|
|
|
.unwrap_or(false)
|
|
|
|
})
|
|
|
|
.map(|(e, _, _, _)| e)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2020-01-25 02:15:15 +00:00
|
|
|
new_target = (&entities).choose(&mut thread_rng()).cloned();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update target when attack begins
|
|
|
|
if let Some(tgt) = new_target {
|
|
|
|
agent.target = Some((tgt, time.0));
|
2020-01-24 21:24:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Chase target
|
|
|
|
if let Some((tgt_pos, min_dist, aggressive)) = chase_tgt {
|
|
|
|
if let Some(bearing) = agent.chaser.chase(&*terrain, pos.0, tgt_pos, min_dist) {
|
|
|
|
inputs.move_dir = Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
|
|
|
|
inputs.jump.set_state(bearing.z > 1.0);
|
|
|
|
}
|
|
|
|
|
2020-01-25 02:15:15 +00:00
|
|
|
if aggressive && pos.0.distance(tgt_pos) < MIN_ATTACK_DIST {
|
2020-01-24 21:24:57 +00:00
|
|
|
inputs.look_dir = tgt_pos - pos.0;
|
|
|
|
inputs.move_dir = Vec2::from(tgt_pos - pos.0)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* 0.01;
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
}
|
2019-04-16 21:06:33 +00:00
|
|
|
|
2020-01-25 02:15:15 +00:00
|
|
|
// We're not wandering
|
|
|
|
agent.wander_pos = None;
|
|
|
|
} else {
|
|
|
|
if let Some(wander_pos) = agent.wander_pos {
|
|
|
|
if pos.0.distance(wander_pos) < 4.0 {
|
|
|
|
agent.wander_pos = None;
|
|
|
|
} else {
|
|
|
|
if let Some(bearing) = agent.chaser.chase(&*terrain, pos.0, wander_pos, 3.0)
|
|
|
|
{
|
2020-01-23 14:10:49 +00:00
|
|
|
inputs.move_dir =
|
2020-01-25 02:15:15 +00:00
|
|
|
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero()) * 0.5;
|
2020-01-22 14:24:11 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.0);
|
2020-01-22 03:12:17 +00:00
|
|
|
}
|
2019-05-11 12:43:19 +00:00
|
|
|
}
|
2019-05-28 18:59:32 +00:00
|
|
|
}
|
2019-08-24 19:12:54 +00:00
|
|
|
|
2020-01-25 02:15:15 +00:00
|
|
|
// Choose new wander position
|
|
|
|
if agent.wander_pos.is_none() || thread_rng().gen::<f32>() < 0.005 {
|
|
|
|
agent.wander_pos = if thread_rng().gen::<f32>() < 0.5 {
|
|
|
|
let max_dist = if agent.owner.is_some() {
|
|
|
|
PET_DIST
|
2019-08-02 20:25:33 +00:00
|
|
|
} else {
|
2020-01-25 02:15:15 +00:00
|
|
|
PATROL_DIST
|
2019-08-02 20:25:33 +00:00
|
|
|
};
|
2020-01-25 02:15:15 +00:00
|
|
|
Some(
|
|
|
|
agent
|
|
|
|
.patrol_origin
|
|
|
|
.unwrap_or(pos.0)
|
|
|
|
.map(|e| e + (thread_rng().gen::<f32>() - 0.5) * max_dist),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2019-05-28 18:59:32 +00:00
|
|
|
}
|
2019-04-16 21:06:33 +00:00
|
|
|
}
|
2019-12-03 06:30:08 +00:00
|
|
|
|
2019-12-01 23:40:05 +00:00
|
|
|
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
|
|
|
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
2019-04-16 21:06:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|