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
190// Represents an ongoing dialogue with another actor.
191#[derive(Copy, Clone)]
192pub struct DialogueSession {
193    pub target: Actor,
194    pub id: DialogueId,
195}
196
197pub struct Brain {
198    pub action: Box<dyn Action<(), !>>,
199}
200
201#[derive(Serialize, Deserialize)]
202pub struct Npc {
203    pub uid: u64,
204    // Persisted state
205    pub seed: u32,
206    /// Represents the location of the NPC.
207    pub wpos: Vec3<f32>,
208    pub dir: Vec2<f32>,
209
210    pub body: comp::Body,
211    pub role: Role,
212    pub home: Option<SiteId>,
213    pub faction: Option<FactionId>,
214    /// The current health of the NPC, < 0.0 is dead and 1.0 is max.
215    pub health_fraction: f32,
216
217    /// The [`Report`]s that the NPC is aware of.
218    pub known_reports: HashSet<ReportId>,
219
220    #[serde(default)]
221    pub personality: Personality,
222    #[serde(default)]
223    pub sentiments: Sentiments,
224
225    /// An NPC can temporarily become a hired hand (`(hiring_actor,
226    /// termination_time)`).
227    #[serde(default)]
228    pub hiring: Option<(Actor, Time)>,
229
230    // Unpersisted state
231    #[serde(skip)]
232    pub chunk_pos: Option<Vec2<i32>>,
233    #[serde(skip)]
234    pub current_site: Option<SiteId>,
235
236    #[serde(skip)]
237    pub controller: Controller,
238    #[serde(skip)]
239    pub inbox: VecDeque<NpcInput>,
240
241    /// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
242    /// server, loaded corresponds to being within a loaded chunk). When in
243    /// loaded mode, the interactions of the NPC should not be simulated but
244    /// should instead be derived from the game.
245    #[serde(skip)]
246    pub mode: SimulationMode,
247
248    #[serde(skip)]
249    pub brain: Option<Brain>,
250}
251
252impl Clone for Npc {
253    fn clone(&self) -> Self {
254        Self {
255            uid: self.uid,
256            seed: self.seed,
257            wpos: self.wpos,
258            dir: self.dir,
259            role: self.role.clone(),
260            home: self.home,
261            faction: self.faction,
262            health_fraction: self.health_fraction,
263            known_reports: self.known_reports.clone(),
264            body: self.body,
265            personality: self.personality,
266            sentiments: self.sentiments.clone(),
267            hiring: self.hiring,
268            // Not persisted
269            chunk_pos: None,
270            current_site: Default::default(),
271            controller: Default::default(),
272            inbox: Default::default(),
273            mode: Default::default(),
274            brain: Default::default(),
275        }
276    }
277}
278
279impl Npc {
280    pub const PERM_ENTITY_CONFIG: u32 = 1;
281    const PERM_NAME: u32 = 0;
282
283    pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body, role: Role) -> Self {
284        Self {
285            // To be assigned later
286            uid: 0,
287            seed,
288            wpos,
289            dir: Vec2::unit_x(),
290            body,
291            personality: Default::default(),
292            sentiments: Default::default(),
293            hiring: None,
294            role,
295            home: None,
296            faction: None,
297            health_fraction: 1.0,
298            known_reports: Default::default(),
299            chunk_pos: None,
300            current_site: None,
301            controller: Default::default(),
302            inbox: Default::default(),
303            mode: SimulationMode::Simulated,
304            brain: None,
305        }
306    }
307
308    pub fn is_dead(&self) -> bool { self.health_fraction <= 0.0 }
309
310    // TODO: have a dedicated `NpcBuilder` type for this.
311    pub fn with_personality(mut self, personality: Personality) -> Self {
312        self.personality = personality;
313        self
314    }
315
316    // // TODO: have a dedicated `NpcBuilder` type for this.
317    // pub fn with_profession(mut self, profession: impl Into<Option<Profession>>)
318    // -> Self {     if let Role::Humanoid(p) = &mut self.role {
319    //         *p = profession.into();
320    //     } else {
321    //         panic!("Tried to assign profession {:?} to NPC, but has role {:?},
322    // which cannot have a profession", profession.into(), self.role);     }
323    //     self
324    // }
325
326    // TODO: have a dedicated `NpcBuilder` type for this.
327    pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
328        self.home = home.into();
329        self
330    }
331
332    // TODO: have a dedicated `NpcBuilder` type for this.
333    pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
334        self.faction = faction.into();
335        self
336    }
337
338    pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed.wrapping_add(perm)) }
339
340    // TODO: Don't make this depend on deterministic RNG, actually persist names
341    // once we've decided that we want to
342    pub fn get_name(&self) -> String { name::generate(&mut self.rng(Self::PERM_NAME)) }
343
344    pub fn profession(&self) -> Option<Profession> {
345        match &self.role {
346            Role::Civilised(profession) => profession.clone(),
347            Role::Monster | Role::Wild | Role::Vehicle => None,
348        }
349    }
350
351    pub fn cleanup(&mut self, reports: &Reports) {
352        // Clear old or superfluous sentiments
353        // TODO: It might be worth giving more important NPCs a higher sentiment
354        // 'budget' than less important ones.
355        self.sentiments
356            .cleanup(crate::data::sentiment::NPC_MAX_SENTIMENTS);
357        // Clear reports that have been forgotten
358        self.known_reports
359            .retain(|report| reports.contains_key(*report));
360        // TODO: Limit number of reports
361        // TODO: Clear old inbox items
362    }
363}
364
365#[derive(Default, Clone, Serialize, Deserialize)]
366pub struct GridCell {
367    pub npcs: Vec<NpcId>,
368}
369
370#[derive(Clone, Serialize, Deserialize, Debug)]
371pub struct NpcLink {
372    pub mount: NpcId,
373    pub rider: Actor,
374    pub is_steering: bool,
375}
376
377#[derive(Clone, Default, Serialize, Deserialize)]
378struct Riders {
379    steerer: Option<MountId>,
380    riders: Vec<MountId>,
381}
382
383#[derive(Clone, Default, Serialize, Deserialize)]
384#[serde(
385    from = "HopSlotMap<MountId, NpcLink>",
386    into = "HopSlotMap<MountId, NpcLink>"
387)]
388pub struct NpcLinks {
389    links: HopSlotMap<MountId, NpcLink>,
390    mount_map: slotmap::SecondaryMap<NpcId, Riders>,
391    rider_map: HashMap<Actor, MountId>,
392}
393
394impl NpcLinks {
395    pub fn remove_mount(&mut self, mount: NpcId) {
396        if let Some(riders) = self.mount_map.remove(mount) {
397            for link in riders
398                .riders
399                .into_iter()
400                .chain(riders.steerer)
401                .filter_map(|link_id| self.links.get(link_id))
402            {
403                self.rider_map.remove(&link.rider);
404            }
405        }
406    }
407
408    /// Internal function, only removes from `mount_map`.
409    fn remove_rider(&mut self, id: MountId, link: &NpcLink) {
410        if let Some(riders) = self.mount_map.get_mut(link.mount) {
411            if link.is_steering && riders.steerer == Some(id) {
412                riders.steerer = None;
413            } else if let Some((i, _)) = riders.riders.iter().enumerate().find(|(_, i)| **i == id) {
414                riders.riders.remove(i);
415            }
416
417            if riders.steerer.is_none() && riders.riders.is_empty() {
418                self.mount_map.remove(link.mount);
419            }
420        }
421    }
422
423    pub fn remove_link(&mut self, link_id: MountId) {
424        if let Some(link) = self.links.remove(link_id) {
425            self.rider_map.remove(&link.rider);
426            self.remove_rider(link_id, &link);
427        }
428    }
429
430    pub fn dismount(&mut self, rider: impl Into<Actor>) {
431        if let Some(id) = self.rider_map.remove(&rider.into()) {
432            if let Some(link) = self.links.remove(id) {
433                self.remove_rider(id, &link);
434            }
435        }
436    }
437
438    // This is the only function to actually add a mount link.
439    // And it ensures that there isn't link chaining
440    pub fn add_mounting(
441        &mut self,
442        mount: NpcId,
443        rider: impl Into<Actor>,
444        steering: bool,
445    ) -> Result<MountId, MountingError> {
446        let rider = rider.into();
447        if Actor::Npc(mount) == rider {
448            return Err(MountingError::MountSelf);
449        }
450        if let Actor::Npc(rider) = rider
451            && self.mount_map.contains_key(rider)
452        {
453            return Err(MountingError::RiderIsMounted);
454        }
455        if self.rider_map.contains_key(&Actor::Npc(mount)) {
456            return Err(MountingError::MountIsRiding);
457        }
458        if let Some(mount_entry) = self.mount_map.entry(mount) {
459            if let hashbrown::hash_map::Entry::Vacant(rider_entry) = self.rider_map.entry(rider) {
460                let riders = mount_entry.or_insert(Riders::default());
461
462                if steering {
463                    if riders.steerer.is_none() {
464                        let id = self.links.insert(NpcLink {
465                            mount,
466                            rider,
467                            is_steering: true,
468                        });
469                        riders.steerer = Some(id);
470                        rider_entry.insert(id);
471                        Ok(id)
472                    } else {
473                        Err(MountingError::HasSteerer)
474                    }
475                } else {
476                    // TODO: Maybe have some limit on the number of riders depending on the mount?
477                    let id = self.links.insert(NpcLink {
478                        mount,
479                        rider,
480                        is_steering: false,
481                    });
482                    riders.riders.push(id);
483                    rider_entry.insert(id);
484                    Ok(id)
485                }
486            } else {
487                Err(MountingError::AlreadyRiding)
488            }
489        } else {
490            Err(MountingError::MountDead)
491        }
492    }
493
494    pub fn steer(
495        &mut self,
496        mount: NpcId,
497        rider: impl Into<Actor>,
498    ) -> Result<MountId, MountingError> {
499        self.add_mounting(mount, rider, true)
500    }
501
502    pub fn ride(
503        &mut self,
504        mount: NpcId,
505        rider: impl Into<Actor>,
506    ) -> Result<MountId, MountingError> {
507        self.add_mounting(mount, rider, false)
508    }
509
510    pub fn get_mount_link(&self, rider: impl Into<Actor>) -> Option<&NpcLink> {
511        self.rider_map
512            .get(&rider.into())
513            .and_then(|link| self.links.get(*link))
514    }
515
516    pub fn get_steerer_link(&self, mount: NpcId) -> Option<&NpcLink> {
517        self.mount_map
518            .get(mount)
519            .and_then(|mount| self.links.get(mount.steerer?))
520    }
521
522    pub fn get(&self, id: MountId) -> Option<&NpcLink> { self.links.get(id) }
523
524    pub fn ids(&self) -> impl Iterator<Item = MountId> + '_ { self.links.keys() }
525
526    pub fn iter(&self) -> impl Iterator<Item = &NpcLink> + '_ { self.links.values() }
527
528    pub fn iter_mounts(&self) -> impl Iterator<Item = NpcId> + '_ { self.mount_map.keys() }
529}
530
531impl From<HopSlotMap<MountId, NpcLink>> for NpcLinks {
532    fn from(mut value: HopSlotMap<MountId, NpcLink>) -> Self {
533        let mut from_map = slotmap::SecondaryMap::new();
534        let mut to_map = HashMap::with_capacity(value.len());
535        let mut delete = Vec::new();
536        for (id, link) in value.iter() {
537            if let Some(entry) = from_map.entry(link.mount) {
538                let riders = entry.or_insert(Riders::default());
539                if link.is_steering {
540                    if let Some(old) = riders.steerer.replace(id) {
541                        error!("Replaced steerer {old:?} with {id:?}");
542                    }
543                } else {
544                    riders.riders.push(id);
545                }
546            } else {
547                delete.push(id);
548            }
549            to_map.insert(link.rider, id);
550        }
551        for id in delete {
552            value.remove(id);
553        }
554        Self {
555            links: value,
556            mount_map: from_map,
557            rider_map: to_map,
558        }
559    }
560}
561
562impl From<NpcLinks> for HopSlotMap<MountId, NpcLink> {
563    fn from(other: NpcLinks) -> Self { other.links }
564}
565slotmap::new_key_type! {
566    pub struct MountId;
567}
568
569#[derive(Clone, Serialize, Deserialize)]
570pub struct MountData {
571    is_steering: bool,
572}
573
574#[derive(Clone, Serialize, Deserialize)]
575pub struct Npcs {
576    pub uid_counter: u64,
577    pub npcs: HopSlotMap<NpcId, Npc>,
578    pub mounts: NpcLinks,
579    // TODO: This feels like it should be its own rtsim resource
580    // TODO: Consider switching to `common::util::SpatialGrid` instead
581    #[serde(skip, default = "construct_npc_grid")]
582    pub npc_grid: Grid<GridCell>,
583    #[serde(skip)]
584    pub character_map: HashMap<Vec2<i32>, Vec<(CharacterId, Vec3<f32>)>>,
585}
586
587impl Default for Npcs {
588    fn default() -> Self {
589        Self {
590            uid_counter: 0,
591            npcs: Default::default(),
592            mounts: Default::default(),
593            npc_grid: construct_npc_grid(),
594            character_map: Default::default(),
595        }
596    }
597}
598
599fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) }
600
601#[derive(Debug)]
602pub enum MountingError {
603    MountDead,
604    RiderDead,
605    HasSteerer,
606    AlreadyRiding,
607    MountIsRiding,
608    RiderIsMounted,
609    MountSelf,
610}
611
612impl Npcs {
613    pub fn create_npc(&mut self, mut npc: Npc) -> NpcId {
614        npc.uid = self.uid_counter;
615        self.uid_counter += 1;
616        self.npcs.insert(npc)
617    }
618
619    /// Queries nearby npcs, not garantueed to work if radius > 32.0
620    // TODO: Find a more efficient way to implement this, it's currently
621    // (theoretically) O(n^2).
622    pub fn nearby(
623        &self,
624        this_npc: Option<NpcId>,
625        wpos: Vec3<f32>,
626        radius: f32,
627    ) -> impl Iterator<Item = Actor> + '_ {
628        let chunk_pos = wpos.xy().as_().wpos_to_cpos();
629        let r_sqr = radius * radius;
630        LOCALITY
631            .into_iter()
632            .flat_map(move |neighbor| {
633                self.npc_grid.get(chunk_pos + neighbor).map(move |cell| {
634                    cell.npcs
635                        .iter()
636                        .copied()
637                        .filter(move |npc| {
638                            self.npcs
639                                .get(*npc)
640                                .is_some_and(|npc| npc.wpos.distance_squared(wpos) < r_sqr)
641                                && Some(*npc) != this_npc
642                        })
643                        .map(Actor::Npc)
644                })
645            })
646            .flatten()
647            .chain(
648                self.character_map
649                    .get(&chunk_pos)
650                    .map(|characters| {
651                        characters.iter().filter_map(move |(character, c_wpos)| {
652                            if c_wpos.distance_squared(wpos) < r_sqr {
653                                Some(Actor::Character(*character))
654                            } else {
655                                None
656                            }
657                        })
658                    })
659                    .into_iter()
660                    .flatten(),
661            )
662    }
663}
664
665impl Deref for Npcs {
666    type Target = HopSlotMap<NpcId, Npc>;
667
668    fn deref(&self) -> &Self::Target { &self.npcs }
669}
670
671impl DerefMut for Npcs {
672    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.npcs }
673}