1use common::{
2 comp::{
3 Agent, Alignment, BehaviorCapability, BehaviorState, Body, BuffKind, CharacterState,
4 ControlAction, ControlEvent, Controller, InputKind, InventoryEvent, Pos, PresenceKind,
5 UtteranceKind,
6 agent::{
7 AgentEvent, AwarenessState, DEFAULT_INTERACTION_TIME, TRADE_INTERACTION_TIME, Target,
8 TimerAction,
9 },
10 dialogue::Subject,
11 is_downed,
12 },
13 consts::MAX_INTERACT_RANGE,
14 interaction::InteractionKind,
15 path::TraversalConfig,
16 rtsim::{NpcAction, RtSimEntity},
17};
18use rand::{Rng, prelude::ThreadRng, thread_rng};
19use server_agent::{data::AgentEmitters, util::is_steering};
20use specs::Entity as EcsEntity;
21use tracing::warn;
22use vek::{Vec2, Vec3};
23
24use self::interaction::{
25 handle_inbox_cancel_interactions, handle_inbox_dialogue, handle_inbox_finished_trade,
26 handle_inbox_talk, handle_inbox_trade_accepted, handle_inbox_trade_invite,
27 handle_inbox_update_pending_trade, increment_timer_deltatime, process_inbox_interaction,
28 process_inbox_sound_and_hurt,
29};
30
31use super::{
32 consts::{
33 DAMAGE_MEMORY_DURATION, FLEE_DURATION, HEALING_ITEM_THRESHOLD, MAX_PATROL_DIST,
34 MAX_STAY_DISTANCE, NORMAL_FLEE_DIR_DIST, NPC_PICKUP_RANGE, RETARGETING_THRESHOLD_SECONDS,
35 STD_AWARENESS_DECAY_RATE,
36 },
37 data::{AgentData, ReadData, TargetData},
38 util::{get_entity_by_id, is_dead, is_dead_or_invulnerable, is_invulnerable, stop_pursuing},
39};
40
41mod interaction;
42
43pub struct BehaviorData<'a, 'b, 'c> {
45 pub agent: &'a mut Agent,
46 pub agent_data: AgentData<'a>,
47 pub read_data: &'a ReadData<'a>,
48 pub emitters: &'a mut AgentEmitters<'c>,
49 pub controller: &'a mut Controller,
50 pub rng: &'b mut ThreadRng,
51}
52
53type BehaviorFn = fn(&mut BehaviorData) -> bool;
57
58pub struct BehaviorTree {
62 tree: Vec<BehaviorFn>,
63}
64
65enum ActionStateBehaviorTreeTimers {
78 TimerBehaviorTree = 0,
79}
80
81impl BehaviorTree {
82 pub fn root() -> Self {
86 Self {
87 tree: vec![
88 maintain_if_gliding,
89 react_on_dangerous_fall,
90 react_if_on_fire,
91 target_if_attacked,
92 process_inbox_sound_and_hurt,
93 process_inbox_interaction,
94 do_target_tree_if_target_else_do_idle_tree,
95 ],
96 }
97 }
98
99 pub fn target() -> Self {
104 Self {
105 tree: vec![
106 update_last_known_pos,
107 untarget_if_dead,
108 update_target_awareness,
109 search_last_known_pos_if_not_alert,
110 do_hostile_tree_if_hostile_and_aware,
111 do_save_allies,
112 do_pet_tree_if_owned,
113 do_pickup_loot,
114 do_idle_tree,
115 ],
116 }
117 }
118
119 pub fn pet() -> Self {
123 Self {
124 tree: vec![follow_if_far_away, attack_if_owner_hurt, do_idle_tree],
125 }
126 }
127
128 pub fn interaction(agent: &Agent) -> Self {
134 let is_in_combat = agent.target.is_some_and(|t| t.hostile);
135 if !is_in_combat
136 && (agent.behavior.can(BehaviorCapability::SPEAK)
137 || agent.behavior.can(BehaviorCapability::TRADE))
138 {
139 let mut tree: Vec<BehaviorFn> = vec![increment_timer_deltatime];
140 if agent.behavior.can(BehaviorCapability::SPEAK) {
141 tree.extend([handle_inbox_dialogue, handle_inbox_talk]);
142 }
143 tree.extend_from_slice(&[
144 handle_inbox_trade_invite,
145 handle_inbox_trade_accepted,
146 handle_inbox_finished_trade,
147 handle_inbox_update_pending_trade,
148 ]);
149 Self { tree }
150 } else {
151 Self {
152 tree: vec![handle_inbox_cancel_interactions],
153 }
154 }
155 }
156
157 pub fn hostile() -> Self {
161 Self {
162 tree: vec![heal_self_if_hurt, hurt_utterance, do_combat],
163 }
164 }
165
166 pub fn idle() -> Self {
168 Self {
169 tree: vec![
170 set_owner_if_no_target,
171 handle_rtsim_actions,
172 handle_timed_events,
173 ],
174 }
175 }
176
177 pub fn run(&self, behavior_data: &mut BehaviorData) -> bool {
179 for behavior_fn in self.tree.iter() {
180 if behavior_fn(behavior_data) {
181 return true;
182 }
183 }
184 false
185 }
186}
187
188fn maintain_if_gliding(bdata: &mut BehaviorData) -> bool {
191 let Some(char_state) = bdata.read_data.char_states.get(*bdata.agent_data.entity) else {
192 return false;
193 };
194
195 match char_state {
196 CharacterState::Glide(_) => {
197 bdata
198 .agent_data
199 .glider_flight(bdata.controller, bdata.read_data);
200 true
201 },
202 CharacterState::GlideWield(_) => {
203 if bdata.agent_data.physics_state.on_ground.is_some() {
204 bdata.controller.push_action(ControlAction::Unwield);
205 }
206 true
212 },
213 _ => false,
214 }
215}
216
217fn react_on_dangerous_fall(bdata: &mut BehaviorData) -> bool {
224 let is_falling_dangerous = bdata.agent_data.vel.0.z < -20.0;
227
228 if is_falling_dangerous {
229 if bdata.read_data.is_riders.contains(*bdata.agent_data.entity) {
230 bdata.controller.push_event(ControlEvent::Unmount);
231 }
232 if bdata.agent_data.traversal_config.can_fly {
233 bdata
234 .agent_data
235 .fly_upward(bdata.controller, bdata.read_data);
236 return true;
237 } else if bdata.agent_data.glider_equipped {
238 bdata
239 .agent_data
240 .glider_equip(bdata.controller, bdata.read_data);
241 return true;
242 }
243 }
244 false
245}
246
247fn react_if_on_fire(bdata: &mut BehaviorData) -> bool {
249 let is_on_fire = bdata
250 .read_data
251 .buffs
252 .get(*bdata.agent_data.entity)
253 .is_some_and(|b| b.kinds[BuffKind::Burning].is_some());
254
255 if is_on_fire
256 && bdata.agent_data.body.is_some_and(|b| b.is_humanoid())
257 && bdata.agent_data.physics_state.on_ground.is_some()
258 && bdata
259 .rng
260 .gen_bool((2.0 * bdata.read_data.dt.0).clamp(0.0, 1.0) as f64)
261 {
262 bdata.controller.inputs.move_dir = bdata
263 .agent_data
264 .ori
265 .look_vec()
266 .xy()
267 .try_normalized()
268 .unwrap_or_else(Vec2::zero);
269 bdata.controller.push_basic_input(InputKind::Roll);
270 return true;
271 }
272 false
273}
274
275fn target_if_attacked(bdata: &mut BehaviorData) -> bool {
278 match bdata.agent_data.health {
279 Some(health)
280 if bdata.read_data.time.0 - health.last_change.time.0 < DAMAGE_MEMORY_DURATION
281 && health.last_change.amount < 0.0 =>
282 {
283 if let Some(by) = health.last_change.damage_by() {
284 if let Some(attacker) = bdata.read_data.id_maps.uid_entity(by.uid()) {
285 if is_dead_or_invulnerable(attacker, bdata.read_data) {
288 bdata.agent.target = None;
289 } else {
290 if bdata.agent.target.is_none() {
291 bdata
292 .controller
293 .push_event(ControlEvent::Utterance(UtteranceKind::Angry));
294 }
295
296 bdata.agent.awareness.change_by(1.0);
297
298 if bdata.agent.target.is_none_or(|target| {
302 bdata.agent_data.is_more_dangerous_than_target(
303 attacker,
304 target,
305 bdata.read_data,
306 )
307 }) {
308 bdata.agent.target = Some(Target {
309 target: attacker,
310 hostile: true,
311 selected_at: bdata.read_data.time.0,
312 aggro_on: true,
313 last_known_pos: bdata
314 .read_data
315 .positions
316 .get(attacker)
317 .map(|pos| pos.0),
318 });
319 }
320
321 }
332 }
333 }
334 },
335 _ => {},
336 }
337 false
338}
339
340fn do_target_tree_if_target_else_do_idle_tree(bdata: &mut BehaviorData) -> bool {
344 if bdata.agent.target.is_some() && !is_steering(*bdata.agent_data.entity, bdata.read_data) {
345 BehaviorTree::target().run(bdata);
346 } else {
347 BehaviorTree::idle().run(bdata);
348 }
349 false
350}
351
352fn do_idle_tree(bdata: &mut BehaviorData) -> bool { BehaviorTree::idle().run(bdata) }
356
357fn untarget_if_dead(bdata: &mut BehaviorData) -> bool {
359 if let Some(Target { target, .. }) = bdata.agent.target {
360 if bdata
362 .read_data
363 .healths
364 .get(target)
365 .is_none_or(|tgt_health| tgt_health.is_dead)
366 {
367 bdata.agent.target = None;
373 return true;
374 }
375 }
376 false
377}
378
379fn do_hostile_tree_if_hostile_and_aware(bdata: &mut BehaviorData) -> bool {
382 let alert = bdata.agent.awareness.reached();
383
384 if let Some(Target { hostile, .. }) = bdata.agent.target {
385 if alert && hostile {
386 BehaviorTree::hostile().run(bdata);
387 return true;
388 }
389 }
390 false
391}
392
393fn do_pet_tree_if_owned(bdata: &mut BehaviorData) -> bool {
395 if let (Some(Target { target, .. }), Some(Alignment::Owned(uid))) =
396 (bdata.agent.target, bdata.agent_data.alignment)
397 {
398 if bdata.read_data.uids.get(target) == Some(uid) {
399 BehaviorTree::pet().run(bdata);
400 } else {
401 bdata.agent.target = None;
402 BehaviorTree::idle().run(bdata);
403 }
404 return true;
405 }
406 false
407}
408
409fn do_pickup_loot(bdata: &mut BehaviorData) -> bool {
411 if let Some(Target { target, .. }) = bdata.agent.target {
412 if matches!(bdata.read_data.bodies.get(target), Some(Body::ItemDrop(_))) {
413 if let Some(tgt_pos) = bdata.read_data.positions.get(target) {
414 let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0);
415 if dist_sqrd < NPC_PICKUP_RANGE.powi(2) {
416 if let Some(uid) = bdata.read_data.uids.get(target) {
417 bdata
418 .controller
419 .push_event(ControlEvent::InventoryEvent(InventoryEvent::Pickup(*uid)));
420 }
421 bdata.agent.target = None;
422 } else if let Some((bearing, speed)) = bdata.agent.chaser.chase(
423 &*bdata.read_data.terrain,
424 bdata.agent_data.pos.0,
425 bdata.agent_data.vel.0,
426 tgt_pos.0,
427 TraversalConfig {
428 min_tgt_dist: NPC_PICKUP_RANGE - 1.0,
429 ..bdata.agent_data.traversal_config
430 },
431 ) {
432 bdata.controller.inputs.move_dir =
433 bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
434 * speed.min(0.2 + (dist_sqrd - (NPC_PICKUP_RANGE - 1.5).powi(2)) / 8.0);
435 bdata.agent_data.jump_if(bearing.z > 1.5, bdata.controller);
436 bdata.controller.inputs.move_z = bearing.z;
437 }
438 }
439 return true;
440 }
441 }
442 false
443}
444
445fn do_save_allies(bdata: &mut BehaviorData) -> bool {
447 if let Some(Target {
448 target,
449 hostile: false,
450 aggro_on: false,
451 ..
452 }) = bdata.agent.target
453 && let Some(target_uid) = bdata.read_data.uids.get(target)
454 {
455 let needs_saving = is_downed(
456 bdata.read_data.healths.get(target),
457 bdata.read_data.char_states.get(target),
458 );
459
460 let wants_to_save = match (bdata.agent_data.alignment, bdata.read_data.alignments.get(target)) {
461 (Some(Alignment::Npc), _) if bdata.read_data.presences.get(target).is_some_and(|presence| matches!(presence.kind, PresenceKind::Character(_))) => true,
464 (Some(Alignment::Npc), Some(Alignment::Npc)) => true,
465 (Some(Alignment::Enemy), Some(Alignment::Enemy)) => true,
466 _ => false,
467 } && bdata.agent.allowed_to_speak()
468 && bdata.read_data
470 .interactors
471 .get(target).is_none_or(|interactors| {
472 !interactors.has_interaction(InteractionKind::HelpDowned)
473 }) && bdata.agent_data.char_state.can_interact();
474
475 if needs_saving
476 && wants_to_save
477 && let Some(target_pos) = bdata.read_data.positions.get(target)
478 {
479 let dist_sqr = bdata.agent_data.pos.0.distance_squared(target_pos.0);
480 if dist_sqr < (MAX_INTERACT_RANGE * 0.5).powi(2) {
481 bdata.controller.push_event(ControlEvent::InteractWith {
482 target: *target_uid,
483 kind: common::interaction::InteractionKind::HelpDowned,
484 });
485 bdata.agent.target = None;
486 } else if let Some((bearing, speed)) = bdata.agent.chaser.chase(
487 &*bdata.read_data.terrain,
488 bdata.agent_data.pos.0,
489 bdata.agent_data.vel.0,
490 target_pos.0,
491 TraversalConfig {
492 min_tgt_dist: MAX_INTERACT_RANGE * 0.5,
493 ..bdata.agent_data.traversal_config
494 },
495 ) {
496 bdata.controller.inputs.move_dir =
497 bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
498 * speed
499 .min(0.2 + (dist_sqr - (MAX_INTERACT_RANGE * 0.5 - 0.5).powi(2)) / 8.0);
500 bdata.agent_data.jump_if(bearing.z > 1.5, bdata.controller);
501 bdata.controller.inputs.move_z = bearing.z;
502 }
503 return true;
504 }
505 }
506 false
507}
508
509fn follow_if_far_away(bdata: &mut BehaviorData) -> bool {
511 if let Some(Target { target, .. }) = bdata.agent.target {
512 if let Some(tgt_pos) = bdata.read_data.positions.get(target) {
513 if let Some(stay_pos) = bdata.agent.stay_pos {
514 let distance_from_stay = stay_pos.0.distance_squared(bdata.agent_data.pos.0);
515 bdata.controller.push_action(ControlAction::Sit);
516 if distance_from_stay > (MAX_STAY_DISTANCE).powi(2) {
517 bdata.agent_data.follow(
518 bdata.agent,
519 bdata.controller,
520 bdata.read_data,
521 &stay_pos,
522 );
523 return true;
524 }
525 } else {
526 bdata.controller.push_action(ControlAction::Stand);
527 let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0);
528 if dist_sqrd > (MAX_PATROL_DIST * bdata.agent.psyche.idle_wander_factor).powi(2) {
529 bdata.agent_data.follow(
530 bdata.agent,
531 bdata.controller,
532 bdata.read_data,
533 tgt_pos,
534 );
535 return true;
536 }
537 }
538 }
539 }
540 false
541}
542
543fn attack_if_owner_hurt(bdata: &mut BehaviorData) -> bool {
546 if let Some(Target { target, .. }) = bdata.agent.target {
547 if bdata.read_data.positions.get(target).is_some() {
548 let owner_recently_attacked =
549 if let Some(target_health) = bdata.read_data.healths.get(target) {
550 bdata.read_data.time.0 - target_health.last_change.time.0 < 5.0
551 && target_health.last_change.amount < 0.0
552 } else {
553 false
554 };
555 let stay = bdata.agent.stay_pos.is_some();
556 if owner_recently_attacked && !stay {
557 bdata.agent_data.attack_target_attacker(
558 bdata.agent,
559 bdata.read_data,
560 bdata.controller,
561 bdata.emitters,
562 bdata.rng,
563 );
564 return true;
565 }
566 }
567 }
568 false
569}
570
571fn set_owner_if_no_target(bdata: &mut BehaviorData) -> bool {
573 let small_chance = bdata.rng.gen_bool(0.1);
574
575 if bdata.agent.target.is_none() && small_chance {
576 if let Some(Alignment::Owned(owner)) = bdata.agent_data.alignment {
577 if let Some(owner) = get_entity_by_id(*owner, bdata.read_data) {
578 let owner_pos = bdata.read_data.positions.get(owner).map(|pos| pos.0);
579
580 bdata.agent.target = Some(Target::new(
581 owner,
582 false,
583 bdata.read_data.time.0,
584 false,
585 owner_pos,
586 ));
587 bdata.agent.awareness.set_maximally_aware();
589 }
590 }
591 }
592 false
593}
594
595fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
597 if let Some(action) = bdata.agent.rtsim_controller.actions.pop_front() {
598 match action {
599 NpcAction::Say(target, msg) => {
600 if bdata.agent.allowed_to_speak() {
601 if let Some(target) =
603 target.and_then(|tgt| bdata.read_data.id_maps.actor_entity(tgt))
604 {
605 bdata.agent.target = Some(Target::new(
606 target,
607 false,
608 bdata.read_data.time.0,
609 false,
610 bdata.read_data.positions.get(target).map(|p| p.0),
611 ));
612 bdata.agent.awareness.set_maximally_aware();
614 bdata
616 .agent
617 .timer
618 .start(bdata.read_data.time.0, TimerAction::Interact);
619 bdata.controller.push_action(ControlAction::Stand);
620
621 if let Some(target_uid) = bdata.read_data.uids.get(target) {
622 bdata
623 .controller
624 .push_event(ControlEvent::Interact(*target_uid, Subject::Regular));
625 }
626 }
627 bdata.controller.push_utterance(UtteranceKind::Greeting);
628 bdata.agent_data.chat_npc(msg, bdata.emitters);
629 }
630 },
631 NpcAction::Attack(target) => {
632 if let Some(target) = bdata.read_data.id_maps.actor_entity(target) {
633 bdata.agent.target = Some(Target::new(
634 target,
635 true,
636 bdata.read_data.time.0,
637 false,
638 bdata.read_data.positions.get(target).map(|p| p.0),
639 ));
640 bdata.agent.awareness.set_maximally_aware();
641 }
642 },
643 NpcAction::Dialogue(target, dialogue) => {
644 if let Some(target) = bdata.read_data.id_maps.actor_entity(target)
645 && let Some(target_uid) = bdata.read_data.uids.get(target)
646 {
647 bdata
648 .controller
649 .push_event(ControlEvent::Dialogue(*target_uid, dialogue));
650 bdata.controller.push_utterance(UtteranceKind::Greeting);
651 } else {
652 warn!("NPC dialogue sent to non-existent target entity");
653 }
654 },
655 }
656 true
657 } else {
658 false
659 }
660}
661
662fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
664 let timeout = if bdata.agent.behavior.is(BehaviorState::TRADING) {
665 TRADE_INTERACTION_TIME
666 } else {
667 DEFAULT_INTERACTION_TIME
668 };
669
670 match bdata.agent.timer.timeout_elapsed(
671 bdata.read_data.time.0,
672 TimerAction::Interact,
673 timeout as f64,
674 ) {
675 None => {
676 if let Some(Target { target, .. }) = &bdata.agent.target {
678 bdata
679 .agent_data
680 .look_toward(bdata.controller, bdata.read_data, *target);
681 bdata.controller.push_action(ControlAction::Talk);
682 }
683 },
684 Some(just_ended) => {
685 if just_ended {
686 bdata.agent.target = None;
687 bdata.controller.push_action(ControlAction::Stand);
688 }
689
690 if bdata.rng.gen::<f32>() < 0.1 {
691 bdata.agent_data.choose_target(
692 bdata.agent,
693 bdata.controller,
694 bdata.read_data,
695 AgentData::is_enemy,
696 );
697 } else {
698 bdata.agent_data.handle_sounds_heard(
699 bdata.agent,
700 bdata.controller,
701 bdata.read_data,
702 bdata.emitters,
703 bdata.rng,
704 );
705 }
706 },
707 }
708 false
709}
710
711fn update_last_known_pos(bdata: &mut BehaviorData) -> bool {
712 let BehaviorData {
713 agent,
714 agent_data,
715 read_data,
716 controller,
717 ..
718 } = bdata;
719
720 if let Some(target_info) = agent.target {
721 let target = target_info.target;
722
723 if let Some(target_pos) = read_data.positions.get(target) {
724 if agent_data.detects_other(
725 agent,
726 controller,
727 &target,
728 target_pos,
729 read_data.scales.get(target),
730 read_data,
731 ) {
732 let updated_pos = Some(target_pos.0);
733
734 let Target {
735 hostile,
736 selected_at,
737 aggro_on,
738 ..
739 } = target_info;
740
741 agent.target = Some(Target::new(
742 target,
743 hostile,
744 selected_at,
745 aggro_on,
746 updated_pos,
747 ));
748 }
749 }
750 }
751
752 false
753}
754
755fn heal_self_if_hurt(bdata: &mut BehaviorData) -> bool {
757 if bdata.agent_data.char_state.can_interact()
758 && bdata.agent_data.damage < HEALING_ITEM_THRESHOLD
759 && bdata
760 .agent_data
761 .heal_self(bdata.agent, bdata.controller, false)
762 {
763 bdata.agent.behavior_state.timers
764 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.01;
765 return true;
766 }
767 false
768}
769
770fn hurt_utterance(bdata: &mut BehaviorData) -> bool {
772 if matches!(bdata.agent.inbox.front(), Some(AgentEvent::Hurt)) {
773 if bdata.rng.gen::<f32>() < 0.4 {
774 bdata.controller.push_utterance(UtteranceKind::Hurt);
775 }
776 bdata.agent.inbox.pop_front();
777 }
778 false
779}
780
781fn update_target_awareness(bdata: &mut BehaviorData) -> bool {
782 let BehaviorData {
783 agent,
784 agent_data,
785 read_data,
786 controller,
787 ..
788 } = bdata;
789
790 let target = agent.target.map(|t| t.target);
791 let tgt_pos = target.and_then(|t| read_data.positions.get(t));
792 let tgt_scale = target.and_then(|t| read_data.scales.get(t));
793
794 if let (Some(target), Some(tgt_pos)) = (target, tgt_pos) {
795 if agent_data.can_see_entity(agent, controller, target, tgt_pos, tgt_scale, read_data) {
796 agent.awareness.change_by(1.75 * read_data.dt.0);
797 } else if agent_data.can_sense_directly_near(tgt_pos) {
798 agent.awareness.change_by(0.25);
799 } else {
800 agent
801 .awareness
802 .change_by(STD_AWARENESS_DECAY_RATE * read_data.dt.0);
803 }
804 } else {
805 agent
806 .awareness
807 .change_by(STD_AWARENESS_DECAY_RATE * read_data.dt.0);
808 }
809
810 if bdata.agent.awareness.state() == AwarenessState::Unaware
811 && !bdata.agent.behavior.is(BehaviorState::TRADING)
812 {
813 bdata.agent.target = None;
814 }
815
816 false
817}
818
819fn search_last_known_pos_if_not_alert(bdata: &mut BehaviorData) -> bool {
820 let awareness = &bdata.agent.awareness;
821 if awareness.reached() || awareness.state() < AwarenessState::Low {
822 return false;
823 }
824
825 let BehaviorData {
826 agent,
827 agent_data,
828 controller,
829 read_data,
830 ..
831 } = bdata;
832
833 if let Some(target) = agent.target {
834 if let Some(last_known_pos) = target.last_known_pos {
835 agent_data.follow(agent, controller, read_data, &Pos(last_known_pos));
836
837 return true;
838 }
839 }
840
841 false
842}
843
844fn do_combat(bdata: &mut BehaviorData) -> bool {
845 let BehaviorData {
846 agent,
847 agent_data,
848 read_data,
849 emitters,
850 controller,
851 rng,
852 } = bdata;
853
854 if let Some(Target {
855 target,
856 selected_at,
857 aggro_on,
858 ..
859 }) = &mut agent.target
860 {
861 let target = *target;
862 let selected_at = *selected_at;
863 if let Some(tgt_pos) = read_data.positions.get(target) {
864 let dist_sqrd = agent_data.pos.0.distance_squared(tgt_pos.0);
865 let origin_dist_sqrd = match agent.patrol_origin {
866 Some(pos) => pos.distance_squared(agent_data.pos.0),
867 None => 1.0,
868 };
869
870 let own_health_fraction = match agent_data.health {
871 Some(val) => val.fraction(),
872 None => 1.0,
873 };
874 let target_health_fraction = match read_data.healths.get(target) {
875 Some(val) => val.fraction(),
876 None => 1.0,
877 };
878 let in_aggro_range = agent
879 .psyche
880 .aggro_dist
881 .is_none_or(|ad| dist_sqrd < (ad * agent.psyche.aggro_range_multiplier).powi(2));
882
883 if in_aggro_range {
884 *aggro_on = true;
885 }
886 let aggro_on = *aggro_on;
887
888 let (flee, flee_dur_mul) = match agent_data.char_state {
889 CharacterState::Crawl => {
890 controller.push_action(ControlAction::Stand);
891
892 if let Some(interactors) = read_data.interactors.get(*agent_data.entity)
894 && interactors.has_interaction(InteractionKind::HelpDowned)
895 {
896 return true;
897 }
898
899 (true, 5.0)
900 },
901 _ => (agent_data.below_flee_health(agent), 1.0),
902 };
903
904 if flee {
905 let flee_timer_done = agent.behavior_state.timers
906 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize]
907 > FLEE_DURATION * flee_dur_mul;
908 let within_normal_flee_dir_dist = dist_sqrd < NORMAL_FLEE_DIR_DIST.powi(2);
909
910 if agent.behavior_state.timers
912 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize]
913 == 0.0
914 {
915 agent_data.cry_out(agent, emitters, read_data);
916 agent.behavior_state.timers
917 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.01;
918 agent.flee_from_pos = {
919 let random = || thread_rng().gen_range(-1.0..1.0);
920 Some(Pos(
921 agent_data.pos.0 + Vec3::new(random(), random(), random())
922 ))
923 };
924 } else if !flee_timer_done {
925 if within_normal_flee_dir_dist {
926 agent_data.flee(agent, controller, read_data, tgt_pos);
927 } else if let Some(random_pos) = agent.flee_from_pos {
928 agent_data.flee(agent, controller, read_data, &random_pos);
929 } else {
930 agent_data.flee(agent, controller, read_data, tgt_pos);
931 }
932
933 agent.behavior_state.timers
934 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] +=
935 read_data.dt.0;
936 } else {
937 agent.behavior_state.timers
938 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.0;
939 agent.target = None;
940 agent.flee_from_pos = None;
941 agent_data.idle(agent, controller, read_data, emitters, rng);
942 }
943 } else if is_dead(target, read_data) {
944 agent_data.exclaim_relief_about_enemy_dead(agent, emitters);
945 agent.target = None;
946 agent_data.idle(agent, controller, read_data, emitters, rng);
947 } else if is_invulnerable(target, read_data)
948 || stop_pursuing(
949 dist_sqrd,
950 origin_dist_sqrd,
951 own_health_fraction,
952 target_health_fraction,
953 read_data.time.0 - selected_at,
954 &agent.psyche,
955 )
956 {
957 agent.target = None;
958 agent_data.idle(agent, controller, read_data, emitters, rng);
959 } else {
960 let is_time_to_retarget =
961 read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS;
962
963 if (!agent.psyche.should_stop_pursuing || !in_aggro_range) && is_time_to_retarget {
964 agent_data.choose_target(agent, controller, read_data, AgentData::is_enemy);
965 }
966
967 if aggro_on {
968 let target_data = TargetData::new(tgt_pos, target, read_data);
969 agent_data.attack(agent, controller, &target_data, read_data, rng);
975 } else {
976 agent_data.menacing(
977 agent,
978 controller,
979 target,
980 read_data,
981 emitters,
982 rng,
983 remembers_fight_with(agent_data.rtsim_entity, read_data, target),
984 );
985 }
989 }
990 }
991 if !agent.psyche.should_stop_pursuing {
993 bdata.agent.awareness.set_maximally_aware();
994 }
995 }
996 false
997}
998
999fn remembers_fight_with(
1000 _rtsim_entity: Option<&RtSimEntity>,
1001 _read_data: &ReadData,
1002 _other: EcsEntity,
1003) -> bool {
1004 false
1013}
1014
1015