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