mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Resolved pet alignment issues, added ranged aggro
This commit is contained in:
parent
7a9f6b26e9
commit
de96551f65
@ -8,13 +8,25 @@ pub enum Alignment {
|
||||
Wild,
|
||||
Enemy,
|
||||
Npc,
|
||||
Owned(EcsEntity),
|
||||
}
|
||||
|
||||
impl Alignment {
|
||||
// Always attacks
|
||||
pub fn hostile_towards(self, other: Alignment) -> bool {
|
||||
match (self, other) {
|
||||
(Alignment::Wild, Alignment::Npc) => false,
|
||||
_ => self != other,
|
||||
(Alignment::Enemy, Alignment::Enemy) => false,
|
||||
(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)]
|
||||
pub struct Agent {
|
||||
pub owner: Option<EcsEntity>,
|
||||
pub patrol_origin: Option<Vec3<f32>>,
|
||||
pub activity: Activity,
|
||||
}
|
||||
|
||||
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 {
|
||||
self.patrol_origin = Some(origin);
|
||||
self
|
||||
@ -50,7 +56,12 @@ impl Component for Agent {
|
||||
pub enum Activity {
|
||||
Idle(Vec2<f32>),
|
||||
Follow(EcsEntity, Chaser),
|
||||
Attack(EcsEntity, Chaser, f64),
|
||||
Attack {
|
||||
target: EcsEntity,
|
||||
chaser: Chaser,
|
||||
time: f64,
|
||||
been_close: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Activity {
|
||||
@ -63,7 +74,7 @@ impl Activity {
|
||||
|
||||
pub fn is_attack(&self) -> bool {
|
||||
match self {
|
||||
Activity::Attack(_, _, _) => true,
|
||||
Activity::Attack { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -219,11 +219,15 @@ where
|
||||
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), [1, 2]),
|
||||
(Vec3::new(1, -1, 0), [0, 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()
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
sync::UidAllocator,
|
||||
vol::ReadVol,
|
||||
};
|
||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
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 MAX_FOLLOW_DIST: f32 = 12.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;
|
||||
|
||||
let mut do_idle = false;
|
||||
@ -146,18 +147,24 @@ impl<'a> System<'a> for Sys {
|
||||
do_idle = true;
|
||||
}
|
||||
}
|
||||
Activity::Attack(target, chaser, _) => {
|
||||
Activity::Attack {
|
||||
target,
|
||||
chaser,
|
||||
been_close,
|
||||
..
|
||||
} => {
|
||||
if let (Some(tgt_pos), _tgt_stats, tgt_alignment) = (
|
||||
positions.get(*target),
|
||||
stats.get(*target),
|
||||
alignments.get(*target),
|
||||
alignments
|
||||
.get(*target)
|
||||
.copied()
|
||||
.unwrap_or(Alignment::Owned(*target)),
|
||||
) {
|
||||
// Don't attack aligned entities
|
||||
// TODO: This is a bit of a hack, find a better way to do this
|
||||
if let (Some(alignment), Some(tgt_alignment)) =
|
||||
(alignment, tgt_alignment)
|
||||
{
|
||||
if !tgt_alignment.hostile_towards(*alignment) {
|
||||
// Don't attack entities we are passive towards
|
||||
// TODO: This is here, it's a bit of a hack
|
||||
if let Some(alignment) = alignment {
|
||||
if (*alignment).passive_towards(tgt_alignment) {
|
||||
do_idle = true;
|
||||
break 'activity;
|
||||
}
|
||||
@ -172,7 +179,13 @@ impl<'a> System<'a> for Sys {
|
||||
.unwrap_or(Vec2::unit_y())
|
||||
* 0.01;
|
||||
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
|
||||
if let Some(bearing) =
|
||||
chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25)
|
||||
@ -185,6 +198,11 @@ impl<'a> System<'a> for Sys {
|
||||
} else {
|
||||
do_idle = true;
|
||||
}
|
||||
|
||||
// Sometimes try searching for new targets
|
||||
if thread_rng().gen::<f32>() < 0.01 {
|
||||
choose_target = true;
|
||||
}
|
||||
} else {
|
||||
do_idle = true;
|
||||
}
|
||||
@ -201,21 +219,26 @@ impl<'a> System<'a> for Sys {
|
||||
if choose_target {
|
||||
// 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
|
||||
let entities = (&entities, &positions, &stats, alignments.maybe())
|
||||
let closest_entity = (&entities, &positions, &stats, alignments.maybe())
|
||||
.join()
|
||||
.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_stats.is_dead
|
||||
&& alignment
|
||||
.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b)))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|(e, _, _, _)| e)
|
||||
.collect::<Vec<_>>();
|
||||
.min_by_key(|(_, e_pos, _, _)| (e_pos.0.distance_squared(pos.0) * 100.0) as i32)
|
||||
.map(|(e, _, _, _)| e);
|
||||
|
||||
if let Some(target) = (&entities).choose(&mut thread_rng()).cloned() {
|
||||
agent.activity = Activity::Attack(target, Chaser::default(), time.0);
|
||||
if let Some(target) = closest_entity {
|
||||
agent.activity = Activity::Attack {
|
||||
target,
|
||||
chaser: Chaser::default(),
|
||||
time: time.0,
|
||||
been_close: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,8 +252,12 @@ impl<'a> System<'a> for Sys {
|
||||
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);
|
||||
agent.activity = Activity::Attack {
|
||||
target: attacker,
|
||||
chaser: Chaser::default(),
|
||||
time: time.0,
|
||||
been_close: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,7 +265,7 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
let dist_sqrd = pos.0.distance_squared(owner_pos.0);
|
||||
if dist_sqrd > MAX_FOLLOW_DIST.powf(2.0) && !agent.activity.is_follow() {
|
||||
@ -255,8 +282,12 @@ impl<'a> System<'a> for Sys {
|
||||
if let Some(attacker) =
|
||||
uid_allocator.retrieve_entity_internal(by.id())
|
||||
{
|
||||
agent.activity =
|
||||
Activity::Attack(attacker, Chaser::default(), time.0);
|
||||
agent.activity = Activity::Attack {
|
||||
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) {
|
||||
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
|
||||
(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
|
||||
.and_then(|a| a.parse().ok())
|
||||
.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) {
|
||||
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)
|
||||
};
|
||||
let agent =
|
||||
if let comp::Alignment::Owned(_) | comp::Alignment::Npc = alignment {
|
||||
comp::Agent::default()
|
||||
} else {
|
||||
comp::Agent::default().with_patrol_origin(pos.0)
|
||||
};
|
||||
|
||||
for _ in 0..amount {
|
||||
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 {
|
||||
"wild" => Some(comp::Alignment::Wild),
|
||||
"enemy" => Some(comp::Alignment::Enemy),
|
||||
"npc" => Some(comp::Alignment::Npc),
|
||||
"pet" => Some(comp::Alignment::Owned(owner)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -300,7 +300,7 @@ impl Server {
|
||||
state.write_component(entity, comp::Ori(Vec3::unit_y()));
|
||||
state.write_component(entity, comp::Gravity(1.0));
|
||||
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::InventoryUpdate);
|
||||
// Make sure physics are accepted.
|
||||
|
Loading…
Reference in New Issue
Block a user