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
359 .read_data
360 .healths
361 .get(target)
362 .is_none_or(|tgt_health| tgt_health.is_dead)
363 {
364 bdata.agent.target = None;
370 return true;
371 }
372 }
373 false
374}
375
376fn do_hostile_tree_if_hostile_and_aware(bdata: &mut BehaviorData) -> bool {
379 let alert = bdata.agent.awareness.reached();
380
381 if let Some(Target { hostile, .. }) = bdata.agent.target {
382 if alert && hostile {
383 BehaviorTree::hostile().run(bdata);
384 return true;
385 }
386 }
387 false
388}
389
390fn do_pet_tree_if_owned(bdata: &mut BehaviorData) -> bool {
392 if let (Some(Target { target, .. }), Some(Alignment::Owned(uid))) =
393 (bdata.agent.target, bdata.agent_data.alignment)
394 {
395 if bdata.read_data.uids.get(target) == Some(uid) {
396 BehaviorTree::pet().run(bdata);
397 } else {
398 bdata.agent.target = None;
399 BehaviorTree::idle().run(bdata);
400 }
401 return true;
402 }
403 false
404}
405
406fn do_pickup_loot(bdata: &mut BehaviorData) -> bool {
408 if let Some(Target { target, .. }) = bdata.agent.target {
409 if let Some(Body::Item(body)) = bdata.read_data.bodies.get(target) {
410 if !matches!(body, body::item::Body::Thrown(_)) {
411 if let Some(tgt_pos) = bdata.read_data.positions.get(target) {
412 let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0);
413 if dist_sqrd < NPC_PICKUP_RANGE.powi(2) {
414 if let Some(uid) = bdata.read_data.uids.get(target) {
415 bdata.controller.push_event(ControlEvent::InventoryEvent(
416 InventoryEvent::Pickup(*uid),
417 ));
418 }
419 bdata.agent.target = None;
420 } else if let Some((bearing, speed)) = bdata.agent.chaser.chase(
421 &*bdata.read_data.terrain,
422 bdata.agent_data.pos.0,
423 bdata.agent_data.vel.0,
424 tgt_pos.0,
425 TraversalConfig {
426 min_tgt_dist: NPC_PICKUP_RANGE - 1.0,
427 ..bdata.agent_data.traversal_config
428 },
429 ) {
430 bdata.controller.inputs.move_dir = bearing
431 .xy()
432 .try_normalized()
433 .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 }
443 false
444}
445
446fn do_save_allies(bdata: &mut BehaviorData) -> bool {
448 if let Some(Target {
449 target,
450 hostile: false,
451 aggro_on: false,
452 ..
453 }) = bdata.agent.target
454 && let Some(target_uid) = bdata.read_data.uids.get(target)
455 {
456 let needs_saving = is_downed(
457 bdata.read_data.healths.get(target),
458 bdata.read_data.char_states.get(target),
459 );
460
461 let wants_to_save = match (bdata.agent_data.alignment, bdata.read_data.alignments.get(target)) {
462 (Some(Alignment::Npc), _) if bdata.read_data.presences.get(target).is_some_and(|presence| matches!(presence.kind, PresenceKind::Character(_))) => true,
465 (Some(Alignment::Npc), Some(Alignment::Npc)) => true,
466 (Some(Alignment::Enemy), Some(Alignment::Enemy)) => true,
467 _ => false,
468 } && bdata.agent.allowed_to_speak()
469 && bdata.read_data
471 .interactors
472 .get(target).is_none_or(|interactors| {
473 !interactors.has_interaction(InteractionKind::HelpDowned)
474 }) && bdata.agent_data.char_state.can_interact();
475
476 if needs_saving
477 && wants_to_save
478 && let Some(target_pos) = bdata.read_data.positions.get(target)
479 {
480 let dist_sqr = bdata.agent_data.pos.0.distance_squared(target_pos.0);
481 if dist_sqr < (MAX_INTERACT_RANGE * 0.5).powi(2) {
482 bdata.controller.push_event(ControlEvent::InteractWith {
483 target: *target_uid,
484 kind: common::interaction::InteractionKind::HelpDowned,
485 });
486 bdata.agent.target = None;
487 } else if let Some((bearing, speed)) = bdata.agent.chaser.chase(
488 &*bdata.read_data.terrain,
489 bdata.agent_data.pos.0,
490 bdata.agent_data.vel.0,
491 target_pos.0,
492 TraversalConfig {
493 min_tgt_dist: MAX_INTERACT_RANGE * 0.5,
494 ..bdata.agent_data.traversal_config
495 },
496 ) {
497 bdata.controller.inputs.move_dir =
498 bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
499 * speed
500 .min(0.2 + (dist_sqr - (MAX_INTERACT_RANGE * 0.5 - 0.5).powi(2)) / 8.0);
501 bdata.agent_data.jump_if(bearing.z > 1.5, bdata.controller);
502 bdata.controller.inputs.move_z = bearing.z;
503 }
504 return true;
505 }
506 }
507 false
508}
509
510fn follow_if_far_away(bdata: &mut BehaviorData) -> bool {
512 if let Some(Target { target, .. }) = bdata.agent.target {
513 if let Some(tgt_pos) = bdata.read_data.positions.get(target) {
514 if let Some(stay_pos) = bdata.agent.stay_pos {
515 let distance_from_stay = stay_pos.0.distance_squared(bdata.agent_data.pos.0);
516 bdata.controller.push_action(ControlAction::Sit);
517 if distance_from_stay > (MAX_STAY_DISTANCE).powi(2) {
518 bdata.agent_data.follow(
519 bdata.agent,
520 bdata.controller,
521 bdata.read_data,
522 &stay_pos,
523 );
524 return true;
525 }
526 } else {
527 bdata.controller.push_action(ControlAction::Stand);
528 let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0);
529 if dist_sqrd > (MAX_PATROL_DIST * bdata.agent.psyche.idle_wander_factor).powi(2) {
530 bdata.agent_data.follow(
531 bdata.agent,
532 bdata.controller,
533 bdata.read_data,
534 tgt_pos,
535 );
536 return true;
537 }
538 }
539 }
540 }
541 false
542}
543
544fn attack_if_owner_hurt(bdata: &mut BehaviorData) -> bool {
547 if let Some(Target { target, .. }) = bdata.agent.target {
548 if bdata.read_data.positions.get(target).is_some() {
549 let owner_recently_attacked =
550 if let Some(target_health) = bdata.read_data.healths.get(target) {
551 bdata.read_data.time.0 - target_health.last_change.time.0 < 5.0
552 && target_health.last_change.amount < 0.0
553 } else {
554 false
555 };
556 let stay = bdata.agent.stay_pos.is_some();
557 if owner_recently_attacked && !stay {
558 bdata.agent_data.attack_target_attacker(
559 bdata.agent,
560 bdata.read_data,
561 bdata.controller,
562 bdata.emitters,
563 bdata.rng,
564 );
565 return true;
566 }
567 }
568 }
569 false
570}
571
572fn set_owner_if_no_target(bdata: &mut BehaviorData) -> bool {
574 let small_chance = bdata.rng.gen_bool(0.1);
575
576 if bdata.agent.target.is_none() && small_chance {
577 if let Some(Alignment::Owned(owner)) = bdata.agent_data.alignment {
578 if let Some(owner) = get_entity_by_id(*owner, bdata.read_data) {
579 let owner_pos = bdata.read_data.positions.get(owner).map(|pos| pos.0);
580
581 bdata.agent.target = Some(Target::new(
582 owner,
583 false,
584 bdata.read_data.time.0,
585 false,
586 owner_pos,
587 ));
588 bdata.agent.awareness.set_maximally_aware();
590 }
591 }
592 }
593 false
594}
595
596fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
598 if let Some(action) = bdata.agent.rtsim_controller.actions.pop_front() {
599 match action {
600 NpcAction::Say(target, msg) => {
601 if bdata.agent.allowed_to_speak() {
602 if let Some(target) =
604 target.and_then(|tgt| bdata.read_data.id_maps.actor_entity(tgt))
605 {
606 bdata.agent.target = Some(Target::new(
607 target,
608 false,
609 bdata.read_data.time.0,
610 false,
611 bdata.read_data.positions.get(target).map(|p| p.0),
612 ));
613 bdata.agent.awareness.set_maximally_aware();
615 bdata
617 .agent
618 .timer
619 .start(bdata.read_data.time.0, TimerAction::Interact);
620 bdata.controller.push_action(ControlAction::Stand);
621
622 if let Some(target_uid) = bdata.read_data.uids.get(target) {
623 bdata
624 .controller
625 .push_event(ControlEvent::Interact(*target_uid));
626 }
627 }
628 bdata.controller.push_utterance(UtteranceKind::Greeting);
629 bdata.agent_data.chat_npc(msg, bdata.emitters);
630 }
631 },
632 NpcAction::Attack(target) => {
633 if let Some(target) = bdata.read_data.id_maps.actor_entity(target) {
634 bdata.agent.target = Some(Target::new(
635 target,
636 true,
637 bdata.read_data.time.0,
638 false,
639 bdata.read_data.positions.get(target).map(|p| p.0),
640 ));
641 bdata.agent.awareness.set_maximally_aware();
642 }
643 },
644 NpcAction::Dialogue(target, dialogue) => {
645 if let Some(target) = bdata.read_data.id_maps.actor_entity(target)
646 && let Some(target_uid) = bdata.read_data.uids.get(target)
647 {
648 bdata
649 .controller
650 .push_event(ControlEvent::Dialogue(*target_uid, dialogue));
651 bdata.controller.push_utterance(UtteranceKind::Greeting);
652 } else {
653 warn!("NPC dialogue sent to non-existent target entity");
654 }
655 },
656 }
657 true
658 } else {
659 false
660 }
661}
662
663fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
665 let timeout = if bdata.agent.behavior.is(BehaviorState::TRADING) {
666 TRADE_INTERACTION_TIME
667 } else {
668 DEFAULT_INTERACTION_TIME
669 };
670
671 match bdata.agent.timer.timeout_elapsed(
672 bdata.read_data.time.0,
673 TimerAction::Interact,
674 timeout as f64,
675 ) {
676 None => {
677 if let Some(Target { target, .. }) = &bdata.agent.target {
679 bdata
680 .agent_data
681 .look_toward(bdata.controller, bdata.read_data, *target);
682 bdata.controller.push_action(ControlAction::Talk);
683 }
684 },
685 Some(just_ended) => {
686 if just_ended {
687 bdata.agent.target = None;
688 bdata.controller.push_action(ControlAction::Stand);
689 }
690
691 if bdata.rng.gen::<f32>() < 0.1 {
692 bdata.agent_data.choose_target(
693 bdata.agent,
694 bdata.controller,
695 bdata.read_data,
696 AgentData::is_enemy,
697 );
698 } else {
699 bdata.agent_data.handle_sounds_heard(
700 bdata.agent,
701 bdata.controller,
702 bdata.read_data,
703 bdata.emitters,
704 bdata.rng,
705 );
706 }
707 },
708 }
709 false
710}
711
712fn update_last_known_pos(bdata: &mut BehaviorData) -> bool {
713 let BehaviorData {
714 agent,
715 agent_data,
716 read_data,
717 controller,
718 ..
719 } = bdata;
720
721 if let Some(target_info) = agent.target {
722 let target = target_info.target;
723
724 if let Some(target_pos) = read_data.positions.get(target) {
725 if agent_data.detects_other(
726 agent,
727 controller,
728 &target,
729 target_pos,
730 read_data.scales.get(target),
731 read_data,
732 ) {
733 let updated_pos = Some(target_pos.0);
734
735 let Target {
736 hostile,
737 selected_at,
738 aggro_on,
739 ..
740 } = target_info;
741
742 agent.target = Some(Target::new(
743 target,
744 hostile,
745 selected_at,
746 aggro_on,
747 updated_pos,
748 ));
749 }
750 }
751 }
752
753 false
754}
755
756fn heal_self_if_hurt(bdata: &mut BehaviorData) -> bool {
758 if bdata.agent_data.char_state.can_interact()
759 && bdata.agent_data.damage < HEALING_ITEM_THRESHOLD
760 && bdata
761 .agent_data
762 .heal_self(bdata.agent, bdata.controller, false)
763 {
764 bdata.agent.behavior_state.timers
765 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.01;
766 return true;
767 }
768 false
769}
770
771fn hurt_utterance(bdata: &mut BehaviorData) -> bool {
773 if matches!(bdata.agent.inbox.front(), Some(AgentEvent::Hurt)) {
774 if bdata.rng.gen::<f32>() < 0.4 {
775 bdata.controller.push_utterance(UtteranceKind::Hurt);
776 }
777 bdata.agent.inbox.pop_front();
778 }
779 false
780}
781
782fn update_target_awareness(bdata: &mut BehaviorData) -> bool {
783 let BehaviorData {
784 agent,
785 agent_data,
786 read_data,
787 controller,
788 ..
789 } = bdata;
790
791 let target = agent.target.map(|t| t.target);
792 let tgt_pos = target.and_then(|t| read_data.positions.get(t));
793 let tgt_scale = target.and_then(|t| read_data.scales.get(t));
794
795 if let (Some(target), Some(tgt_pos)) = (target, tgt_pos) {
796 if agent_data.can_see_entity(agent, controller, target, tgt_pos, tgt_scale, read_data) {
797 agent.awareness.change_by(1.75 * read_data.dt.0);
798 } else if agent_data.can_sense_directly_near(tgt_pos) {
799 agent.awareness.change_by(0.25);
800 } else {
801 agent
802 .awareness
803 .change_by(STD_AWARENESS_DECAY_RATE * read_data.dt.0);
804 }
805 } else {
806 agent
807 .awareness
808 .change_by(STD_AWARENESS_DECAY_RATE * read_data.dt.0);
809 }
810
811 if bdata.agent.awareness.state() == AwarenessState::Unaware
812 && !bdata.agent.behavior.is(BehaviorState::TRADING)
813 {
814 bdata.agent.target = None;
815 }
816
817 false
818}
819
820fn search_last_known_pos_if_not_alert(bdata: &mut BehaviorData) -> bool {
821 let awareness = &bdata.agent.awareness;
822 if awareness.reached() || awareness.state() < AwarenessState::Low {
823 return false;
824 }
825
826 let BehaviorData {
827 agent,
828 agent_data,
829 controller,
830 read_data,
831 ..
832 } = bdata;
833
834 if let Some(target) = agent.target {
835 if let Some(last_known_pos) = target.last_known_pos {
836 agent_data.follow(agent, controller, read_data, &Pos(last_known_pos));
837
838 return true;
839 }
840 }
841
842 false
843}
844
845fn do_combat(bdata: &mut BehaviorData) -> bool {
846 let BehaviorData {
847 agent,
848 agent_data,
849 read_data,
850 emitters,
851 controller,
852 rng,
853 } = bdata;
854
855 if let Some(Target {
856 target,
857 selected_at,
858 aggro_on,
859 ..
860 }) = &mut agent.target
861 {
862 let target = *target;
863 let selected_at = *selected_at;
864 if let Some(tgt_pos) = read_data.positions.get(target) {
865 let dist_sqrd = agent_data.pos.0.distance_squared(tgt_pos.0);
866 let origin_dist_sqrd = match agent.patrol_origin {
867 Some(pos) => pos.distance_squared(agent_data.pos.0),
868 None => 1.0,
869 };
870
871 let own_health_fraction = match agent_data.health {
872 Some(val) => val.fraction(),
873 None => 1.0,
874 };
875 let target_health_fraction = match read_data.healths.get(target) {
876 Some(val) => val.fraction(),
877 None => 1.0,
878 };
879 let in_aggro_range = agent
880 .psyche
881 .aggro_dist
882 .is_none_or(|ad| dist_sqrd < (ad * agent.psyche.aggro_range_multiplier).powi(2));
883
884 if in_aggro_range {
885 *aggro_on = true;
886 }
887 let aggro_on = *aggro_on;
888
889 let (flee, flee_dur_mul) = match agent_data.char_state {
890 CharacterState::Crawl => {
891 controller.push_action(ControlAction::Stand);
892
893 if let Some(interactors) = read_data.interactors.get(*agent_data.entity)
895 && interactors.has_interaction(InteractionKind::HelpDowned)
896 {
897 return true;
898 }
899
900 (true, 5.0)
901 },
902 _ => (agent_data.below_flee_health(agent), 1.0),
903 };
904
905 if flee {
906 let flee_timer_done = agent.behavior_state.timers
907 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize]
908 > FLEE_DURATION * flee_dur_mul;
909 let within_normal_flee_dir_dist = dist_sqrd < NORMAL_FLEE_DIR_DIST.powi(2);
910
911 if agent.behavior_state.timers
913 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize]
914 == 0.0
915 {
916 agent_data.cry_out(agent, emitters, read_data);
917 agent.behavior_state.timers
918 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.01;
919 agent.flee_from_pos = {
920 let random = || thread_rng().gen_range(-1.0..1.0);
921 Some(Pos(
922 agent_data.pos.0 + Vec3::new(random(), random(), random())
923 ))
924 };
925 } else if !flee_timer_done {
926 if within_normal_flee_dir_dist {
927 agent_data.flee(agent, controller, read_data, tgt_pos);
928 } else if let Some(random_pos) = agent.flee_from_pos {
929 agent_data.flee(agent, controller, read_data, &random_pos);
930 } else {
931 agent_data.flee(agent, controller, read_data, tgt_pos);
932 }
933
934 agent.behavior_state.timers
935 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] +=
936 read_data.dt.0;
937 } else {
938 agent.behavior_state.timers
939 [ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.0;
940 agent.target = None;
941 agent.flee_from_pos = None;
942 agent_data.idle(agent, controller, read_data, emitters, rng);
943 }
944 } else if is_dead(target, read_data) {
945 agent_data.exclaim_relief_about_enemy_dead(agent, emitters);
946 agent.target = None;
947 agent_data.idle(agent, controller, read_data, emitters, rng);
948 } else if is_invulnerable(target, read_data)
949 || stop_pursuing(
950 dist_sqrd,
951 origin_dist_sqrd,
952 own_health_fraction,
953 target_health_fraction,
954 read_data.time.0 - selected_at,
955 &agent.psyche,
956 )
957 {
958 agent.target = None;
959 agent_data.idle(agent, controller, read_data, emitters, rng);
960 } else {
961 let is_time_to_retarget =
962 read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS;
963
964 if (!agent.psyche.should_stop_pursuing || !in_aggro_range) && is_time_to_retarget {
965 agent_data.choose_target(agent, controller, read_data, AgentData::is_enemy);
966 }
967
968 if aggro_on {
969 let target_data = TargetData::new(tgt_pos, target, read_data);
970 agent_data.attack(agent, controller, &target_data, read_data, rng);
976 } else {
977 agent_data.menacing(
978 agent,
979 controller,
980 target,
981 read_data,
982 emitters,
983 rng,
984 remembers_fight_with(agent_data.rtsim_entity, read_data, target),
985 );
986 }
990 }
991 }
992 if !agent.psyche.should_stop_pursuing {
994 bdata.agent.awareness.set_maximally_aware();
995 }
996 }
997 false
998}
999
1000fn remembers_fight_with(
1001 _rtsim_entity: Option<&RtSimEntity>,
1002 _read_data: &ReadData,
1003 _other: EcsEntity,
1004) -> bool {
1005 false
1014}
1015
1016