mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/small-fixes' into 'master'
NPC AI bug fixes, improvements See merge request veloren/veloren!767
This commit is contained in:
commit
3bd3422f8a
@ -8,13 +8,25 @@ pub enum Alignment {
|
|||||||
Wild,
|
Wild,
|
||||||
Enemy,
|
Enemy,
|
||||||
Npc,
|
Npc,
|
||||||
|
Owned(EcsEntity),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Alignment {
|
impl Alignment {
|
||||||
|
// Always attacks
|
||||||
pub fn hostile_towards(self, other: Alignment) -> bool {
|
pub fn hostile_towards(self, other: Alignment) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Alignment::Wild, Alignment::Npc) => false,
|
(Alignment::Enemy, Alignment::Enemy) => false,
|
||||||
_ => self != other,
|
(Alignment::Enemy, _) => true,
|
||||||
|
(_, Alignment::Enemy) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never attacks
|
||||||
|
pub fn passive_towards(self, other: Alignment) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Alignment::Owned(a), Alignment::Owned(b)) if a == b => true,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,17 +37,11 @@ impl Component for Alignment {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Agent {
|
pub struct Agent {
|
||||||
pub owner: Option<EcsEntity>,
|
|
||||||
pub patrol_origin: Option<Vec3<f32>>,
|
pub patrol_origin: Option<Vec3<f32>>,
|
||||||
pub activity: Activity,
|
pub activity: Activity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Agent {
|
impl Agent {
|
||||||
pub fn with_pet(mut self, owner: EcsEntity) -> Self {
|
|
||||||
self.owner = Some(owner);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_patrol_origin(mut self, origin: Vec3<f32>) -> Self {
|
pub fn with_patrol_origin(mut self, origin: Vec3<f32>) -> Self {
|
||||||
self.patrol_origin = Some(origin);
|
self.patrol_origin = Some(origin);
|
||||||
self
|
self
|
||||||
@ -50,7 +56,12 @@ impl Component for Agent {
|
|||||||
pub enum Activity {
|
pub enum Activity {
|
||||||
Idle(Vec2<f32>),
|
Idle(Vec2<f32>),
|
||||||
Follow(EcsEntity, Chaser),
|
Follow(EcsEntity, Chaser),
|
||||||
Attack(EcsEntity, Chaser, f64),
|
Attack {
|
||||||
|
target: EcsEntity,
|
||||||
|
chaser: Chaser,
|
||||||
|
time: f64,
|
||||||
|
been_close: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Activity {
|
impl Activity {
|
||||||
@ -63,7 +74,7 @@ impl Activity {
|
|||||||
|
|
||||||
pub fn is_attack(&self) -> bool {
|
pub fn is_attack(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Activity::Attack(_, _, _) => true,
|
Activity::Attack { .. } => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,11 +219,15 @@ where
|
|||||||
is_walkable(&(pos + Vec3::new(0, -1, 0))),
|
is_walkable(&(pos + Vec3::new(0, -1, 0))),
|
||||||
];
|
];
|
||||||
|
|
||||||
const DIAGONALS: [(Vec3<i32>, [usize; 2]); 4] = [
|
const DIAGONALS: [(Vec3<i32>, [usize; 2]); 8] = [
|
||||||
(Vec3::new(1, 1, 0), [0, 2]),
|
(Vec3::new(1, 1, 0), [0, 2]),
|
||||||
(Vec3::new(-1, 1, 0), [1, 2]),
|
(Vec3::new(-1, 1, 0), [1, 2]),
|
||||||
(Vec3::new(1, -1, 0), [0, 3]),
|
(Vec3::new(1, -1, 0), [0, 3]),
|
||||||
(Vec3::new(-1, -1, 0), [1, 3]),
|
(Vec3::new(-1, -1, 0), [1, 3]),
|
||||||
|
(Vec3::new(1, 1, 1), [0, 2]),
|
||||||
|
(Vec3::new(-1, 1, 1), [1, 2]),
|
||||||
|
(Vec3::new(1, -1, 1), [0, 3]),
|
||||||
|
(Vec3::new(-1, -1, 1), [1, 3]),
|
||||||
];
|
];
|
||||||
|
|
||||||
DIRS.iter()
|
DIRS.iter()
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
sync::UidAllocator,
|
sync::UidAllocator,
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use specs::{
|
use specs::{
|
||||||
saveload::{Marker, MarkerAllocator},
|
saveload::{Marker, MarkerAllocator},
|
||||||
Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
|
Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
|
||||||
@ -75,7 +75,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
const AVG_FOLLOW_DIST: f32 = 6.0;
|
const AVG_FOLLOW_DIST: f32 = 6.0;
|
||||||
const MAX_FOLLOW_DIST: f32 = 12.0;
|
const MAX_FOLLOW_DIST: f32 = 12.0;
|
||||||
const MAX_CHASE_DIST: f32 = 24.0;
|
const MAX_CHASE_DIST: f32 = 24.0;
|
||||||
const SIGHT_DIST: f32 = 30.0;
|
const SEARCH_DIST: f32 = 30.0;
|
||||||
|
const SIGHT_DIST: f32 = 64.0;
|
||||||
const MIN_ATTACK_DIST: f32 = 3.25;
|
const MIN_ATTACK_DIST: f32 = 3.25;
|
||||||
|
|
||||||
let mut do_idle = false;
|
let mut do_idle = false;
|
||||||
@ -146,18 +147,24 @@ impl<'a> System<'a> for Sys {
|
|||||||
do_idle = true;
|
do_idle = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Activity::Attack(target, chaser, _) => {
|
Activity::Attack {
|
||||||
|
target,
|
||||||
|
chaser,
|
||||||
|
been_close,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
if let (Some(tgt_pos), _tgt_stats, tgt_alignment) = (
|
if let (Some(tgt_pos), _tgt_stats, tgt_alignment) = (
|
||||||
positions.get(*target),
|
positions.get(*target),
|
||||||
stats.get(*target),
|
stats.get(*target),
|
||||||
alignments.get(*target),
|
alignments
|
||||||
|
.get(*target)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(Alignment::Owned(*target)),
|
||||||
) {
|
) {
|
||||||
// Don't attack aligned entities
|
// Don't attack entities we are passive towards
|
||||||
// TODO: This is a bit of a hack, find a better way to do this
|
// TODO: This is here, it's a bit of a hack
|
||||||
if let (Some(alignment), Some(tgt_alignment)) =
|
if let Some(alignment) = alignment {
|
||||||
(alignment, tgt_alignment)
|
if (*alignment).passive_towards(tgt_alignment) {
|
||||||
{
|
|
||||||
if !tgt_alignment.hostile_towards(*alignment) {
|
|
||||||
do_idle = true;
|
do_idle = true;
|
||||||
break 'activity;
|
break 'activity;
|
||||||
}
|
}
|
||||||
@ -172,7 +179,13 @@ impl<'a> System<'a> for Sys {
|
|||||||
.unwrap_or(Vec2::unit_y())
|
.unwrap_or(Vec2::unit_y())
|
||||||
* 0.01;
|
* 0.01;
|
||||||
inputs.primary.set_state(true);
|
inputs.primary.set_state(true);
|
||||||
} else if dist_sqrd < MAX_CHASE_DIST.powf(2.0) {
|
} else if dist_sqrd < MAX_CHASE_DIST.powf(2.0)
|
||||||
|
|| (dist_sqrd < SIGHT_DIST.powf(2.0) && !*been_close)
|
||||||
|
{
|
||||||
|
if dist_sqrd < MAX_CHASE_DIST.powf(2.0) {
|
||||||
|
*been_close = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Long-range chase
|
// Long-range chase
|
||||||
if let Some(bearing) =
|
if let Some(bearing) =
|
||||||
chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25)
|
chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25)
|
||||||
@ -182,6 +195,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
.unwrap_or(Vec2::zero());
|
.unwrap_or(Vec2::zero());
|
||||||
inputs.jump.set_state(bearing.z > 1.0);
|
inputs.jump.set_state(bearing.z > 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dist_sqrd < (MAX_CHASE_DIST * 0.65).powf(2.0)
|
||||||
|
&& thread_rng().gen::<f32>() < 0.01
|
||||||
|
{
|
||||||
|
inputs.roll.set_state(true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
do_idle = true;
|
do_idle = true;
|
||||||
}
|
}
|
||||||
@ -201,21 +220,26 @@ impl<'a> System<'a> for Sys {
|
|||||||
if choose_target {
|
if choose_target {
|
||||||
// Search for new targets (this looks expensive, but it's only run occasionally)
|
// Search for new targets (this looks expensive, but it's only run occasionally)
|
||||||
// TODO: Replace this with a better system that doesn't consider *all* entities
|
// TODO: Replace this with a better system that doesn't consider *all* entities
|
||||||
let entities = (&entities, &positions, &stats, alignments.maybe())
|
let closest_entity = (&entities, &positions, &stats, alignments.maybe())
|
||||||
.join()
|
.join()
|
||||||
.filter(|(e, e_pos, e_stats, e_alignment)| {
|
.filter(|(e, e_pos, e_stats, e_alignment)| {
|
||||||
(e_pos.0 - pos.0).magnitude_squared() < SIGHT_DIST.powf(2.0)
|
e_pos.0.distance_squared(pos.0) < SEARCH_DIST.powf(2.0)
|
||||||
&& *e != entity
|
&& *e != entity
|
||||||
&& !e_stats.is_dead
|
&& !e_stats.is_dead
|
||||||
&& alignment
|
&& alignment
|
||||||
.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b)))
|
.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b)))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
})
|
})
|
||||||
.map(|(e, _, _, _)| e)
|
.min_by_key(|(_, e_pos, _, _)| (e_pos.0.distance_squared(pos.0) * 100.0) as i32)
|
||||||
.collect::<Vec<_>>();
|
.map(|(e, _, _, _)| e);
|
||||||
|
|
||||||
if let Some(target) = (&entities).choose(&mut thread_rng()).cloned() {
|
if let Some(target) = closest_entity {
|
||||||
agent.activity = Activity::Attack(target, Chaser::default(), time.0);
|
agent.activity = Activity::Attack {
|
||||||
|
target,
|
||||||
|
chaser: Chaser::default(),
|
||||||
|
time: time.0,
|
||||||
|
been_close: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,8 +253,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
if !agent.activity.is_attack() {
|
if !agent.activity.is_attack() {
|
||||||
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id())
|
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id())
|
||||||
{
|
{
|
||||||
agent.activity =
|
agent.activity = Activity::Attack {
|
||||||
Activity::Attack(attacker, Chaser::default(), time.0);
|
target: attacker,
|
||||||
|
chaser: Chaser::default(),
|
||||||
|
time: time.0,
|
||||||
|
been_close: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,7 +266,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Follow owner if we're too far, or if they're under attack
|
// Follow owner if we're too far, or if they're under attack
|
||||||
if let Some(owner) = agent.owner {
|
if let Some(Alignment::Owned(owner)) = alignment.copied() {
|
||||||
if let Some(owner_pos) = positions.get(owner) {
|
if let Some(owner_pos) = positions.get(owner) {
|
||||||
let dist_sqrd = pos.0.distance_squared(owner_pos.0);
|
let dist_sqrd = pos.0.distance_squared(owner_pos.0);
|
||||||
if dist_sqrd > MAX_FOLLOW_DIST.powf(2.0) && !agent.activity.is_follow() {
|
if dist_sqrd > MAX_FOLLOW_DIST.powf(2.0) && !agent.activity.is_follow() {
|
||||||
@ -255,8 +283,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
if let Some(attacker) =
|
if let Some(attacker) =
|
||||||
uid_allocator.retrieve_entity_internal(by.id())
|
uid_allocator.retrieve_entity_internal(by.id())
|
||||||
{
|
{
|
||||||
agent.activity =
|
agent.activity = Activity::Attack {
|
||||||
Activity::Attack(attacker, Chaser::default(), time.0);
|
target: attacker,
|
||||||
|
chaser: Chaser::default(),
|
||||||
|
time: time.0,
|
||||||
|
been_close: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -458,7 +458,7 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat
|
|||||||
fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
|
||||||
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
|
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
|
||||||
(Some(opt_align), Some(id), opt_amount) => {
|
(Some(opt_align), Some(id), opt_amount) => {
|
||||||
if let Some(alignment) = parse_alignment(&opt_align) {
|
if let Some(alignment) = parse_alignment(entity, &opt_align) {
|
||||||
let amount = opt_amount
|
let amount = opt_amount
|
||||||
.and_then(|a| a.parse().ok())
|
.and_then(|a| a.parse().ok())
|
||||||
.filter(|x| *x > 0)
|
.filter(|x| *x > 0)
|
||||||
@ -467,11 +467,12 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
|
|||||||
|
|
||||||
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 {
|
let agent =
|
||||||
comp::Agent::default().with_pet(entity)
|
if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
|
||||||
} else {
|
comp::Agent::default()
|
||||||
comp::Agent::default().with_patrol_origin(pos.0)
|
} 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(
|
||||||
@ -583,11 +584,12 @@ fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_alignment(alignment: &str) -> Option<comp::Alignment> {
|
fn parse_alignment(owner: EcsEntity, alignment: &str) -> Option<comp::Alignment> {
|
||||||
match alignment {
|
match alignment {
|
||||||
"wild" => Some(comp::Alignment::Wild),
|
"wild" => Some(comp::Alignment::Wild),
|
||||||
"enemy" => Some(comp::Alignment::Enemy),
|
"enemy" => Some(comp::Alignment::Enemy),
|
||||||
"npc" => Some(comp::Alignment::Npc),
|
"npc" => Some(comp::Alignment::Npc),
|
||||||
|
"pet" => Some(comp::Alignment::Owned(owner)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,7 +300,7 @@ impl Server {
|
|||||||
state.write_component(entity, comp::Ori(Vec3::unit_y()));
|
state.write_component(entity, comp::Ori(Vec3::unit_y()));
|
||||||
state.write_component(entity, comp::Gravity(1.0));
|
state.write_component(entity, comp::Gravity(1.0));
|
||||||
state.write_component(entity, comp::CharacterState::default());
|
state.write_component(entity, comp::CharacterState::default());
|
||||||
state.write_component(entity, comp::Alignment::Npc);
|
state.write_component(entity, comp::Alignment::Owned(entity));
|
||||||
state.write_component(entity, comp::Inventory::default());
|
state.write_component(entity, comp::Inventory::default());
|
||||||
state.write_component(entity, comp::InventoryUpdate);
|
state.write_component(entity, comp::InventoryUpdate);
|
||||||
// Make sure physics are accepted.
|
// Make sure physics are accepted.
|
||||||
|
Loading…
Reference in New Issue
Block a user