veloren_common/comp/
agent.rs

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
23//intentionally very few concurrent action state variables are allowed. This is
24// to keep the complexity of our AI from getting too large, too quickly.
25// Originally I was going to provide 30 of these, but if we decide later that
26// this is too many and somebody is already using 30 in one of their AI, it will
27// be difficult to go back.
28
29/// The number of timers that a single Action node can track concurrently
30/// Define constants within a given action node to index between them.
31const ACTIONSTATE_NUMBER_OF_CONCURRENT_TIMERS: usize = 5;
32/// The number of float counters that a single Action node can track
33/// concurrently Define constants within a given action node to index between
34/// them.
35const ACTIONSTATE_NUMBER_OF_CONCURRENT_COUNTERS: usize = 5;
36/// The number of integer counters that a single Action node can track
37/// concurrently Define constants within a given action node to index between
38/// them.
39const ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS: usize = 5;
40/// The number of booleans that a single Action node can track concurrently
41/// Define constants within a given action node to index between them.
42const ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS: usize = 5;
43/// The number of positions that can be remembered by an agent
44const ACTIONSTATE_NUMBER_OF_CONCURRENT_POSITIONS: usize = 5;
45
46#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub enum Alignment {
48    /// Wild animals and gentle giants
49    Wild,
50    /// Dungeon cultists and bandits
51    Enemy,
52    /// Friendly folk in villages
53    Npc,
54    /// Farm animals and pets of villagers
55    Tame,
56    /// Pets you've tamed with a collar
57    Owned(Uid),
58    /// Passive objects like training dummies
59    Passive,
60}
61
62#[derive(Copy, Clone, Debug, PartialEq, Eq)]
63pub enum Mark {
64    Merchant,
65    Guard,
66}
67
68impl Alignment {
69    // Always attacks
70    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    // Usually never attacks
87    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    // Never attacks
103    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    /// Default group for this alignment
117    // NOTE: This is a HACK
118    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/// # Behavior Component
169/// This component allow an Entity to register one or more behavior tags.
170/// These tags act as flags of what an Entity can do, or what it is doing.
171/// Behaviors Tags can be added and removed as the Entity lives, to update its
172/// state when needed
173#[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    /// Builder function
192    /// Set capabilities if Option is Some
193    #[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    /// Builder function
205    /// Set trade_site if Option is Some
206    #[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    /// Set capabilities to the Behavior
215    pub fn allow(&mut self, capabilities: BehaviorCapability) {
216        self.capabilities.set(capabilities, true)
217    }
218
219    /// Unset capabilities to the Behavior
220    pub fn deny(&mut self, capabilities: BehaviorCapability) {
221        self.capabilities.set(capabilities, false)
222    }
223
224    /// Check if the Behavior is able to do something
225    pub fn can(&self, capabilities: BehaviorCapability) -> bool {
226        self.capabilities.contains(capabilities)
227    }
228
229    /// Check if the Behavior is able to trade
230    pub fn can_trade(&self, alignment: Option<Alignment>, counterparty: Uid) -> bool {
231        self.trading_behavior.can_trade(alignment, counterparty)
232    }
233
234    /// Set a state to the Behavior
235    pub fn set(&mut self, state: BehaviorState) { self.state.set(state, true) }
236
237    /// Unset a state to the Behavior
238    pub fn unset(&mut self, state: BehaviorState) { self.state.set(state, false) }
239
240    /// Check if the Behavior has a specific state
241    pub fn is(&self, state: BehaviorState) -> bool { self.state.contains(state) }
242
243    /// Get the trade site at which this behavior evaluates prices, if it does
244    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    /// The proportion of health below which entities will start fleeing.
256    /// 0.0 = never flees, 1.0 = always flees, 0.5 = flee at 50% health.
257    pub flee_health: f32,
258    /// The distance below which the agent will see enemies if it has line of
259    /// sight.
260    pub sight_dist: f32,
261    /// The distance below which the agent can hear enemies without seeing them.
262    pub listen_dist: f32,
263    /// The distance below which the agent will attack enemies. Should be lower
264    /// than `sight_dist`. `None` implied that the agent is always aggro
265    /// towards enemies that it is aware of.
266    pub aggro_dist: Option<f32>,
267    /// A factor that controls how much further an agent will wander when in the
268    /// idle state. `1.0` is normal.
269    pub idle_wander_factor: f32,
270    /// Aggro range is multiplied by this factor. `1.0` is normal.
271    ///
272    /// This includes scaling the effective `sight_dist` and `listen_dist`
273    /// when finding new targets to attack, adjusting the strength of
274    /// wandering behavior in the idle state, and scaling `aggro_dist` in
275    /// certain situations.
276    pub aggro_range_multiplier: f32,
277    /// Whether this entity should *intelligently* decide to loose aggro based
278    /// on distance from agent home and health, this is not suitable for world
279    /// bosses and roaming entities
280    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                    // FIXME: This is to balance for enemy rats in dungeons
314                    // Normal rats should probably always flee.
315                    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                    // T1
323                    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                    // T2
329                    // Should probably not have non-grouped, hostile animals flee until fleeing is
330                    // improved.
331                    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                    // T3A
350                    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                    // T1
362                    quadruped_low::Species::Pangolin => 0.3,
363                    // T2,
364                    quadruped_low::Species::Tortoise => 0.1,
365                    // There are a lot of hostile, solo entities.
366                    _ => 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, // Always aggressive if detected
445            },
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                    // Skip stop_pursuing, as that function breaks when the health fraction is 0.0
456                    // (to keep aggro after death transform)
457                    !matches!(bird_medium.species, bird_medium::Species::BloodmoonBat)
458                },
459                _ => true,
460            },
461        }
462    }
463}
464
465impl Psyche {
466    /// The maximum distance that targets to attack might be detected by this
467    /// agent.
468    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)]
474/// Events that affect agent behavior from other entities/players/environment
475pub enum AgentEvent {
476    /// Engage in conversation with entity with Uid
477    Talk(Uid),
478    TradeInvite(Uid),
479    TradeAccepted(Uid),
480    FinishedTrade(TradeResult),
481    UpdatePendingTrade(
482        // This data structure is large so box it to keep AgentEvent small
483        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    /// Whether the target is hostile
539    pub hostile: bool,
540    /// The time at which the target was selected
541    pub selected_at: f64,
542    /// Whether the target has come close enough to trigger aggro.
543    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/// A time used for managing agent-related timeouts. The timer is designed to
571/// keep track of the start of any number of previous actions. However,
572/// starting/progressing an action will end previous actions. Therefore, the
573/// timer should be used for actions that are mutually-exclusive.
574#[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 // Can't fail, EnumIter is exhaustive
596    }
597
598    /// Reset the timer for the given action, returning true if the timer was
599    /// not already reset.
600    pub fn reset(&mut self, action: TimerAction) -> bool {
601        self.action_starts[Self::idx_for(action)].take().is_some()
602    }
603
604    /// Start the timer for the given action, even if it was already started.
605    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    /// Continue timing the given action, starting it if it was not already
611    /// started.
612    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    /// Return the time that the given action was last performed at.
619    pub fn time_of_last(&self, action: TimerAction) -> Option<f64> {
620        self.action_starts[Self::idx_for(action)]
621    }
622
623    /// Return `true` if the time since the action was last started exceeds the
624    /// given timeout.
625    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    /// Return `true` while the time since the action was last started is less
631    /// than the given period. Once the time has elapsed, reset the timer.
632    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/// For use with the builder pattern <https://doc.rust-lang.org/1.0.0/style/ownership/builders.html>
648#[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    /// Position from which to flee. Intended to be the agent's position plus a
664    /// random position offset, to be used when a random flee direction is
665    /// required and reset each time the flee timer is reset.
666    pub flee_from_pos: Option<Pos>,
667    pub awareness: Awareness,
668    pub stay_pos: Option<Pos>,
669    /// Inputs sent up to rtsim
670    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)]
680/// Always clamped between `0.0` and `1.0`.
681pub 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    /// The level of awareness as a decimal.
703    pub fn level(&self) -> f32 { self.level }
704
705    /// The level of awareness in English. To see if awareness has been fully
706    /// reached, use `self.reached()`.
707    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    /// Awareness was reached at some point and has not been reset.
722    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/// State persistence object for the behavior tree
750/// Allows for state to be stored between subsequent, sequential calls of a
751/// single action node. If the executed action node changes between ticks, then
752/// the state should be considered lost.
753#[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    /// Instantiates agent from body using the body's psyche
765    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    // FIXME: Only one of *three* things in this method sets a location.
810    #[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    /// Makes agent aggressive without warning
836    #[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            // Keep (retain) only newer sounds
845            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 to verify that Behavior is working correctly at its most basic
862    /// usages
863    #[test]
864    pub fn behavior_basic() {
865        let mut b = Behavior::default();
866        // test capabilities
867        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        // test states
873        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        // test `from`
879        let b = Behavior::from(BehaviorCapability::SPEAK);
880        assert!(b.can(BehaviorCapability::SPEAK));
881    }
882
883    /// Makes agent flee
884    #[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    /// Makes agent not flee
895    #[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/// PID controllers (Proportional–integral–derivative controllers) are used for
917/// automatically adapting nonlinear controls (like buoyancy for balloons)
918/// and for damping out oscillations in control feedback loops (like vehicle
919/// cruise control). PID controllers can monitor an error signal between a
920/// setpoint and a process variable, and use that error to adjust a control
921/// variable. A PID controller can only control one variable at a time.
922#[derive(Clone)]
923pub struct PidController<F: Fn(f32, f32) -> f32, const NUM_SAMPLES: usize> {
924    /// The coefficient of the proportional term
925    pub kp: f32,
926    /// The coefficient of the integral term
927    pub ki: f32,
928    /// The coefficient of the derivative term
929    pub kd: f32,
930    /// The setpoint that the process has as its goal
931    pub sp: f32,
932    /// A ring buffer of the last NUM_SAMPLES measured process variables
933    pv_samples: [(f64, f32); NUM_SAMPLES],
934    /// The index into the ring buffer of process variables
935    pv_idx: usize,
936    /// The total integral error
937    integral_error: f64,
938    /// The error function, to change how the difference between the setpoint
939    /// and process variables are calculated
940    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    /// Constructs a PidController with the specified weights, setpoint,
960    /// starting time, and error function
961    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    /// Adds a measurement of the process variable to the ringbuffer
975    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    /// The amount to set the control variable to is a weighed sum of the
983    /// proportional error, the integral error, and the derivative error.
984    /// <https://en.wikipedia.org/wiki/PID_controller#Mathematical_form>
985    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    /// The proportional error is the error function applied to the set point
992    /// and the most recent process variable measurement
993    pub fn proportional_err(&self) -> f32 { (self.e)(self.sp, self.pv_samples[self.pv_idx].1) }
994
995    /// The integral error is the error function integrated over all previous
996    /// values, updated per point. The trapezoid rule for numerical integration
997    /// was chosen because it's fairly easy to calculate and sufficiently
998    /// accurate. <https://en.wikipedia.org/wiki/Trapezoidal_rule#Uniform_grid>
999    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        // Discard updates with too long between them, likely caused by either
1007        // initialization or latency, since they're likely to be spurious
1008        if dx < 5.0 {
1009            self.integral_error += dx * (f(x1) + f(x0)) / 2.0;
1010        }
1011    }
1012
1013    /// The integral error accumulates over time, and can get to the point where
1014    /// the output of the controller is saturated. This is called integral
1015    /// windup, and it commonly occurs when the controller setpoint is
1016    /// changed. To limit the integral error signal, this function can be
1017    /// called with a fn/closure that can modify the integral_error value.
1018    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    /// The derivative error is the numerical derivative of the error function
1026    /// based on the most recent 2 samples. Using more than 2 samples might
1027    /// improve the accuracy of the estimate of the derivative, but it would be
1028    /// an estimate of the derivative error further in the past.
1029    /// <https://en.wikipedia.org/wiki/Numerical_differentiation#Finite_differences>
1030    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/// The desired flight behavior when an entity is approaching a target position.
1044#[derive(Copy, Clone, Debug, PartialEq)]
1045pub enum FlightMode {
1046    /// The agent should cause the entity to decelerate and stop at the target
1047    /// position.
1048    Braking(BrakingMode),
1049    /// The agent should cause the entity to fly to and through the target
1050    /// position without slowing.
1051    FlyThrough,
1052}
1053
1054/// When FlightMode is Braking, the agent can use different braking modes to
1055/// control the amount of overshoot and oscillation.
1056#[derive(Default, Copy, Clone, Debug, PartialEq)]
1057pub enum BrakingMode {
1058    /// Oscillation around the target position is acceptable, with slow damping.
1059    /// This mode is useful for when the entity is not expected to fully stop at
1060    /// the target position, and arrival near the target position is more
1061    /// important than precision.
1062    #[default]
1063    Normal,
1064    /// Prefer faster damping of position error, reducing overshoot and
1065    /// oscillation around the target position. This mode is useful for when
1066    /// the entity is expected to fully stop at the target and precision is
1067    /// most important.
1068    Precise,
1069}
1070
1071/// A Mulit-PID controller that can control the acceleration/velocity in one or
1072/// all three axes.
1073#[derive(Clone)]
1074pub struct PidControllers<const NUM_SAMPLES: usize> {
1075    pub mode: FlightMode,
1076    /// This is ignored unless the mode FixedDirection.
1077    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
1093/// A collection of PidControllers, one for each 3D axis.
1094/// Airships use either one or three PidControllers, depending on the mode
1095/// (see action_nodes.rs for details of Airship modes).
1096impl<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        // FixedDirection requires x and y and z controllers
1109        // PureZ requires only z controller
1110        if matches!(mode, FlightMode::Braking(_)) {
1111            // Add x_controller && y_controller
1112            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        // Add z_controller
1131        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
1229/// Get the PID coefficients associated with some Body, since it will likely
1230/// need to be tuned differently for each body type
1231pub fn pid_coefficients(body: &Body) -> Option<(f32, f32, f32)> {
1232    // A pure-proportional controller is { kp: 1.0, ki: 0.0, kd: 0.0 }
1233    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
1245/// Get the PID coefficients (for the airship, for now) by PID modes.
1246pub 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}