Merge branch 'holychowders/store_last_known_pos' into 'master'

Store Target's Last Known Position

See merge request veloren/veloren!3698
This commit is contained in:
Marcel 2022-11-20 22:38:14 +00:00
commit 2f990fb06e
5 changed files with 146 additions and 14 deletions

View File

@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Sword
- Doors now animate opening when entities are near them.
- Musical instruments can now be crafted, looted and played
- NPCs now move to their target's last known position.
### Changed

View File

@ -420,15 +420,23 @@ pub struct Target {
pub selected_at: f64,
/// Whether the target has come close enough to trigger aggro.
pub aggro_on: bool,
pub last_known_pos: Option<Vec3<f32>>,
}
impl Target {
pub fn new(target: EcsEntity, hostile: bool, selected_at: f64, aggro_on: bool) -> Self {
pub fn new(
target: EcsEntity,
hostile: bool,
selected_at: f64,
aggro_on: bool,
last_known_pos: Option<Vec3<f32>>,
) -> Self {
Self {
target,
hostile,
selected_at,
aggro_on,
last_known_pos,
}
}
}

View File

@ -701,9 +701,8 @@ impl<'a> AgentData<'a> {
},
};
let is_detected = |entity: EcsEntity, e_pos: &Pos| {
self.can_sense_directly_near(e_pos)
|| self.can_see_entity(agent, controller, entity, e_pos, read_data)
let is_detected = |entity: &EcsEntity, e_pos: &Pos| {
self.detects_other(agent, controller, entity, e_pos, read_data)
};
let target = entities_nearby
@ -713,7 +712,7 @@ impl<'a> AgentData<'a> {
.filter_map(|(entity, attack_target)| {
get_pos(entity).map(|pos| (entity, pos, attack_target))
})
.filter(|(entity, e_pos, _)| is_detected(*entity, e_pos))
.filter(|(entity, e_pos, _)| is_detected(entity, e_pos))
.min_by_key(|(_, e_pos, attack_target)| {
(
*attack_target,
@ -735,6 +734,7 @@ impl<'a> AgentData<'a> {
hostile: attack_target,
selected_at: read_data.time.0,
aggro_on,
last_known_pos: get_pos(entity).map(|pos| pos.0),
})
}
@ -1341,14 +1341,24 @@ impl<'a> AgentData<'a> {
controller.push_utterance(UtteranceKind::Angry);
}
agent.target = Some(Target::new(attacker, true, read_data.time.0, true));
let attacker_pos = read_data.positions.get(attacker).map(|pos| pos.0);
agent.target = Some(Target::new(
attacker,
true,
read_data.time.0,
true,
attacker_pos,
));
if let Some(tgt_pos) = read_data.positions.get(attacker) {
if is_dead_or_invulnerable(attacker, read_data) {
// FIXME?: Shouldn't target be set to `None`?
// If is dead, then probably. If invulnerable, maybe not.
agent.target =
Some(Target::new(target, false, read_data.time.0, false));
agent.target = Some(Target::new(
target,
false,
read_data.time.0,
false,
Some(tgt_pos.0),
));
self.idle(agent, controller, read_data, rng);
} else {
@ -1551,6 +1561,18 @@ impl<'a> AgentData<'a> {
&& entities_have_line_of_sight(self.pos, self.body, other_pos, other_body, read_data)
}
pub fn detects_other(
&self,
agent: &Agent,
controller: &Controller,
other: &EcsEntity,
other_pos: &Pos,
read_data: &ReadData,
) -> bool {
self.can_sense_directly_near(other_pos)
|| self.can_see_entity(agent, controller, *other, other_pos, read_data)
}
pub fn can_sense_directly_near(&self, e_pos: &Pos) -> bool {
let chance = thread_rng().gen_bool(0.3);
e_pos.0.distance_squared(self.pos.0) < 5_f32.powi(2) && chance

View File

@ -100,8 +100,10 @@ impl BehaviorTree {
pub fn target() -> Self {
Self {
tree: vec![
update_last_known_pos,
untarget_if_dead,
update_target_awareness,
search_last_known_pos_if_not_alert,
do_hostile_tree_if_hostile_and_aware,
do_pet_tree_if_owned,
do_pickup_loot,
@ -266,6 +268,11 @@ fn target_if_attacked(bdata: &mut BehaviorData) -> bool {
hostile: true,
selected_at: bdata.read_data.time.0,
aggro_on: true,
last_known_pos: bdata
.read_data
.positions
.get(attacker)
.map(|pos| pos.0),
});
}
@ -440,7 +447,15 @@ fn set_owner_if_no_target(bdata: &mut BehaviorData) -> bool {
if bdata.agent.target.is_none() && small_chance {
if let Some(Alignment::Owned(owner)) = bdata.agent_data.alignment {
if let Some(owner) = get_entity_by_id(owner.id(), bdata.read_data) {
bdata.agent.target = Some(Target::new(owner, false, bdata.read_data.time.0, false));
let owner_pos = bdata.read_data.positions.get(owner).map(|pos| pos.0);
bdata.agent.target = Some(Target::new(
owner,
false,
bdata.read_data.time.0,
false,
owner_pos,
));
}
}
}
@ -496,6 +511,43 @@ fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
false
}
fn update_last_known_pos(bdata: &mut BehaviorData) -> bool {
let BehaviorData {
agent,
agent_data,
read_data,
controller,
..
} = bdata;
if let Some(target_info) = agent.target {
let target = target_info.target;
if let Some(target_pos) = read_data.positions.get(target) {
if agent_data.detects_other(agent, controller, &target, target_pos, read_data) {
let updated_pos = Some(target_pos.0);
let Target {
hostile,
selected_at,
aggro_on,
..
} = target_info;
agent.target = Some(Target::new(
target,
hostile,
selected_at,
aggro_on,
updated_pos,
));
}
}
}
false
}
/// Try to heal self if our damage went below a certain threshold
fn heal_self_if_hurt(bdata: &mut BehaviorData) -> bool {
if bdata.agent_data.damage < HEALING_ITEM_THRESHOLD
@ -556,6 +608,31 @@ fn update_target_awareness(bdata: &mut BehaviorData) -> bool {
false
}
fn search_last_known_pos_if_not_alert(bdata: &mut BehaviorData) -> bool {
let awareness = &bdata.agent.awareness;
if awareness.reached() || awareness.state() < AwarenessState::Low {
return false;
}
let BehaviorData {
agent,
agent_data,
controller,
read_data,
..
} = bdata;
if let Some(target) = agent.target {
if let Some(last_known_pos) = target.last_known_pos {
agent_data.follow(agent, controller, &read_data.terrain, &Pos(last_known_pos));
return true;
}
}
false
}
fn do_combat(bdata: &mut BehaviorData) -> bool {
let BehaviorData {
agent,

View File

@ -91,7 +91,15 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
if let Some(AgentEvent::Talk(by, subject)) = agent.inbox.pop_front() {
if agent.allowed_to_speak() {
if let Some(target) = get_entity_by_id(by.id(), read_data) {
agent.target = Some(Target::new(target, false, read_data.time.0, false));
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
agent.target = Some(Target::new(
target,
false,
read_data.time.0,
false,
target_pos,
));
if agent_data.look_toward(controller, read_data, target) {
controller.push_action(ControlAction::Stand);
@ -415,7 +423,15 @@ pub fn handle_inbox_trade_invite(bdata: &mut BehaviorData) -> bool {
controller.push_action(ControlAction::Stand);
controller.push_action(ControlAction::Talk);
if let Some(target) = get_entity_by_id(with.id(), read_data) {
agent.target = Some(Target::new(target, false, read_data.time.0, false));
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
agent.target = Some(Target::new(
target,
false,
read_data.time.0,
false,
target_pos,
));
}
controller.push_invite_response(InviteResponse::Accept);
agent.behavior.unset(BehaviorState::TRADING_ISSUER);
@ -454,7 +470,15 @@ pub fn handle_inbox_trade_accepted(bdata: &mut BehaviorData) -> bool {
if let Some(AgentEvent::TradeAccepted(with)) = agent.inbox.pop_front() {
if !agent.behavior.is(BehaviorState::TRADING) {
if let Some(target) = get_entity_by_id(with.id(), read_data) {
agent.target = Some(Target::new(target, false, read_data.time.0, false));
let target_pos = read_data.positions.get(target).map(|pos| pos.0);
agent.target = Some(Target::new(
target,
false,
read_data.time.0,
false,
target_pos,
));
}
agent.behavior.set(BehaviorState::TRADING);
agent.behavior.set(BehaviorState::TRADING_ISSUER);