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