1use crate::{
2 comp::{
3 Body, UtteranceKind, biped_large, biped_small, bird_medium, humanoid, object,
4 quadruped_low, quadruped_medium, quadruped_small, ship,
5 },
6 path::Chaser,
7 rtsim::{self, NpcInput, RtSimController},
8 trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
9 uid::Uid,
10};
11use serde::{Deserialize, Serialize};
12use specs::{Component, DerefFlaggedStorage, Entity as EcsEntity};
13use std::{collections::VecDeque, fmt};
14use strum::{EnumIter, IntoEnumIterator};
15use vek::*;
16
17use super::{Group, Pos};
18
19pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
20pub const TRADE_INTERACTION_TIME: f32 = 300.0;
21const SECONDS_BEFORE_FORGET_SOUNDS: f64 = 180.0;
22
23const ACTIONSTATE_NUMBER_OF_CONCURRENT_TIMERS: usize = 5;
32const ACTIONSTATE_NUMBER_OF_CONCURRENT_COUNTERS: usize = 5;
36const ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS: usize = 5;
40const ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS: usize = 5;
43const ACTIONSTATE_NUMBER_OF_CONCURRENT_POSITIONS: usize = 5;
45
46#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub enum Alignment {
48 Wild,
50 Enemy,
52 Npc,
54 Tame,
56 Owned(Uid),
58 Passive,
60}
61
62#[derive(Copy, Clone, Debug, PartialEq, Eq)]
63pub enum Mark {
64 Merchant,
65 Guard,
66}
67
68impl Alignment {
69 pub fn hostile_towards(self, other: Alignment) -> bool {
71 match (self, other) {
72 (Alignment::Passive, _) => false,
73 (_, Alignment::Passive) => false,
74 (Alignment::Enemy, Alignment::Enemy) => false,
75 (Alignment::Enemy, Alignment::Wild) => false,
76 (Alignment::Wild, Alignment::Enemy) => false,
77 (Alignment::Wild, Alignment::Wild) => false,
78 (Alignment::Npc, Alignment::Wild) => false,
79 (Alignment::Npc, Alignment::Enemy) => true,
80 (_, Alignment::Enemy) => true,
81 (Alignment::Enemy, _) => true,
82 _ => false,
83 }
84 }
85
86 pub fn passive_towards(self, other: Alignment) -> bool {
88 match (self, other) {
89 (Alignment::Enemy, Alignment::Enemy) => true,
90 (Alignment::Owned(a), Alignment::Owned(b)) if a == b => true,
91 (Alignment::Npc, Alignment::Npc) => true,
92 (Alignment::Npc, Alignment::Tame) => true,
93 (Alignment::Enemy, Alignment::Wild) => true,
94 (Alignment::Wild, Alignment::Enemy) => true,
95 (Alignment::Tame, Alignment::Npc) => true,
96 (Alignment::Tame, Alignment::Tame) => true,
97 (_, Alignment::Passive) => true,
98 _ => false,
99 }
100 }
101
102 pub fn friendly_towards(self, other: Alignment) -> bool {
104 match (self, other) {
105 (Alignment::Enemy, Alignment::Enemy) => true,
106 (Alignment::Owned(a), Alignment::Owned(b)) if a == b => true,
107 (Alignment::Npc, Alignment::Npc) => true,
108 (Alignment::Npc, Alignment::Tame) => true,
109 (Alignment::Tame, Alignment::Npc) => true,
110 (Alignment::Tame, Alignment::Tame) => true,
111 (_, Alignment::Passive) => true,
112 _ => false,
113 }
114 }
115
116 pub fn group(&self) -> Option<Group> {
119 match self {
120 Alignment::Wild => None,
121 Alignment::Passive => None,
122 Alignment::Enemy => Some(super::group::ENEMY),
123 Alignment::Npc | Alignment::Tame => Some(super::group::NPC),
124 Alignment::Owned(_) => None,
125 }
126 }
127}
128
129impl Component for Alignment {
130 type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
131}
132
133bitflags::bitflags! {
134 #[derive(Clone, Copy, Debug, Default)]
135 pub struct BehaviorCapability: u8 {
136 const SPEAK = 0b00000001;
137 const TRADE = 0b00000010;
138 }
139}
140bitflags::bitflags! {
141 #[derive(Clone, Copy, Debug, Default)]
142 pub struct BehaviorState: u8 {
143 const TRADING = 0b00000001;
144 const TRADING_ISSUER = 0b00000010;
145 }
146}
147
148#[derive(Default, Copy, Clone, Debug)]
149pub enum TradingBehavior {
150 #[default]
151 None,
152 RequireBalanced {
153 trade_site: SiteId,
154 },
155 AcceptFood,
156}
157
158impl TradingBehavior {
159 fn can_trade(&self, alignment: Option<Alignment>, counterparty: Uid) -> bool {
160 match self {
161 TradingBehavior::RequireBalanced { .. } => true,
162 TradingBehavior::AcceptFood => alignment == Some(Alignment::Owned(counterparty)),
163 TradingBehavior::None => false,
164 }
165 }
166}
167
168#[derive(Default, Copy, Clone, Debug)]
174pub struct Behavior {
175 capabilities: BehaviorCapability,
176 state: BehaviorState,
177 pub trading_behavior: TradingBehavior,
178}
179
180impl From<BehaviorCapability> for Behavior {
181 fn from(capabilities: BehaviorCapability) -> Self {
182 Behavior {
183 capabilities,
184 state: BehaviorState::default(),
185 trading_behavior: TradingBehavior::None,
186 }
187 }
188}
189
190impl Behavior {
191 #[must_use]
194 pub fn maybe_with_capabilities(
195 mut self,
196 maybe_capabilities: Option<BehaviorCapability>,
197 ) -> Self {
198 if let Some(capabilities) = maybe_capabilities {
199 self.allow(capabilities)
200 }
201 self
202 }
203
204 #[must_use]
207 pub fn with_trade_site(mut self, trade_site: Option<SiteId>) -> Self {
208 if let Some(trade_site) = trade_site {
209 self.trading_behavior = TradingBehavior::RequireBalanced { trade_site };
210 }
211 self
212 }
213
214 pub fn allow(&mut self, capabilities: BehaviorCapability) {
216 self.capabilities.set(capabilities, true)
217 }
218
219 pub fn deny(&mut self, capabilities: BehaviorCapability) {
221 self.capabilities.set(capabilities, false)
222 }
223
224 pub fn can(&self, capabilities: BehaviorCapability) -> bool {
226 self.capabilities.contains(capabilities)
227 }
228
229 pub fn can_trade(&self, alignment: Option<Alignment>, counterparty: Uid) -> bool {
231 self.trading_behavior.can_trade(alignment, counterparty)
232 }
233
234 pub fn set(&mut self, state: BehaviorState) { self.state.set(state, true) }
236
237 pub fn unset(&mut self, state: BehaviorState) { self.state.set(state, false) }
239
240 pub fn is(&self, state: BehaviorState) -> bool { self.state.contains(state) }
242
243 pub fn trade_site(&self) -> Option<SiteId> {
245 if let TradingBehavior::RequireBalanced { trade_site } = self.trading_behavior {
246 Some(trade_site)
247 } else {
248 None
249 }
250 }
251}
252
253#[derive(Clone, Debug, Default)]
254pub struct Psyche {
255 pub flee_health: f32,
258 pub sight_dist: f32,
261 pub listen_dist: f32,
263 pub aggro_dist: Option<f32>,
267 pub idle_wander_factor: f32,
270 pub aggro_range_multiplier: f32,
277 pub should_stop_pursuing: bool,
281}
282
283impl<'a> From<&'a Body> for Psyche {
284 fn from(body: &'a Body) -> Self {
285 Self {
286 flee_health: match body {
287 Body::Humanoid(humanoid) => match humanoid.species {
288 humanoid::Species::Danari => 0.4,
289 humanoid::Species::Dwarf => 0.3,
290 humanoid::Species::Elf => 0.4,
291 humanoid::Species::Human => 0.4,
292 humanoid::Species::Orc => 0.3,
293 humanoid::Species::Draugr => 0.3,
294 },
295 Body::QuadrupedSmall(quadruped_small) => match quadruped_small.species {
296 quadruped_small::Species::Pig => 0.5,
297 quadruped_small::Species::Fox => 0.3,
298 quadruped_small::Species::Sheep => 0.25,
299 quadruped_small::Species::Boar => 0.1,
300 quadruped_small::Species::Skunk => 0.4,
301 quadruped_small::Species::Cat => 0.99,
302 quadruped_small::Species::Batfox => 0.1,
303 quadruped_small::Species::Raccoon => 0.4,
304 quadruped_small::Species::Hyena => 0.1,
305 quadruped_small::Species::Dog => 0.8,
306 quadruped_small::Species::Rabbit | quadruped_small::Species::Jackalope => 0.25,
307 quadruped_small::Species::Truffler => 0.08,
308 quadruped_small::Species::Hare => 0.3,
309 quadruped_small::Species::Goat => 0.3,
310 quadruped_small::Species::Porcupine => 0.2,
311 quadruped_small::Species::Turtle => 0.4,
312 quadruped_small::Species::Beaver => 0.2,
313 quadruped_small::Species::Rat
316 | quadruped_small::Species::TreantSapling
317 | quadruped_small::Species::Holladon
318 | quadruped_small::Species::MossySnail => 0.0,
319 _ => 1.0,
320 },
321 Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
322 quadruped_medium::Species::Antelope => 0.15,
324 quadruped_medium::Species::Donkey => 0.05,
325 quadruped_medium::Species::Horse => 0.15,
326 quadruped_medium::Species::Mouflon => 0.1,
327 quadruped_medium::Species::Zebra => 0.1,
328 quadruped_medium::Species::Barghest
332 | quadruped_medium::Species::Bear
333 | quadruped_medium::Species::Bristleback
334 | quadruped_medium::Species::Bonerattler => 0.0,
335 quadruped_medium::Species::Cattle => 0.1,
336 quadruped_medium::Species::Frostfang => 0.07,
337 quadruped_medium::Species::Grolgar => 0.0,
338 quadruped_medium::Species::Highland => 0.05,
339 quadruped_medium::Species::Kelpie => 0.35,
340 quadruped_medium::Species::Lion => 0.0,
341 quadruped_medium::Species::Moose => 0.15,
342 quadruped_medium::Species::Panda => 0.35,
343 quadruped_medium::Species::Saber
344 | quadruped_medium::Species::Tarasque
345 | quadruped_medium::Species::Tiger => 0.0,
346 quadruped_medium::Species::Tuskram => 0.1,
347 quadruped_medium::Species::Wolf => 0.2,
348 quadruped_medium::Species::Yak => 0.09,
349 quadruped_medium::Species::Akhlut
351 | quadruped_medium::Species::Catoblepas
352 | quadruped_medium::Species::ClaySteed
353 | quadruped_medium::Species::Dreadhorn
354 | quadruped_medium::Species::Hirdrasil
355 | quadruped_medium::Species::Mammoth
356 | quadruped_medium::Species::Ngoubou
357 | quadruped_medium::Species::Roshwalr => 0.0,
358 _ => 0.15,
359 },
360 Body::QuadrupedLow(quadruped_low) => match quadruped_low.species {
361 quadruped_low::Species::Pangolin => 0.3,
363 quadruped_low::Species::Tortoise => 0.1,
365 _ => 0.0,
367 },
368 Body::BipedSmall(biped_small) => match biped_small.species {
369 biped_small::Species::Gnarling => 0.2,
370 biped_small::Species::Mandragora => 0.1,
371 biped_small::Species::Adlet => 0.2,
372 biped_small::Species::Haniwa => 0.1,
373 biped_small::Species::Sahagin => 0.1,
374 biped_small::Species::Myrmidon => 0.0,
375 biped_small::Species::TreasureEgg => 9.9,
376 biped_small::Species::Husk
377 | biped_small::Species::Boreal
378 | biped_small::Species::IronDwarf
379 | biped_small::Species::Flamekeeper
380 | biped_small::Species::Irrwurz
381 | biped_small::Species::GoblinThug
382 | biped_small::Species::GoblinChucker
383 | biped_small::Species::GoblinRuffian
384 | biped_small::Species::GreenLegoom
385 | biped_small::Species::OchreLegoom
386 | biped_small::Species::PurpleLegoom
387 | biped_small::Species::RedLegoom
388 | biped_small::Species::ShamanicSpirit
389 | biped_small::Species::Jiangshi
390 | biped_small::Species::Bushly
391 | biped_small::Species::Cactid
392 | biped_small::Species::BloodmoonHeiress
393 | biped_small::Species::Bloodservant
394 | biped_small::Species::Harlequin
395 | biped_small::Species::GnarlingChieftain => 0.0,
396
397 _ => 0.5,
398 },
399 Body::BirdMedium(bird_medium) => match bird_medium.species {
400 bird_medium::Species::SnowyOwl => 0.4,
401 bird_medium::Species::HornedOwl => 0.4,
402 bird_medium::Species::Duck => 0.6,
403 bird_medium::Species::Cockatiel => 0.6,
404 bird_medium::Species::Chicken => 0.5,
405 bird_medium::Species::Bat => 0.1,
406 bird_medium::Species::Penguin => 0.5,
407 bird_medium::Species::Goose => 0.4,
408 bird_medium::Species::Peacock => 0.3,
409 bird_medium::Species::Eagle => 0.2,
410 bird_medium::Species::Parrot => 0.8,
411 bird_medium::Species::Crow => 0.4,
412 bird_medium::Species::Dodo => 0.8,
413 bird_medium::Species::Parakeet => 0.8,
414 bird_medium::Species::Puffin => 0.8,
415 bird_medium::Species::Toucan => 0.4,
416 bird_medium::Species::BloodmoonBat => 0.0,
417 bird_medium::Species::VampireBat => 0.0,
418 },
419 Body::BirdLarge(_) => 0.0,
420 Body::FishSmall(_) => 1.0,
421 Body::FishMedium(_) => 0.75,
422 Body::BipedLarge(_) => 0.0,
423 Body::Object(_) => 0.0,
424 Body::Item(_) => 0.0,
425 Body::Golem(_) => 0.0,
426 Body::Theropod(_) => 0.0,
427 Body::Ship(_) => 0.0,
428 Body::Dragon(_) => 0.0,
429 Body::Arthropod(_) => 0.0,
430 Body::Crustacean(_) => 0.0,
431 Body::Plugin(body) => body.flee_health(),
432 },
433 sight_dist: match body {
434 Body::BirdLarge(_) => 250.0,
435 Body::BipedLarge(biped_large) => match biped_large.species {
436 biped_large::Species::Gigasfrost | biped_large::Species::Gigasfire => 200.0,
437 _ => 100.0,
438 },
439 _ => 40.0,
440 },
441 listen_dist: 30.0,
442 aggro_dist: match body {
443 Body::Humanoid(_) => Some(20.0),
444 _ => None, },
446 idle_wander_factor: 1.0,
447 aggro_range_multiplier: 1.0,
448 should_stop_pursuing: match body {
449 Body::BirdLarge(_) => false,
450 Body::BipedLarge(biped_large) => !matches!(
451 biped_large.species,
452 biped_large::Species::Gigasfrost | biped_large::Species::Gigasfire
453 ),
454 Body::BirdMedium(bird_medium) => {
455 !matches!(bird_medium.species, bird_medium::Species::BloodmoonBat)
458 },
459 _ => true,
460 },
461 }
462 }
463}
464
465impl Psyche {
466 pub fn search_dist(&self) -> f32 {
469 self.sight_dist.max(self.listen_dist) * self.aggro_range_multiplier
470 }
471}
472
473#[derive(Clone, Debug)]
474pub enum AgentEvent {
476 Talk(Uid),
478 TradeInvite(Uid),
479 TradeAccepted(Uid),
480 FinishedTrade(TradeResult),
481 UpdatePendingTrade(
482 Box<(
484 TradeId,
485 PendingTrade,
486 SitePrices,
487 [Option<ReducedInventory>; 2],
488 )>,
489 ),
490 ServerSound(Sound),
491 Hurt,
492 Dialogue(Uid, rtsim::Dialogue<true>),
493}
494
495#[derive(Copy, Clone, Debug)]
496pub struct Sound {
497 pub kind: SoundKind,
498 pub pos: Vec3<f32>,
499 pub vol: f32,
500 pub time: f64,
501}
502
503impl Sound {
504 pub fn new(kind: SoundKind, pos: Vec3<f32>, vol: f32, time: f64) -> Self {
505 Sound {
506 kind,
507 pos,
508 vol,
509 time,
510 }
511 }
512
513 #[must_use]
514 pub fn with_new_vol(mut self, new_vol: f32) -> Self {
515 self.vol = new_vol;
516
517 self
518 }
519}
520
521#[derive(Copy, Clone, Debug)]
522pub enum SoundKind {
523 Unknown,
524 Utterance(UtteranceKind, Body),
525 Movement,
526 Melee,
527 Projectile,
528 Explosion,
529 Beam,
530 Shockwave,
531 Mine,
532 Trap,
533}
534
535#[derive(Clone, Copy, Debug)]
536pub struct Target {
537 pub target: EcsEntity,
538 pub hostile: bool,
540 pub selected_at: f64,
542 pub aggro_on: bool,
544 pub last_known_pos: Option<Vec3<f32>>,
545}
546
547impl Target {
548 pub fn new(
549 target: EcsEntity,
550 hostile: bool,
551 selected_at: f64,
552 aggro_on: bool,
553 last_known_pos: Option<Vec3<f32>>,
554 ) -> Self {
555 Self {
556 target,
557 hostile,
558 selected_at,
559 aggro_on,
560 last_known_pos,
561 }
562 }
563}
564
565#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, EnumIter)]
566pub enum TimerAction {
567 Interact,
568}
569
570#[derive(Clone, Debug)]
575pub struct Timer {
576 action_starts: Vec<Option<f64>>,
577 last_action: Option<TimerAction>,
578}
579
580impl Default for Timer {
581 fn default() -> Self {
582 Self {
583 action_starts: TimerAction::iter().map(|_| None).collect(),
584 last_action: None,
585 }
586 }
587}
588
589impl Timer {
590 fn idx_for(action: TimerAction) -> usize {
591 TimerAction::iter()
592 .enumerate()
593 .find(|(_, a)| a == &action)
594 .unwrap()
595 .0 }
597
598 pub fn reset(&mut self, action: TimerAction) -> bool {
601 self.action_starts[Self::idx_for(action)].take().is_some()
602 }
603
604 pub fn start(&mut self, time: f64, action: TimerAction) {
606 self.action_starts[Self::idx_for(action)] = Some(time);
607 self.last_action = Some(action);
608 }
609
610 pub fn progress(&mut self, time: f64, action: TimerAction) {
613 if self.last_action != Some(action) {
614 self.start(time, action);
615 }
616 }
617
618 pub fn time_of_last(&self, action: TimerAction) -> Option<f64> {
620 self.action_starts[Self::idx_for(action)]
621 }
622
623 pub fn time_since_exceeds(&self, time: f64, action: TimerAction, timeout: f64) -> bool {
626 self.time_of_last(action)
627 .is_none_or(|last_time| (time - last_time).max(0.0) > timeout)
628 }
629
630 pub fn timeout_elapsed(
633 &mut self,
634 time: f64,
635 action: TimerAction,
636 timeout: f64,
637 ) -> Option<bool> {
638 if self.time_since_exceeds(time, action, timeout) {
639 Some(self.reset(action))
640 } else {
641 self.progress(time, action);
642 None
643 }
644 }
645}
646
647#[derive(Clone, Debug)]
649pub struct Agent {
650 pub rtsim_controller: RtSimController,
651 pub patrol_origin: Option<Vec3<f32>>,
652 pub target: Option<Target>,
653 pub chaser: Chaser,
654 pub behavior: Behavior,
655 pub psyche: Psyche,
656 pub inbox: VecDeque<AgentEvent>,
657 pub combat_state: ActionState,
658 pub behavior_state: ActionState,
659 pub timer: Timer,
660 pub bearing: Vec2<f32>,
661 pub sounds_heard: Vec<Sound>,
662 pub multi_pid_controllers: Option<PidControllers<16>>,
663 pub flee_from_pos: Option<Pos>,
667 pub awareness: Awareness,
668 pub stay_pos: Option<Pos>,
669 pub rtsim_outbox: Option<VecDeque<NpcInput>>,
671}
672
673#[derive(Serialize, Deserialize)]
674pub struct SharedChaser {
675 pub nodes: Vec<Vec3<f32>>,
676 pub goal: Option<Vec3<f32>>,
677}
678
679#[derive(Clone, Debug)]
680pub struct Awareness {
682 level: f32,
683 reached: bool,
684}
685impl fmt::Display for Awareness {
686 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.2}", self.level) }
687}
688impl Awareness {
689 const ALERT: f32 = 1.0;
690 const HIGH: f32 = 0.6;
691 const LOW: f32 = 0.1;
692 const MEDIUM: f32 = 0.3;
693 const UNAWARE: f32 = 0.0;
694
695 pub fn new(level: f32) -> Self {
696 Self {
697 level: level.clamp(Self::UNAWARE, Self::ALERT),
698 reached: false,
699 }
700 }
701
702 pub fn level(&self) -> f32 { self.level }
704
705 pub fn state(&self) -> AwarenessState {
708 if self.level == Self::ALERT {
709 AwarenessState::Alert
710 } else if self.level.is_between(Self::HIGH, Self::ALERT) {
711 AwarenessState::High
712 } else if self.level.is_between(Self::MEDIUM, Self::HIGH) {
713 AwarenessState::Medium
714 } else if self.level.is_between(Self::LOW, Self::MEDIUM) {
715 AwarenessState::Low
716 } else {
717 AwarenessState::Unaware
718 }
719 }
720
721 pub fn reached(&self) -> bool { self.reached }
723
724 pub fn change_by(&mut self, amount: f32) {
725 self.level = (self.level + amount).clamp(Self::UNAWARE, Self::ALERT);
726
727 if self.state() == AwarenessState::Alert {
728 self.reached = true;
729 } else if self.state() == AwarenessState::Unaware {
730 self.reached = false;
731 }
732 }
733
734 pub fn set_maximally_aware(&mut self) {
735 self.reached = true;
736 self.level = Self::ALERT;
737 }
738}
739
740#[derive(Clone, Debug, PartialOrd, PartialEq, Eq)]
741pub enum AwarenessState {
742 Unaware = 0,
743 Low = 1,
744 Medium = 2,
745 High = 3,
746 Alert = 4,
747}
748
749#[derive(Clone, Debug, Default)]
754pub struct ActionState {
755 pub timers: [f32; ACTIONSTATE_NUMBER_OF_CONCURRENT_TIMERS],
756 pub counters: [f32; ACTIONSTATE_NUMBER_OF_CONCURRENT_COUNTERS],
757 pub conditions: [bool; ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS],
758 pub int_counters: [u8; ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS],
759 pub positions: [Option<Vec3<f32>>; ACTIONSTATE_NUMBER_OF_CONCURRENT_POSITIONS],
760 pub initialized: bool,
761}
762
763impl Agent {
764 pub fn from_body(body: &Body) -> Self {
766 Agent {
767 rtsim_controller: RtSimController::default(),
768 patrol_origin: None,
769 target: None,
770 chaser: Chaser::default(),
771 behavior: Behavior::default(),
772 psyche: Psyche::from(body),
773 inbox: VecDeque::new(),
774 combat_state: ActionState::default(),
775 behavior_state: ActionState::default(),
776 timer: Timer::default(),
777 bearing: Vec2::zero(),
778 sounds_heard: Vec::new(),
779 multi_pid_controllers: None,
780 flee_from_pos: None,
781 stay_pos: None,
782 awareness: Awareness::new(0.0),
783 rtsim_outbox: None,
784 }
785 }
786
787 #[must_use]
788 pub fn with_patrol_origin(mut self, origin: Vec3<f32>) -> Self {
789 self.patrol_origin = Some(origin);
790 self
791 }
792
793 #[must_use]
794 pub fn with_behavior(mut self, behavior: Behavior) -> Self {
795 self.behavior = behavior;
796 self
797 }
798
799 #[must_use]
800 pub fn with_no_flee_if(mut self, condition: bool) -> Self {
801 if condition {
802 self.psyche.flee_health = 0.0;
803 }
804 self
805 }
806
807 pub fn set_no_flee(&mut self) { self.psyche.flee_health = 0.0; }
808
809 #[must_use]
811 pub fn with_destination(mut self, pos: Vec3<f32>) -> Self {
812 self.psyche.flee_health = 0.0;
813 self.rtsim_controller = RtSimController::with_destination(pos);
814 self.behavior.allow(BehaviorCapability::SPEAK);
815 self
816 }
817
818 #[must_use]
819 pub fn with_idle_wander_factor(mut self, idle_wander_factor: f32) -> Self {
820 self.psyche.idle_wander_factor = idle_wander_factor;
821 self
822 }
823
824 pub fn with_aggro_range_multiplier(mut self, aggro_range_multiplier: f32) -> Self {
825 self.psyche.aggro_range_multiplier = aggro_range_multiplier;
826 self
827 }
828
829 #[must_use]
830 pub fn with_altitude_pid_controller(mut self, mpid: PidControllers<16>) -> Self {
831 self.multi_pid_controllers = Some(mpid);
832 self
833 }
834
835 #[must_use]
837 pub fn with_aggro_no_warn(mut self) -> Self {
838 self.psyche.aggro_dist = None;
839 self
840 }
841
842 pub fn forget_old_sounds(&mut self, time: f64) {
843 if !self.sounds_heard.is_empty() {
844 self.sounds_heard
846 .retain(|&sound| time - sound.time <= SECONDS_BEFORE_FORGET_SOUNDS);
847 }
848 }
849
850 pub fn allowed_to_speak(&self) -> bool { self.behavior.can(BehaviorCapability::SPEAK) }
851}
852
853impl Component for Agent {
854 type Storage = specs::DenseVecStorage<Self>;
855}
856
857#[cfg(test)]
858mod tests {
859 use super::{Agent, Behavior, BehaviorCapability, BehaviorState, Body, humanoid};
860
861 #[test]
864 pub fn behavior_basic() {
865 let mut b = Behavior::default();
866 assert!(!b.can(BehaviorCapability::SPEAK));
868 b.allow(BehaviorCapability::SPEAK);
869 assert!(b.can(BehaviorCapability::SPEAK));
870 b.deny(BehaviorCapability::SPEAK);
871 assert!(!b.can(BehaviorCapability::SPEAK));
872 assert!(!b.is(BehaviorState::TRADING));
874 b.set(BehaviorState::TRADING);
875 assert!(b.is(BehaviorState::TRADING));
876 b.unset(BehaviorState::TRADING);
877 assert!(!b.is(BehaviorState::TRADING));
878 let b = Behavior::from(BehaviorCapability::SPEAK);
880 assert!(b.can(BehaviorCapability::SPEAK));
881 }
882
883 #[test]
885 pub fn enable_flee() {
886 let body = Body::Humanoid(humanoid::Body::random());
887 let mut agent = Agent::from_body(&body);
888
889 agent.psyche.flee_health = 1.0;
890 agent = agent.with_no_flee_if(false);
891 assert_eq!(agent.psyche.flee_health, 1.0);
892 }
893
894 #[test]
896 pub fn set_no_flee() {
897 let body = Body::Humanoid(humanoid::Body::random());
898 let mut agent = Agent::from_body(&body);
899
900 agent.psyche.flee_health = 1.0;
901 agent.set_no_flee();
902 assert_eq!(agent.psyche.flee_health, 0.0);
903 }
904
905 #[test]
906 pub fn with_aggro_no_warn() {
907 let body = Body::Humanoid(humanoid::Body::random());
908 let mut agent = Agent::from_body(&body);
909
910 agent.psyche.aggro_dist = Some(1.0);
911 agent = agent.with_aggro_no_warn();
912 assert_eq!(agent.psyche.aggro_dist, None);
913 }
914}
915
916#[derive(Clone)]
923pub struct PidController<F: Fn(f32, f32) -> f32, const NUM_SAMPLES: usize> {
924 pub kp: f32,
926 pub ki: f32,
928 pub kd: f32,
930 pub sp: f32,
932 pv_samples: [(f64, f32); NUM_SAMPLES],
934 pv_idx: usize,
936 integral_error: f64,
938 e: F,
941}
942
943impl<F: Fn(f32, f32) -> f32, const NUM_SAMPLES: usize> fmt::Debug
944 for PidController<F, NUM_SAMPLES>
945{
946 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
947 f.debug_struct("PidController")
948 .field("kp", &self.kp)
949 .field("ki", &self.ki)
950 .field("kd", &self.kd)
951 .field("sp", &self.sp)
952 .field("pv_samples", &self.pv_samples)
953 .field("pv_idx", &self.pv_idx)
954 .finish()
955 }
956}
957
958impl<F: Fn(f32, f32) -> f32, const NUM_SAMPLES: usize> PidController<F, NUM_SAMPLES> {
959 pub fn new(kp: f32, ki: f32, kd: f32, sp: f32, time: f64, e: F) -> Self {
962 Self {
963 kp,
964 ki,
965 kd,
966 sp,
967 pv_samples: [(time, sp); NUM_SAMPLES],
968 pv_idx: 0,
969 integral_error: 0.0,
970 e,
971 }
972 }
973
974 pub fn add_measurement(&mut self, time: f64, pv: f32) {
976 self.pv_idx += 1;
977 self.pv_idx %= NUM_SAMPLES;
978 self.pv_samples[self.pv_idx] = (time, pv);
979 self.update_integral_err();
980 }
981
982 pub fn calc_err(&self) -> f32 {
986 self.kp * self.proportional_err()
987 + self.ki * self.integral_err()
988 + self.kd * self.derivative_err()
989 }
990
991 pub fn proportional_err(&self) -> f32 { (self.e)(self.sp, self.pv_samples[self.pv_idx].1) }
994
995 pub fn integral_err(&self) -> f32 { self.integral_error as f32 }
1000
1001 fn update_integral_err(&mut self) {
1002 let f = |x| (self.e)(self.sp, x) as f64;
1003 let (a, x0) = self.pv_samples[(self.pv_idx + NUM_SAMPLES - 1) % NUM_SAMPLES];
1004 let (b, x1) = self.pv_samples[self.pv_idx];
1005 let dx = b - a;
1006 if dx < 5.0 {
1009 self.integral_error += dx * (f(x1) + f(x0)) / 2.0;
1010 }
1011 }
1012
1013 pub fn limit_integral_windup<W>(&mut self, f: W)
1019 where
1020 W: Fn(&mut f64),
1021 {
1022 f(&mut self.integral_error);
1023 }
1024
1025 pub fn derivative_err(&self) -> f32 {
1031 let f = |x| (self.e)(self.sp, x);
1032 let (a, x0) = self.pv_samples[(self.pv_idx + NUM_SAMPLES - 1) % NUM_SAMPLES];
1033 let (b, x1) = self.pv_samples[self.pv_idx];
1034 let h = b - a;
1035 if h == 0.0 {
1036 0.0
1037 } else {
1038 (f(x1) - f(x0)) / h as f32
1039 }
1040 }
1041}
1042
1043#[derive(Copy, Clone, Debug, PartialEq)]
1045pub enum FlightMode {
1046 Braking(BrakingMode),
1049 FlyThrough,
1052}
1053
1054#[derive(Default, Copy, Clone, Debug, PartialEq)]
1057pub enum BrakingMode {
1058 #[default]
1063 Normal,
1064 Precise,
1069}
1070
1071#[derive(Clone)]
1074pub struct PidControllers<const NUM_SAMPLES: usize> {
1075 pub mode: FlightMode,
1076 pub x_controller: Option<PidController<fn(f32, f32) -> f32, NUM_SAMPLES>>,
1078 pub y_controller: Option<PidController<fn(f32, f32) -> f32, NUM_SAMPLES>>,
1079 pub z_controller: Option<PidController<fn(f32, f32) -> f32, NUM_SAMPLES>>,
1080}
1081
1082impl<const NUM_SAMPLES: usize> fmt::Debug for PidControllers<NUM_SAMPLES> {
1083 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1084 f.debug_struct("PidControllers")
1085 .field("mode", &self.mode)
1086 .field("x_controller", &self.x_controller)
1087 .field("y_controller", &self.y_controller)
1088 .field("z_controller", &self.z_controller)
1089 .finish()
1090 }
1091}
1092
1093impl<const NUM_SAMPLES: usize> PidControllers<NUM_SAMPLES> {
1097 pub fn new(mode: FlightMode) -> Self {
1098 Self {
1099 mode,
1100 x_controller: None,
1101 y_controller: None,
1102 z_controller: None,
1103 }
1104 }
1105
1106 pub fn new_multi_pid_controllers(mode: FlightMode, travel_to: Vec3<f32>) -> PidControllers<16> {
1107 let mut mpid = PidControllers::new(mode);
1108 if matches!(mode, FlightMode::Braking(_)) {
1111 let (kp, ki, kd) = pid_coefficients_for_mode(mode, false);
1113 mpid.x_controller = Some(PidController::new(
1114 kp,
1115 ki,
1116 kd,
1117 travel_to.x,
1118 0.0,
1119 |sp, pv| sp - pv,
1120 ));
1121 mpid.y_controller = Some(PidController::new(
1122 kp,
1123 ki,
1124 kd,
1125 travel_to.y,
1126 0.0,
1127 |sp, pv| sp - pv,
1128 ));
1129 }
1130 let (kp, ki, kd) = pid_coefficients_for_mode(mode, true);
1132 mpid.z_controller = Some(PidController::new(
1133 kp,
1134 ki,
1135 kd,
1136 travel_to.z,
1137 0.0,
1138 |sp, pv| sp - pv,
1139 ));
1140 mpid
1141 }
1142
1143 #[inline(always)]
1144 pub fn add_x_measurement(&mut self, time: f64, pv: f32) {
1145 if let Some(controller) = &mut self.x_controller {
1146 controller.add_measurement(time, pv);
1147 }
1148 }
1149
1150 #[inline(always)]
1151 pub fn add_y_measurement(&mut self, time: f64, pv: f32) {
1152 if let Some(controller) = &mut self.y_controller {
1153 controller.add_measurement(time, pv);
1154 }
1155 }
1156
1157 #[inline(always)]
1158 pub fn add_z_measurement(&mut self, time: f64, pv: f32) {
1159 if let Some(controller) = &mut self.z_controller {
1160 controller.add_measurement(time, pv);
1161 }
1162 }
1163
1164 #[inline(always)]
1165 pub fn add_measurement(&mut self, time: f64, pos: Vec3<f32>) {
1166 if let Some(controller) = &mut self.x_controller {
1167 controller.add_measurement(time, pos.x);
1168 }
1169 if let Some(controller) = &mut self.y_controller {
1170 controller.add_measurement(time, pos.y);
1171 }
1172 if let Some(controller) = &mut self.z_controller {
1173 controller.add_measurement(time, pos.z);
1174 }
1175 }
1176
1177 #[inline(always)]
1178 pub fn calc_err_x(&self) -> Option<f32> {
1179 self.x_controller
1180 .as_ref()
1181 .map(|controller| controller.calc_err())
1182 }
1183
1184 #[inline(always)]
1185 pub fn calc_err_y(&self) -> Option<f32> {
1186 self.y_controller
1187 .as_ref()
1188 .map(|controller| controller.calc_err())
1189 }
1190
1191 #[inline(always)]
1192 pub fn calc_err_z(&self) -> Option<f32> {
1193 self.z_controller
1194 .as_ref()
1195 .map(|controller| controller.calc_err())
1196 }
1197
1198 #[inline(always)]
1199 pub fn limit_windup_x<F>(&mut self, f: F)
1200 where
1201 F: Fn(&mut f64),
1202 {
1203 if let Some(controller) = &mut self.x_controller {
1204 controller.limit_integral_windup(f);
1205 }
1206 }
1207
1208 #[inline(always)]
1209 pub fn limit_windup_y<F>(&mut self, f: F)
1210 where
1211 F: Fn(&mut f64),
1212 {
1213 if let Some(controller) = &mut self.y_controller {
1214 controller.limit_integral_windup(f);
1215 }
1216 }
1217
1218 #[inline(always)]
1219 pub fn limit_windup_z<F>(&mut self, f: F)
1220 where
1221 F: Fn(&mut f64),
1222 {
1223 if let Some(controller) = &mut self.z_controller {
1224 controller.limit_integral_windup(f);
1225 }
1226 }
1227}
1228
1229pub fn pid_coefficients(body: &Body) -> Option<(f32, f32, f32)> {
1232 match body {
1234 Body::Ship(ship::Body::AirBalloon) => {
1235 let kp = 1.0;
1236 let ki = 0.1;
1237 let kd = 0.8;
1238 Some((kp, ki, kd))
1239 },
1240 Body::Object(object::Body::Crux) => Some((0.9, 0.3, 1.0)),
1241 _ => None,
1242 }
1243}
1244
1245pub fn pid_coefficients_for_mode(mode: FlightMode, z_axis: bool) -> (f32, f32, f32) {
1247 match mode {
1248 FlightMode::FlyThrough => {
1249 if z_axis {
1250 (1.0, 0.4, 0.25)
1251 } else {
1252 (0.0, 0.0, 0.0)
1253 }
1254 },
1255 FlightMode::Braking(braking_mode) => match braking_mode {
1256 BrakingMode::Normal => (1.0, 0.3, 0.4),
1257 BrakingMode::Precise => (0.9, 0.3, 1.0),
1258 },
1259 }
1260}