mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Improved patrol idling
This commit is contained in:
parent
d04a595b3f
commit
feeccc2ff3
@ -25,11 +25,9 @@ impl Component for Alignment {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Agent {
|
pub struct Agent {
|
||||||
pub chaser: Chaser,
|
|
||||||
pub target: Option<(EcsEntity, f64)>,
|
|
||||||
pub owner: Option<EcsEntity>,
|
pub owner: Option<EcsEntity>,
|
||||||
pub patrol_origin: Option<Vec3<f32>>,
|
pub patrol_origin: Option<Vec3<f32>>,
|
||||||
pub wander_pos: Option<Vec3<f32>>,
|
pub activity: Activity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Agent {
|
impl Agent {
|
||||||
@ -47,3 +45,32 @@ impl Agent {
|
|||||||
impl Component for Agent {
|
impl Component for Agent {
|
||||||
type Storage = IDVStorage<Self>;
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
mod admin;
|
mod admin;
|
||||||
mod agent;
|
pub mod agent;
|
||||||
mod body;
|
mod body;
|
||||||
mod character_state;
|
mod character_state;
|
||||||
mod controller;
|
mod controller;
|
||||||
|
@ -171,7 +171,7 @@ where
|
|||||||
};
|
};
|
||||||
let get_walkable_z = |pos| {
|
let get_walkable_z = |pos| {
|
||||||
let mut z_incr = 0;
|
let mut z_incr = 0;
|
||||||
for i in 0..32 {
|
for _ in 0..32 {
|
||||||
let test_pos = pos + Vec3::unit_z() * z_incr;
|
let test_pos = pos + Vec3::unit_z() * z_incr;
|
||||||
if is_walkable(&test_pos) {
|
if is_walkable(&test_pos) {
|
||||||
return Some(test_pos);
|
return Some(test_pos);
|
||||||
@ -192,7 +192,7 @@ where
|
|||||||
let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
|
let heuristic = |pos: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
|
||||||
let neighbors = |pos: &Vec3<i32>| {
|
let neighbors = |pos: &Vec3<i32>| {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
const dirs: [Vec3<i32>; 17] = [
|
const DIRS: [Vec3<i32>; 17] = [
|
||||||
Vec3::new(0, 1, 0), // Forward
|
Vec3::new(0, 1, 0), // Forward
|
||||||
Vec3::new(0, 1, 1), // Forward upward
|
Vec3::new(0, 1, 1), // Forward upward
|
||||||
Vec3::new(0, 1, 2), // Forward Upwardx2
|
Vec3::new(0, 1, 2), // Forward Upwardx2
|
||||||
@ -212,9 +212,31 @@ where
|
|||||||
Vec3::new(0, 0, -1), // Downwards
|
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)
|
.map(move |dir| pos + dir)
|
||||||
.filter(move |pos| is_walkable(pos))
|
.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 transition = |_: &Vec3<i32>, _: &Vec3<i32>| 1.0;
|
||||||
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::terrain::TerrainGrid;
|
use crate::terrain::TerrainGrid;
|
||||||
use crate::{
|
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,
|
state::Time,
|
||||||
sync::UidAllocator,
|
sync::UidAllocator,
|
||||||
};
|
};
|
||||||
@ -20,7 +21,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
Entities<'a>,
|
Entities<'a>,
|
||||||
ReadStorage<'a, Pos>,
|
ReadStorage<'a, Pos>,
|
||||||
ReadStorage<'a, Stats>,
|
ReadStorage<'a, Stats>,
|
||||||
ReadStorage<'a, CharacterState>,
|
|
||||||
ReadExpect<'a, TerrainGrid>,
|
ReadExpect<'a, TerrainGrid>,
|
||||||
ReadStorage<'a, Alignment>,
|
ReadStorage<'a, Alignment>,
|
||||||
WriteStorage<'a, Agent>,
|
WriteStorage<'a, Agent>,
|
||||||
@ -36,7 +36,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
entities,
|
entities,
|
||||||
positions,
|
positions,
|
||||||
stats,
|
stats,
|
||||||
character_states,
|
|
||||||
terrain,
|
terrain,
|
||||||
alignments,
|
alignments,
|
||||||
mut agents,
|
mut agents,
|
||||||
@ -72,7 +71,165 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
let mut inputs = &mut controller.inputs;
|
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 PATROL_DIST: f32 = 32.0;
|
||||||
const SIGHT_DIST: f32 = 24.0;
|
const SIGHT_DIST: f32 = 24.0;
|
||||||
const MIN_ATTACK_DIST: f32 = 3.25;
|
const MIN_ATTACK_DIST: f32 = 3.25;
|
||||||
@ -99,12 +256,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
} else {
|
} else {
|
||||||
choose_target = true;
|
choose_target = true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
choose_target = thread_rng().gen::<f32>() < 0.05;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return to owner
|
// Return to owner
|
||||||
if let Some(owner) = agent.owner {
|
if let Some(owner) = agent.owner {
|
||||||
if let Some(tgt_pos) = positions.get(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
|
// Follow owner
|
||||||
chase_tgt = Some((tgt_pos.0, 6.0, false));
|
chase_tgt = Some((tgt_pos.0, 6.0, false));
|
||||||
} else if agent.target.is_none() {
|
} else if agent.target.is_none() {
|
||||||
@ -118,8 +278,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Return to patrol origin
|
// Return to patrol origin
|
||||||
chase_tgt = Some((patrol_origin, 64.0, false));
|
chase_tgt = Some((patrol_origin, 64.0, false));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
choose_target = thread_rng().gen::<f32>() < 0.05;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attack a target that's attacking us
|
// Attack a target that's attacking us
|
||||||
@ -156,8 +314,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update target when attack begins
|
// Update target when attack begins
|
||||||
if let Some(tgt) = new_target {
|
match agent.target {
|
||||||
agent.target = Some((tgt, time.0));
|
Some((tgt, time)) if Some(tgt) == new_target => {},
|
||||||
|
_ => agent.target = new_target.map(|tgt| (tgt, time.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chase target
|
// Chase target
|
||||||
@ -193,6 +352,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Choose new wander position
|
// Choose new wander position
|
||||||
|
/*
|
||||||
if agent.wander_pos.is_none() || thread_rng().gen::<f32>() < 0.005 {
|
if agent.wander_pos.is_none() || thread_rng().gen::<f32>() < 0.005 {
|
||||||
agent.wander_pos = if thread_rng().gen::<f32>() < 0.5 {
|
agent.wander_pos = if thread_rng().gen::<f32>() < 0.5 {
|
||||||
let max_dist = if agent.owner.is_some() {
|
let max_dist = if agent.owner.is_some() {
|
||||||
@ -210,7 +370,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
||||||
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
||||||
|
@ -465,14 +465,14 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
|
|||||||
.unwrap_or(1)
|
.unwrap_or(1)
|
||||||
.min(10);
|
.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) {
|
match server.state.read_component_cloned::<comp::Pos>(entity) {
|
||||||
Some(pos) => {
|
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 {
|
for _ in 0..amount {
|
||||||
let vel = Vec3::new(
|
let vel = Vec3::new(
|
||||||
rand::thread_rng().gen_range(-2.0, 3.0),
|
rand::thread_rng().gen_range(-2.0, 3.0),
|
||||||
|
@ -32,7 +32,7 @@ impl World {
|
|||||||
|
|
||||||
let mut supplement = ChunkSupplement::default();
|
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 {
|
supplement = supplement.with_entity(EntityInfo {
|
||||||
pos: Vec3::<f32>::from(chunk_pos.map(|e| e as f32 * 32.0)) + Vec3::unit_z() * 256.0,
|
pos: Vec3::<f32>::from(chunk_pos.map(|e| e as f32 * 32.0)) + Vec3::unit_z() * 256.0,
|
||||||
kind: EntityKind::Waypoint,
|
kind: EntityKind::Waypoint,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user