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)]
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
mod admin;
|
||||
mod agent;
|
||||
pub mod agent;
|
||||
mod body;
|
||||
mod character_state;
|
||||
mod controller;
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user