Resolved pet alignment issues, added ranged aggro

This commit is contained in:
Joshua Barretto 2020-01-27 15:51:07 +00:00
parent 7a9f6b26e9
commit de96551f65
5 changed files with 89 additions and 41 deletions

View File

@ -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,
}
}

View File

@ -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()

View File

@ -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,
};
}
}
}

View File

@ -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,
}
}

View File

@ -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.