Improved patrol idling

This commit is contained in:
Joshua Barretto 2020-01-25 18:49:47 +00:00
parent d04a595b3f
commit feeccc2ff3
6 changed files with 234 additions and 23 deletions

View File

@ -25,11 +25,9 @@ impl Component for Alignment {
#[derive(Clone, Debug, Default)]
pub struct Agent {
pub chaser: Chaser,
pub target: Option<(EcsEntity, f64)>,
pub owner: Option<EcsEntity>,
pub patrol_origin: Option<Vec3<f32>>,
pub wander_pos: Option<Vec3<f32>>,
pub activity: Activity,
}
impl Agent {
@ -47,3 +45,32 @@ impl Agent {
impl Component for Agent {
type Storage = IDVStorage<Self>;
}
#[derive(Clone, Debug)]
pub enum Activity {
Idle(Option<Vec3<f32>>, Chaser),
Follow(EcsEntity, Chaser),
Attack(EcsEntity, Chaser, f64),
}
impl Activity {
pub fn is_follow(&self) -> bool {
match self {
Activity::Follow(_, _) => true,
_ => false,
}
}
pub fn is_attack(&self) -> bool {
match self {
Activity::Attack(_, _, _) => true,
_ => false,
}
}
}
impl Default for Activity {
fn default() -> Self {
Activity::Idle(None, Chaser::default())
}
}

View File

@ -1,5 +1,5 @@
mod admin;
mod agent;
pub mod agent;
mod body;
mod character_state;
mod controller;

View File

@ -171,7 +171,7 @@ where
};
let get_walkable_z = |pos| {
let mut z_incr = 0;
for i in 0..32 {
for _ in 0..32 {
let test_pos = pos + Vec3::unit_z() * z_incr;
if is_walkable(&test_pos) {
return Some(test_pos);
@ -192,7 +192,7 @@ where
let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
let neighbors = |pos: &Vec3<i32>| {
let pos = *pos;
const dirs: [Vec3<i32>; 17] = [
const DIRS: [Vec3<i32>; 17] = [
Vec3::new(0, 1, 0), // Forward
Vec3::new(0, 1, 1), // Forward upward
Vec3::new(0, 1, 2), // Forward Upwardx2
@ -212,9 +212,31 @@ where
Vec3::new(0, 0, -1), // Downwards
];
dirs.iter()
let walkable = [
is_walkable(&(pos + Vec3::new(1, 0, 0))),
is_walkable(&(pos + Vec3::new(-1, 0, 0))),
is_walkable(&(pos + Vec3::new(0, 1, 0))),
is_walkable(&(pos + Vec3::new(0, -1, 0))),
];
const DIAGONALS: [(Vec3<i32>, [usize; 2]); 4] = [
(Vec3::new(1, 1, 0), [0, 2]),
(Vec3::new(-1, 1, 0), [1, 2]),
(Vec3::new(1, -1, 0), [0, 3]),
(Vec3::new(-1, -1, 0), [1, 3]),
];
DIRS.iter()
.map(move |dir| pos + dir)
.filter(move |pos| is_walkable(pos))
.chain(
DIAGONALS
.iter()
.filter(move |(dir, [a, b])| {
is_walkable(&(pos + *dir)) && walkable[*a] && walkable[*b]
})
.map(move |(dir, _)| pos + *dir),
)
};
let transition = |_: &Vec3<i32>, _: &Vec3<i32>| 1.0;
let satisfied = |pos: &Vec3<i32>| pos == &end;

View File

@ -1,6 +1,7 @@
use crate::terrain::TerrainGrid;
use crate::{
comp::{self, Agent, Alignment, CharacterState, Controller, MountState, Pos, Stats},
comp::{self, agent::Activity, Agent, Alignment, Controller, MountState, Pos, Stats},
path::Chaser,
state::Time,
sync::UidAllocator,
};
@ -20,7 +21,6 @@ impl<'a> System<'a> for Sys {
Entities<'a>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Stats>,
ReadStorage<'a, CharacterState>,
ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, Alignment>,
WriteStorage<'a, Agent>,
@ -36,7 +36,6 @@ impl<'a> System<'a> for Sys {
entities,
positions,
stats,
character_states,
terrain,
alignments,
mut agents,
@ -72,7 +71,165 @@ impl<'a> System<'a> for Sys {
let mut inputs = &mut controller.inputs;
const PET_DIST: f32 = 12.0;
const AVG_FOLLOW_DIST: f32 = 6.0;
const MAX_FOLLOW_DIST: f32 = 12.0;
const MAX_CHASE_DIST: f32 = 24.0;
const SIGHT_DIST: f32 = 20.0;
const MIN_ATTACK_DIST: f32 = 3.25;
const PATROL_DIST: f32 = 32.0;
let mut do_idle = false;
match &mut agent.activity {
Activity::Idle(wander_pos, chaser) => {
if let Some(patrol_origin) = agent.patrol_origin {
if thread_rng().gen::<f32>() < 0.002 {
*wander_pos =
if thread_rng().gen::<f32>() < 0.5 {
Some(patrol_origin.map(|e| {
e + thread_rng().gen_range(-1.0, 1.0) * PATROL_DIST
}))
} else {
None
};
}
if let Some(wander_pos) = wander_pos {
if let Some(bearing) = chaser.chase(&*terrain, pos.0, *wander_pos, 2.0)
{
inputs.move_dir =
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
} else {
*wander_pos = None;
}
}
}
// Sometimes try searching for new targets
if thread_rng().gen::<f32>() < 0.025 {
// 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<_>>();
if let Some(target) = (&entities).choose(&mut thread_rng()).cloned() {
agent.activity = Activity::Attack(target, Chaser::default(), time.0);
}
}
}
Activity::Follow(target, chaser) => {
if let (Some(tgt_pos), _tgt_stats) =
(positions.get(*target), stats.get(*target))
{
let dist = pos.0.distance(tgt_pos.0);
// Follow, or return to idle
if dist > AVG_FOLLOW_DIST {
if let Some(bearing) =
chaser.chase(&*terrain, pos.0, tgt_pos.0, AVG_FOLLOW_DIST)
{
inputs.move_dir =
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
}
} else {
do_idle = true;
}
} else {
do_idle = true;
}
}
Activity::Attack(target, chaser, _) => {
if let (Some(tgt_pos), _tgt_stats) =
(positions.get(*target), stats.get(*target))
{
let dist = pos.0.distance(tgt_pos.0);
if dist < MIN_ATTACK_DIST {
// Close-range attack
inputs.look_dir = tgt_pos.0 - pos.0;
inputs.move_dir = Vec2::from(tgt_pos.0 - pos.0)
.try_normalized()
.unwrap_or(Vec2::zero())
* 0.01;
inputs.primary.set_state(true);
} else if dist < MAX_CHASE_DIST {
// Long-range chase
if let Some(bearing) = chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25) {
inputs.move_dir =
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
}
} else {
do_idle = true;
}
} else {
do_idle = true;
}
}
}
if do_idle {
agent.activity = Activity::Idle(None, Chaser::default());
}
// --- Activity overrides (in reverse order of priority: most important goes last!) ---
// Attack a target that's attacking us
if let Some(stats) = stats.get(entity) {
// Only if the attack was recent
if stats.health.last_change.0 < 5.0 {
if let comp::HealthSource::Attack { by } = stats.health.last_change.1.cause {
if !agent.activity.is_attack() {
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id())
{
agent.activity =
Activity::Attack(attacker, Chaser::default(), time.0);
}
}
}
}
}
// Follow owner if we're too far, or if they're under attack
if let Some(owner) = agent.owner {
if let Some(owner_pos) = positions.get(owner) {
let dist = pos.0.distance(owner_pos.0);
if dist > MAX_FOLLOW_DIST && !agent.activity.is_follow() {
agent.activity = Activity::Follow(owner, Chaser::default());
}
// Attack owner's attacker
if let Some(owner_stats) = stats.get(owner) {
if owner_stats.health.last_change.0 < 5.0 {
if let comp::HealthSource::Attack { by } =
owner_stats.health.last_change.1.cause
{
if !agent.activity.is_attack() {
if let Some(attacker) =
uid_allocator.retrieve_entity_internal(by.id())
{
agent.activity =
Activity::Attack(attacker, Chaser::default(), time.0);
}
}
}
}
}
}
}
/*
const PET_DIST: f32 = 6.0;
const MAX_PET_DIST: f32 = 16.0;
const PATROL_DIST: f32 = 32.0;
const SIGHT_DIST: f32 = 24.0;
const MIN_ATTACK_DIST: f32 = 3.25;
@ -99,12 +256,15 @@ impl<'a> System<'a> for Sys {
} else {
choose_target = true;
}
} else {
choose_target = thread_rng().gen::<f32>() < 0.05;
}
// Return to owner
if let Some(owner) = agent.owner {
if let Some(tgt_pos) = positions.get(owner) {
if pos.0.distance(tgt_pos.0) > PET_DIST {
let dist = pos.0.distance(tgt_pos.0);
if dist > MAX_PET_DIST || (dist > PET_DIST && agent.target.is_none()) {
// Follow owner
chase_tgt = Some((tgt_pos.0, 6.0, false));
} else if agent.target.is_none() {
@ -118,8 +278,6 @@ impl<'a> System<'a> for Sys {
// 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
@ -156,8 +314,9 @@ impl<'a> System<'a> for Sys {
}
// Update target when attack begins
if let Some(tgt) = new_target {
agent.target = Some((tgt, time.0));
match agent.target {
Some((tgt, time)) if Some(tgt) == new_target => {},
_ => agent.target = new_target.map(|tgt| (tgt, time.0))
}
// Chase target
@ -193,6 +352,7 @@ impl<'a> System<'a> for Sys {
}
// 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() {
@ -210,7 +370,9 @@ impl<'a> System<'a> for Sys {
None
};
}
*/
}
*/
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());

View File

@ -465,14 +465,14 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
.unwrap_or(1)
.min(10);
let agent = if let comp::Alignment::Npc = alignment {
comp::Agent::default().with_pet(entity)
} else {
comp::Agent::default()
};
match server.state.read_component_cloned::<comp::Pos>(entity) {
Some(pos) => {
let agent = if let comp::Alignment::Npc = alignment {
comp::Agent::default().with_pet(entity)
} else {
comp::Agent::default().with_patrol_origin(pos.0)
};
for _ in 0..amount {
let vel = Vec3::new(
rand::thread_rng().gen_range(-2.0, 3.0),

View File

@ -32,7 +32,7 @@ impl World {
let mut supplement = ChunkSupplement::default();
if chunk_pos.map(|e| e % 3 == 0).reduce_and() {
if chunk_pos.map(|e| e % 8 == 0).reduce_and() {
supplement = supplement.with_entity(EntityInfo {
pos: Vec3::<f32>::from(chunk_pos.map(|e| e as f32 * 32.0)) + Vec3::unit_z() * 256.0,
kind: EntityKind::Waypoint,