veloren_rtsim/data/
npc.rs

1use crate::{
2    ai::Action,
3    data::{Reports, Sentiments},
4    gen::name,
5};
6pub use common::rtsim::{NpcId, Profession};
7use common::{
8    character::CharacterId,
9    comp::{self, agent::FlightMode},
10    grid::Grid,
11    resources::Time,
12    rtsim::{
13        Actor, ChunkResource, Dialogue, DialogueId, DialogueKind, FactionId, NpcAction,
14        NpcActivity, NpcInput, Personality, ReportId, Response, Role, SiteId,
15    },
16    store::Id,
17    terrain::CoordinateConversions,
18    util::Dir,
19};
20use hashbrown::{HashMap, HashSet};
21use rand::prelude::*;
22use serde::{Deserialize, Serialize};
23use slotmap::HopSlotMap;
24use std::{
25    collections::VecDeque,
26    ops::{Deref, DerefMut},
27};
28use tracing::error;
29use vek::*;
30use world::{
31    civ::Track,
32    site::Site as WorldSite,
33    util::{LOCALITY, RandomPerm},
34};
35
36#[derive(Copy, Clone, Debug, Default)]
37pub enum SimulationMode {
38    /// The NPC is unloaded and is being simulated via rtsim.
39    #[default]
40    Simulated,
41    /// The NPC has been loaded into the game world as an ECS entity.
42    Loaded,
43}
44
45#[derive(Clone)]
46pub struct PathData<P, N> {
47    pub end: N,
48    pub path: VecDeque<P>,
49    pub repoll: bool,
50}
51
52#[derive(Clone, Default)]
53pub struct PathingMemory {
54    pub intrasite_path: Option<(PathData<Vec2<i32>, Vec2<i32>>, Id<WorldSite>)>,
55    pub intersite_path: Option<(PathData<(Id<Track>, bool), SiteId>, usize)>,
56}
57
58#[derive(Default)]
59pub struct Controller {
60    pub actions: Vec<NpcAction>,
61    pub activity: Option<NpcActivity>,
62    pub new_home: Option<SiteId>,
63    pub look_dir: Option<Dir>,
64    pub hiring: Option<Option<(Actor, Time)>>,
65}
66
67impl Controller {
68    /// Reset the controller to a neutral state before the start of the next
69    /// brain tick.
70    pub fn reset(&mut self) {
71        self.activity = None;
72        self.look_dir = None;
73    }
74
75    pub fn do_idle(&mut self) { self.activity = None; }
76
77    pub fn do_talk(&mut self, tgt: Actor) { self.activity = Some(NpcActivity::Talk(tgt)); }
78
79    pub fn do_goto(&mut self, wpos: Vec3<f32>, speed_factor: f32) {
80        self.activity = Some(NpcActivity::Goto(wpos, speed_factor));
81    }
82
83    /// go to with height above terrain and direction
84    pub fn do_goto_with_height_and_dir(
85        &mut self,
86        wpos: Vec3<f32>,
87        speed_factor: f32,
88        height: Option<f32>,
89        dir: Option<Dir>,
90        flight_mode: FlightMode,
91    ) {
92        self.activity = Some(NpcActivity::GotoFlying(
93            wpos,
94            speed_factor,
95            height,
96            dir,
97            flight_mode,
98        ));
99    }
100
101    pub fn do_gather(&mut self, resources: &'static [ChunkResource]) {
102        self.activity = Some(NpcActivity::Gather(resources));
103    }
104
105    pub fn do_hunt_animals(&mut self) { self.activity = Some(NpcActivity::HuntAnimals); }
106
107    pub fn do_dance(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Dance(dir)); }
108
109    pub fn do_cheer(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Cheer(dir)); }
110
111    pub fn do_sit(&mut self, dir: Option<Dir>, pos: Option<Vec3<i32>>) {
112        self.activity = Some(NpcActivity::Sit(dir, pos));
113    }
114
115    pub fn say(&mut self, target: impl Into<Option<Actor>>, content: comp::Content) {
116        self.actions.push(NpcAction::Say(target.into(), content));
117    }
118
119    pub fn attack(&mut self, target: impl Into<Actor>) {
120        self.actions.push(NpcAction::Attack(target.into()));
121    }
122
123    pub fn set_new_home(&mut self, new_home: SiteId) { self.new_home = Some(new_home); }
124
125    pub fn set_newly_hired(&mut self, actor: Actor, expires: Time) {
126        self.hiring = Some(Some((actor, expires)));
127    }
128
129    pub fn end_hiring(&mut self) { self.hiring = Some(None); }
130
131    /// Start a new dialogue.
132    pub fn dialogue_start(&mut self, target: impl Into<Actor>) -> DialogueSession {
133        let target = target.into();
134
135        let session = DialogueSession {
136            target,
137            id: DialogueId(thread_rng().gen()),
138        };
139
140        self.actions.push(NpcAction::Dialogue(target, Dialogue {
141            id: session.id,
142            kind: DialogueKind::Start,
143        }));
144
145        session
146    }
147
148    /// End an existing dialogue.
149    pub fn dialogue_end(&mut self, session: DialogueSession) {
150        self.actions
151            .push(NpcAction::Dialogue(session.target, Dialogue {
152                id: session.id,
153                kind: DialogueKind::End,
154            }));
155    }
156
157    /// Ask a question, with various possible answers. Returns the dialogue tag,
158    /// used for identifying the answer.
159    pub fn dialogue_question(
160        &mut self,
161        session: DialogueSession,
162        msg: comp::Content,
163        responses: impl IntoIterator<Item = (u16, Response)>,
164    ) -> u32 {
165        let tag = thread_rng().gen();
166
167        self.actions
168            .push(NpcAction::Dialogue(session.target, Dialogue {
169                id: session.id,
170                kind: DialogueKind::Question {
171                    tag,
172                    msg,
173                    responses: responses.into_iter().collect(),
174                },
175            }));
176
177        tag
178    }
179
180    /// Provide a statement as part of a dialogue.
181    pub fn dialogue_statement(&mut self, session: DialogueSession, msg: comp::Content) {
182        self.actions
183            .push(NpcAction::Dialogue(session.target, Dialogue {
184                id: session.id,
185                kind: DialogueKind::Statement(msg),
186            }));
187    }
188
189    /// Provide a location marker as part of a dialogue.
190    pub fn dialogue_marker(
191        &mut self,
192        session: DialogueSession,
193        wpos: Vec2<i32>,
194        name: comp::Content,
195    ) {
196        self.actions
197            .push(NpcAction::Dialogue(session.target, Dialogue {
198                id: session.id,
199                kind: DialogueKind::Marker { wpos, name },
200            }));
201    }
202}
203
204// Represents an ongoing dialogue with another actor.
205#[derive(Copy, Clone)]
206pub struct DialogueSession {
207    pub target: Actor,
208    pub id: DialogueId,
209}
210
211pub struct Brain {
212    pub action: Box<dyn Action<(), !>>,
213}
214
215#[derive(Serialize, Deserialize)]
216pub struct Npc {
217    pub uid: u64,
218    // Persisted state
219    pub seed: u32,
220    /// Represents the location of the NPC.
221    pub wpos: Vec3<f32>,
222    pub dir: Vec2<f32>,
223
224    pub body: comp::Body,
225    pub role: Role,
226    pub home: Option<SiteId>,
227    pub faction: Option<FactionId>,
228    /// The current health of the NPC, < 0.0 is dead and 1.0 is max.
229    pub health_fraction: f32,
230
231    /// The [`Report`]s that the NPC is aware of.
232    pub known_reports: HashSet<ReportId>,
233
234    #[serde(default)]
235    pub personality: Personality,
236    #[serde(default)]
237    pub sentiments: Sentiments,
238
239    /// An NPC can temporarily become a hired hand (`(hiring_actor,
240    /// termination_time)`).
241    #[serde(default)]
242    pub hiring: Option<(Actor, Time)>,
243
244    // Unpersisted state
245    #[serde(skip)]
246    pub chunk_pos: Option<Vec2<i32>>,
247    #[serde(skip)]
248    pub current_site: Option<SiteId>,
249
250    #[serde(skip)]
251    pub controller: Controller,
252    #[serde(skip)]
253    pub inbox: VecDeque<NpcInput>,
254
255    /// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
256    /// server, loaded corresponds to being within a loaded chunk). When in
257    /// loaded mode, the interactions of the NPC should not be simulated but
258    /// should instead be derived from the game.
259    #[serde(skip)]
260    pub mode: SimulationMode,
261
262    #[serde(skip)]
263    pub brain: Option<Brain>,
264}
265
266impl Clone for Npc {
267    fn clone(&self) -> Self {
268        Self {
269            uid: self.uid,
270            seed: self.seed,
271            wpos: self.wpos,
272            dir: self.dir,
273            role: self.role.clone(),
274            home: self.home,
275            faction: self.faction,
276            health_fraction: self.health_fraction,
277            known_reports: self.known_reports.clone(),
278            body: self.body,
279            personality: self.personality,
280            sentiments: self.sentiments.clone(),
281            hiring: self.hiring,
282            // Not persisted
283            chunk_pos: None,
284            current_site: Default::default(),
285            controller: Default::default(),
286            inbox: Default::default(),
287            mode: Default::default(),
288            brain: Default::default(),
289        }
290    }
291}
292
293impl Npc {
294    pub const PERM_ENTITY_CONFIG: u32 = 1;
295    const PERM_NAME: u32 = 0;
296
297    pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body, role: Role) -> Self {
298        Self {
299            // To be assigned later
300            uid: 0,
301            seed,
302            wpos,
303            dir: Vec2::unit_x(),
304            body,
305            personality: Default::default(),
306            sentiments: Default::default(),
307            hiring: None,
308            role,
309            home: None,
310            faction: None,
311            health_fraction: 1.0,
312            known_reports: Default::default(),
313            chunk_pos: None,
314            current_site: None,
315            controller: Default::default(),
316            inbox: Default::default(),
317            mode: SimulationMode::Simulated,
318            brain: None,
319        }
320    }
321
322    pub fn is_dead(&self) -> bool { self.health_fraction <= 0.0 }
323
324    // TODO: have a dedicated `NpcBuilder` type for this.
325    pub fn with_personality(mut self, personality: Personality) -> Self {
326        self.personality = personality;
327        self
328    }
329
330    // // TODO: have a dedicated `NpcBuilder` type for this.
331    // pub fn with_profession(mut self, profession: impl Into<Option<Profession>>)
332    // -> Self {     if let Role::Humanoid(p) = &mut self.role {
333    //         *p = profession.into();
334    //     } else {
335    //         panic!("Tried to assign profession {:?} to NPC, but has role {:?},
336    // which cannot have a profession", profession.into(), self.role);     }
337    //     self
338    // }
339
340    // TODO: have a dedicated `NpcBuilder` type for this.
341    pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
342        self.home = home.into();
343        self
344    }
345
346    // TODO: have a dedicated `NpcBuilder` type for this.
347    pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
348        self.faction = faction.into();
349        self
350    }
351
352    pub fn rng(&self, perm: u32) -> impl Rng + use<> {
353        RandomPerm::new(self.seed.wrapping_add(perm))
354    }
355
356    // TODO: Don't make this depend on deterministic RNG, actually persist names
357    // once we've decided that we want to
358    pub fn get_name(&self) -> String { name::generate(&mut self.rng(Self::PERM_NAME)) }
359
360    pub fn profession(&self) -> Option<Profession> {
361        match &self.role {
362            Role::Civilised(profession) => profession.clone(),
363            Role::Monster | Role::Wild | Role::Vehicle => None,
364        }
365    }
366
367    pub fn cleanup(&mut self, reports: &Reports) {
368        // Clear old or superfluous sentiments
369        // TODO: It might be worth giving more important NPCs a higher sentiment
370        // 'budget' than less important ones.
371        self.sentiments
372            .cleanup(crate::data::sentiment::NPC_MAX_SENTIMENTS);
373        // Clear reports that have been forgotten
374        self.known_reports
375            .retain(|report| reports.contains_key(*report));
376        // TODO: Limit number of reports
377        // TODO: Clear old inbox items
378    }
379}
380
381#[derive(Default, Clone, Serialize, Deserialize)]
382pub struct GridCell {
383    pub npcs: Vec<NpcId>,
384}
385
386#[derive(Clone, Serialize, Deserialize, Debug)]
387pub struct NpcLink {
388    pub mount: NpcId,
389    pub rider: Actor,
390    pub is_steering: bool,
391}
392
393#[derive(Clone, Default, Serialize, Deserialize)]
394struct Riders {
395    steerer: Option<MountId>,
396    riders: Vec<MountId>,
397}
398
399#[derive(Clone, Default, Serialize, Deserialize)]
400#[serde(
401    from = "HopSlotMap<MountId, NpcLink>",
402    into = "HopSlotMap<MountId, NpcLink>"
403)]
404pub struct NpcLinks {
405    links: HopSlotMap<MountId, NpcLink>,
406    mount_map: slotmap::SecondaryMap<NpcId, Riders>,
407    rider_map: HashMap<Actor, MountId>,
408}
409
410impl NpcLinks {
411    pub fn remove_mount(&mut self, mount: NpcId) {
412        if let Some(riders) = self.mount_map.remove(mount) {
413            for link in riders
414                .riders
415                .into_iter()
416                .chain(riders.steerer)
417                .filter_map(|link_id| self.links.get(link_id))
418            {
419                self.rider_map.remove(&link.rider);
420            }
421        }
422    }
423
424    /// Internal function, only removes from `mount_map`.
425    fn remove_rider(&mut self, id: MountId, link: &NpcLink) {
426        if let Some(riders) = self.mount_map.get_mut(link.mount) {
427            if link.is_steering && riders.steerer == Some(id) {
428                riders.steerer = None;
429            } else if let Some((i, _)) = riders.riders.iter().enumerate().find(|(_, i)| **i == id) {
430                riders.riders.remove(i);
431            }
432
433            if riders.steerer.is_none() && riders.riders.is_empty() {
434                self.mount_map.remove(link.mount);
435            }
436        }
437    }
438
439    pub fn remove_link(&mut self, link_id: MountId) {
440        if let Some(link) = self.links.remove(link_id) {
441            self.rider_map.remove(&link.rider);
442            self.remove_rider(link_id, &link);
443        }
444    }
445
446    pub fn dismount(&mut self, rider: impl Into<Actor>) {
447        if let Some(id) = self.rider_map.remove(&rider.into()) {
448            if let Some(link) = self.links.remove(id) {
449                self.remove_rider(id, &link);
450            }
451        }
452    }
453
454    // This is the only function to actually add a mount link.
455    // And it ensures that there isn't link chaining
456    pub fn add_mounting(
457        &mut self,
458        mount: NpcId,
459        rider: impl Into<Actor>,
460        steering: bool,
461    ) -> Result<MountId, MountingError> {
462        let rider = rider.into();
463        if Actor::Npc(mount) == rider {
464            return Err(MountingError::MountSelf);
465        }
466        if let Actor::Npc(rider) = rider
467            && self.mount_map.contains_key(rider)
468        {
469            return Err(MountingError::RiderIsMounted);
470        }
471        if self.rider_map.contains_key(&Actor::Npc(mount)) {
472            return Err(MountingError::MountIsRiding);
473        }
474        if let Some(mount_entry) = self.mount_map.entry(mount) {
475            if let hashbrown::hash_map::Entry::Vacant(rider_entry) = self.rider_map.entry(rider) {
476                let riders = mount_entry.or_insert(Riders::default());
477
478                if steering {
479                    if riders.steerer.is_none() {
480                        let id = self.links.insert(NpcLink {
481                            mount,
482                            rider,
483                            is_steering: true,
484                        });
485                        riders.steerer = Some(id);
486                        rider_entry.insert(id);
487                        Ok(id)
488                    } else {
489                        Err(MountingError::HasSteerer)
490                    }
491                } else {
492                    // TODO: Maybe have some limit on the number of riders depending on the mount?
493                    let id = self.links.insert(NpcLink {
494                        mount,
495                        rider,
496                        is_steering: false,
497                    });
498                    riders.riders.push(id);
499                    rider_entry.insert(id);
500                    Ok(id)
501                }
502            } else {
503                Err(MountingError::AlreadyRiding)
504            }
505        } else {
506            Err(MountingError::MountDead)
507        }
508    }
509
510    pub fn steer(
511        &mut self,
512        mount: NpcId,
513        rider: impl Into<Actor>,
514    ) -> Result<MountId, MountingError> {
515        self.add_mounting(mount, rider, true)
516    }
517
518    pub fn ride(
519        &mut self,
520        mount: NpcId,
521        rider: impl Into<Actor>,
522    ) -> Result<MountId, MountingError> {
523        self.add_mounting(mount, rider, false)
524    }
525
526    pub fn get_mount_link(&self, rider: impl Into<Actor>) -> Option<&NpcLink> {
527        self.rider_map
528            .get(&rider.into())
529            .and_then(|link| self.links.get(*link))
530    }
531
532    pub fn get_steerer_link(&self, mount: NpcId) -> Option<&NpcLink> {
533        self.mount_map
534            .get(mount)
535            .and_then(|mount| self.links.get(mount.steerer?))
536    }
537
538    pub fn get(&self, id: MountId) -> Option<&NpcLink> { self.links.get(id) }
539
540    pub fn ids(&self) -> impl Iterator<Item = MountId> + '_ { self.links.keys() }
541
542    pub fn iter(&self) -> impl Iterator<Item = &NpcLink> + '_ { self.links.values() }
543
544    pub fn iter_mounts(&self) -> impl Iterator<Item = NpcId> + '_ { self.mount_map.keys() }
545}
546
547impl From<HopSlotMap<MountId, NpcLink>> for NpcLinks {
548    fn from(mut value: HopSlotMap<MountId, NpcLink>) -> Self {
549        let mut from_map = slotmap::SecondaryMap::new();
550        let mut to_map = HashMap::with_capacity(value.len());
551        let mut delete = Vec::new();
552        for (id, link) in value.iter() {
553            if let Some(entry) = from_map.entry(link.mount) {
554                let riders = entry.or_insert(Riders::default());
555                if link.is_steering {
556                    if let Some(old) = riders.steerer.replace(id) {
557                        error!("Replaced steerer {old:?} with {id:?}");
558                    }
559                } else {
560                    riders.riders.push(id);
561                }
562            } else {
563                delete.push(id);
564            }
565            to_map.insert(link.rider, id);
566        }
567        for id in delete {
568            value.remove(id);
569        }
570        Self {
571            links: value,
572            mount_map: from_map,
573            rider_map: to_map,
574        }
575    }
576}
577
578impl From<NpcLinks> for HopSlotMap<MountId, NpcLink> {
579    fn from(other: NpcLinks) -> Self { other.links }
580}
581slotmap::new_key_type! {
582    pub struct MountId;
583}
584
585#[derive(Clone, Serialize, Deserialize)]
586pub struct MountData {
587    is_steering: bool,
588}
589
590#[derive(Clone, Serialize, Deserialize)]
591pub struct Npcs {
592    pub uid_counter: u64,
593    pub npcs: HopSlotMap<NpcId, Npc>,
594    pub mounts: NpcLinks,
595    // TODO: This feels like it should be its own rtsim resource
596    // TODO: Consider switching to `common::util::SpatialGrid` instead
597    #[serde(skip, default = "construct_npc_grid")]
598    pub npc_grid: Grid<GridCell>,
599    #[serde(skip)]
600    pub character_map: HashMap<Vec2<i32>, Vec<(CharacterId, Vec3<f32>)>>,
601}
602
603impl Default for Npcs {
604    fn default() -> Self {
605        Self {
606            uid_counter: 0,
607            npcs: Default::default(),
608            mounts: Default::default(),
609            npc_grid: construct_npc_grid(),
610            character_map: Default::default(),
611        }
612    }
613}
614
615fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) }
616
617#[derive(Debug)]
618pub enum MountingError {
619    MountDead,
620    RiderDead,
621    HasSteerer,
622    AlreadyRiding,
623    MountIsRiding,
624    RiderIsMounted,
625    MountSelf,
626}
627
628impl Npcs {
629    pub fn create_npc(&mut self, mut npc: Npc) -> NpcId {
630        npc.uid = self.uid_counter;
631        self.uid_counter += 1;
632        self.npcs.insert(npc)
633    }
634
635    /// Queries nearby npcs, not garantueed to work if radius > 32.0
636    // TODO: Find a more efficient way to implement this, it's currently
637    // (theoretically) O(n^2).
638    pub fn nearby(
639        &self,
640        this_npc: Option<NpcId>,
641        wpos: Vec3<f32>,
642        radius: f32,
643    ) -> impl Iterator<Item = Actor> + '_ {
644        let chunk_pos = wpos.xy().as_().wpos_to_cpos();
645        let r_sqr = radius * radius;
646        LOCALITY
647            .into_iter()
648            .flat_map(move |neighbor| {
649                self.npc_grid.get(chunk_pos + neighbor).map(move |cell| {
650                    cell.npcs
651                        .iter()
652                        .copied()
653                        .filter(move |npc| {
654                            self.npcs
655                                .get(*npc)
656                                .is_some_and(|npc| npc.wpos.distance_squared(wpos) < r_sqr)
657                                && Some(*npc) != this_npc
658                        })
659                        .map(Actor::Npc)
660                })
661            })
662            .flatten()
663            .chain(
664                self.character_map
665                    .get(&chunk_pos)
666                    .map(|characters| {
667                        characters.iter().filter_map(move |(character, c_wpos)| {
668                            if c_wpos.distance_squared(wpos) < r_sqr {
669                                Some(Actor::Character(*character))
670                            } else {
671                                None
672                            }
673                        })
674                    })
675                    .into_iter()
676                    .flatten(),
677            )
678    }
679}
680
681impl Deref for Npcs {
682    type Target = HopSlotMap<NpcId, Npc>;
683
684    fn deref(&self) -> &Self::Target { &self.npcs }
685}
686
687impl DerefMut for Npcs {
688    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.npcs }
689}