1use crate::{
2 ai::Action,
3 data::{Reports, Sentiments, quest::Quest},
4 generate::name,
5};
6pub use common::rtsim::{NpcId, Profession};
7use common::{
8 character::CharacterId,
9 comp::{self, agent::FlightMode, item::ItemDef},
10 grid::Grid,
11 map::Marker,
12 resources::Time,
13 rtsim::{
14 Actor, Dialogue, DialogueId, DialogueKind, FactionId, NpcAction, NpcActivity, NpcInput,
15 NpcMsg, Personality, QuestId, ReportId, Response, Role, SiteId, TerrainResource,
16 },
17 store::Id,
18 terrain::CoordinateConversions,
19 util::Dir,
20};
21use hashbrown::{HashMap, HashSet};
22use rand::prelude::*;
23use serde::{Deserialize, Serialize};
24use slotmap::HopSlotMap;
25use std::{
26 collections::VecDeque,
27 ops::{Deref, DerefMut},
28 sync::{
29 Arc,
30 atomic::{AtomicU32, Ordering},
31 },
32};
33use tracing::error;
34use vek::*;
35use world::{
36 civ::Track,
37 site::Site as WorldSite,
38 util::{LOCALITY, RandomPerm},
39};
40
41#[derive(Copy, Clone, Debug, Default)]
42pub enum SimulationMode {
43 #[default]
45 Simulated,
46 Loaded,
48}
49
50#[derive(Clone)]
51pub struct PathData<P, N> {
52 pub end: N,
53 pub path: VecDeque<P>,
54 pub repoll: bool,
55}
56
57#[derive(Clone, Default)]
58pub struct PathingMemory {
59 pub intrasite_path: Option<(PathData<Vec2<i32>, Vec2<i32>>, Id<WorldSite>)>,
60 pub intersite_path: Option<(PathData<(Id<Track>, bool), SiteId>, usize)>,
61}
62
63#[derive(Default)]
64pub struct Controller {
65 pub actions: Vec<NpcAction>,
66 pub activity: Option<NpcActivity>,
67 pub new_home: Option<Option<SiteId>>,
68 pub look_dir: Option<Dir>,
69 pub job: Option<Job>,
70 pub quests_to_create: Vec<(QuestId, Quest)>,
71}
72
73impl Controller {
74 pub fn reset(&mut self, npc: &Npc) {
77 self.activity = None;
78 self.look_dir = None;
79 self.job = npc.job.clone();
80 }
81
82 pub fn do_idle(&mut self) { self.activity = None; }
83
84 pub fn do_talk(&mut self, tgt: Actor) { self.activity = Some(NpcActivity::Talk(tgt)); }
85
86 pub fn do_goto(&mut self, wpos: Vec3<f32>, speed_factor: f32) {
87 self.activity = Some(NpcActivity::Goto(wpos, speed_factor));
88 }
89
90 pub fn do_goto_with_height_and_dir(
92 &mut self,
93 wpos: Vec3<f32>,
94 speed_factor: f32,
95 height: Option<f32>,
96 dir: Option<Dir>,
97 flight_mode: FlightMode,
98 ) {
99 self.activity = Some(NpcActivity::GotoFlying(
100 wpos,
101 speed_factor,
102 height,
103 dir,
104 flight_mode,
105 ));
106 }
107
108 pub fn do_gather(&mut self, resources: &'static [TerrainResource]) {
109 self.activity = Some(NpcActivity::Gather(resources));
110 }
111
112 pub fn do_hunt_animals(&mut self) { self.activity = Some(NpcActivity::HuntAnimals); }
113
114 pub fn do_dance(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Dance(dir)); }
115
116 pub fn do_cheer(&mut self, dir: Option<Dir>) { self.activity = Some(NpcActivity::Cheer(dir)); }
117
118 pub fn do_sit(&mut self, dir: Option<Dir>, pos: Option<Vec3<i32>>) {
119 self.activity = Some(NpcActivity::Sit(dir, pos));
120 }
121
122 pub fn say(&mut self, target: impl Into<Option<Actor>>, content: comp::Content) {
123 self.actions.push(NpcAction::Say(target.into(), content));
124 }
125
126 pub fn attack(&mut self, target: impl Into<Actor>) {
127 self.actions.push(NpcAction::Attack(target.into()));
128 }
129
130 pub fn set_new_home(&mut self, new_home: impl Into<Option<SiteId>>) {
131 self.new_home = Some(new_home.into());
132 }
133
134 pub fn set_newly_hired(&mut self, actor: Actor, expires: Time) {
135 self.job = Some(Job::Hired(actor, expires));
136 }
137
138 pub fn end_hiring(&mut self) {
139 if matches!(self.job, Some(Job::Hired(..))) {
140 self.job = None;
141 }
142 }
143
144 pub fn end_quest(&mut self) {
145 if matches!(self.job, Some(Job::Quest(..))) {
146 self.job = None;
147 }
148 }
149
150 pub fn send_msg(&mut self, to: impl Into<Actor>, msg: NpcMsg) {
151 self.actions.push(NpcAction::Msg { to: to.into(), msg });
152 }
153
154 pub fn dialogue_start(&mut self, target: impl Into<Actor>) -> DialogueSession {
156 let target = target.into();
157
158 let session = DialogueSession {
159 target,
160 id: DialogueId(rand::rng().random()),
161 };
162
163 self.actions.push(NpcAction::Dialogue(target, Dialogue {
164 id: session.id,
165 kind: DialogueKind::Start,
166 }));
167
168 session
169 }
170
171 pub fn dialogue_end(&mut self, session: DialogueSession) {
173 self.actions
174 .push(NpcAction::Dialogue(session.target, Dialogue {
175 id: session.id,
176 kind: DialogueKind::End,
177 }));
178 }
179
180 pub fn dialogue_response(
181 &mut self,
182 session: DialogueSession,
183 tag: u32,
184 response: &(u16, Response),
185 ) {
186 self.actions
187 .push(NpcAction::Dialogue(session.target, Dialogue {
188 id: session.id,
189 kind: DialogueKind::Response {
190 tag,
191 response: response.1.clone(),
192 response_id: response.0,
193 },
194 }));
195 }
196
197 fn new_dialogue_tag(&self) -> u32 {
198 static TAG_COUNTER: AtomicU32 = AtomicU32::new(0);
199 TAG_COUNTER.fetch_add(1, Ordering::Relaxed)
200 }
201
202 pub fn dialogue_question(
205 &mut self,
206 session: DialogueSession,
207 msg: comp::Content,
208 responses: impl IntoIterator<Item = (u16, Response)>,
209 ) -> u32 {
210 let tag = self.new_dialogue_tag();
211
212 self.actions
213 .push(NpcAction::Dialogue(session.target, Dialogue {
214 id: session.id,
215 kind: DialogueKind::Question {
216 tag,
217 msg,
218 responses: responses.into_iter().collect(),
219 },
220 }));
221
222 tag
223 }
224
225 pub fn dialogue_statement(
228 &mut self,
229 session: DialogueSession,
230 msg: comp::Content,
231 given_item: Option<(Arc<ItemDef>, u32)>,
232 ) -> u32 {
233 let tag = self.new_dialogue_tag();
234
235 self.actions
236 .push(NpcAction::Dialogue(session.target, Dialogue {
237 id: session.id,
238 kind: DialogueKind::Statement {
239 msg,
240 given_item,
241 tag,
242 },
243 }));
244
245 tag
246 }
247
248 pub fn dialogue_marker(&mut self, session: DialogueSession, marker: Marker) {
250 self.actions
251 .push(NpcAction::Dialogue(session.target, Dialogue {
252 id: session.id,
253 kind: DialogueKind::Marker(marker),
254 }));
255 }
256}
257
258#[derive(Copy, Clone)]
260pub struct DialogueSession {
261 pub target: Actor,
262 pub id: DialogueId,
263}
264
265pub struct Brain {
266 pub action: Box<dyn Action<(), !>>,
267}
268
269#[derive(Serialize, Deserialize)]
270pub struct Npc {
271 pub uid: u64,
272 pub seed: u32,
274 pub wpos: Vec3<f32>,
276 pub dir: Vec2<f32>,
277
278 pub body: comp::Body,
279 pub role: Role,
280 pub home: Option<SiteId>,
281 pub faction: Option<FactionId>,
282 pub health_fraction: f32,
284
285 pub known_reports: HashSet<ReportId>,
287
288 #[serde(default)]
289 pub personality: Personality,
290 #[serde(default)]
291 pub sentiments: Sentiments,
292
293 #[serde(default)]
294 pub job: Option<Job>,
295
296 #[serde(skip)]
298 pub chunk_pos: Option<Vec2<i32>>,
299 #[serde(skip)]
300 pub current_site: Option<SiteId>,
301
302 #[serde(skip)]
303 pub controller: Controller,
304 #[serde(skip)]
305 pub inbox: VecDeque<NpcInput>,
306
307 #[serde(skip)]
312 pub mode: SimulationMode,
313
314 #[serde(skip)]
315 pub brain: Option<Brain>,
316}
317
318#[derive(Clone, Serialize, Deserialize, PartialEq)]
322pub enum Job {
323 Hired(Actor, Time),
326 Quest(QuestId),
328}
329
330impl Clone for Npc {
331 fn clone(&self) -> Self {
332 Self {
333 uid: self.uid,
334 seed: self.seed,
335 wpos: self.wpos,
336 dir: self.dir,
337 role: self.role.clone(),
338 home: self.home,
339 faction: self.faction,
340 health_fraction: self.health_fraction,
341 known_reports: self.known_reports.clone(),
342 body: self.body,
343 personality: self.personality,
344 sentiments: self.sentiments.clone(),
345 job: self.job.clone(),
346 chunk_pos: None,
348 current_site: Default::default(),
349 controller: Default::default(),
350 inbox: Default::default(),
351 mode: Default::default(),
352 brain: Default::default(),
353 }
354 }
355}
356
357impl Npc {
358 pub const PERM_ENTITY_CONFIG: u32 = 1;
359 const PERM_NAME: u32 = 0;
360
361 pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body, role: Role) -> Self {
362 Self {
363 uid: 0,
365 seed,
366 wpos,
367 dir: Vec2::unit_x(),
368 body,
369 personality: Default::default(),
370 sentiments: Default::default(),
371 job: None,
372 role,
373 home: None,
374 faction: None,
375 health_fraction: 1.0,
376 known_reports: Default::default(),
377 chunk_pos: None,
378 current_site: None,
379 controller: Default::default(),
380 inbox: Default::default(),
381 mode: SimulationMode::Simulated,
382 brain: None,
383 }
384 }
385
386 pub fn is_dead(&self) -> bool { self.health_fraction <= 0.0 }
387
388 pub fn with_personality(mut self, personality: Personality) -> Self {
390 self.personality = personality;
391 self
392 }
393
394 pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
406 self.home = home.into();
407 self
408 }
409
410 pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
412 self.faction = faction.into();
413 self
414 }
415
416 pub fn rng(&self, perm: u32) -> impl Rng + use<> {
417 RandomPerm::new(self.seed.wrapping_add(perm))
418 }
419
420 pub fn get_name(&self) -> Option<String> {
423 if let comp::Body::Humanoid(_) = &self.body {
424 Some(name::generate_npc(&mut self.rng(Self::PERM_NAME)))
425 } else {
426 None
427 }
428 }
429
430 pub fn profession(&self) -> Option<Profession> {
431 match &self.role {
432 Role::Civilised(profession) => *profession,
433 Role::Monster | Role::Wild | Role::Vehicle => None,
434 }
435 }
436
437 pub fn hired(&self) -> Option<(Actor, Time)> {
438 if let Some(Job::Hired(actor, time)) = self.job {
439 Some((actor, time))
440 } else {
441 None
442 }
443 }
444
445 pub fn cleanup(&mut self, reports: &Reports) {
446 self.sentiments
450 .cleanup(crate::data::sentiment::NPC_MAX_SENTIMENTS);
451 self.known_reports
453 .retain(|report| reports.contains_key(*report));
454 }
457}
458
459#[derive(Default, Clone, Serialize, Deserialize)]
460pub struct GridCell {
461 pub npcs: Vec<NpcId>,
462}
463
464#[derive(Clone, Serialize, Deserialize, Debug)]
465pub struct NpcLink {
466 pub mount: NpcId,
467 pub rider: Actor,
468 pub is_steering: bool,
469}
470
471#[derive(Clone, Default, Serialize, Deserialize)]
472struct Riders {
473 steerer: Option<MountId>,
474 riders: Vec<MountId>,
475}
476
477#[derive(Clone, Default, Serialize, Deserialize)]
478#[serde(
479 from = "HopSlotMap<MountId, NpcLink>",
480 into = "HopSlotMap<MountId, NpcLink>"
481)]
482pub struct NpcLinks {
483 links: HopSlotMap<MountId, NpcLink>,
484 mount_map: slotmap::SecondaryMap<NpcId, Riders>,
485 rider_map: HashMap<Actor, MountId>,
486}
487
488impl NpcLinks {
489 pub fn remove_mount(&mut self, mount: NpcId) {
490 if let Some(riders) = self.mount_map.remove(mount) {
491 for link in riders
492 .riders
493 .into_iter()
494 .chain(riders.steerer)
495 .filter_map(|link_id| self.links.get(link_id))
496 {
497 self.rider_map.remove(&link.rider);
498 }
499 }
500 }
501
502 fn remove_rider(&mut self, id: MountId, link: &NpcLink) {
504 if let Some(riders) = self.mount_map.get_mut(link.mount) {
505 if link.is_steering && riders.steerer == Some(id) {
506 riders.steerer = None;
507 } else if let Some((i, _)) = riders.riders.iter().enumerate().find(|(_, i)| **i == id) {
508 riders.riders.remove(i);
509 }
510
511 if riders.steerer.is_none() && riders.riders.is_empty() {
512 self.mount_map.remove(link.mount);
513 }
514 }
515 }
516
517 pub fn remove_link(&mut self, link_id: MountId) {
518 if let Some(link) = self.links.remove(link_id) {
519 self.rider_map.remove(&link.rider);
520 self.remove_rider(link_id, &link);
521 }
522 }
523
524 pub fn dismount(&mut self, rider: impl Into<Actor>) {
525 if let Some(id) = self.rider_map.remove(&rider.into())
526 && let Some(link) = self.links.remove(id)
527 {
528 self.remove_rider(id, &link);
529 }
530 }
531
532 pub fn add_mounting(
535 &mut self,
536 mount: NpcId,
537 rider: impl Into<Actor>,
538 steering: bool,
539 ) -> Result<MountId, MountingError> {
540 let rider = rider.into();
541 if Actor::Npc(mount) == rider {
542 return Err(MountingError::MountSelf);
543 }
544 if let Actor::Npc(rider) = rider
545 && self.mount_map.contains_key(rider)
546 {
547 return Err(MountingError::RiderIsMounted);
548 }
549 if self.rider_map.contains_key(&Actor::Npc(mount)) {
550 return Err(MountingError::MountIsRiding);
551 }
552 if let Some(mount_entry) = self.mount_map.entry(mount) {
553 if let hashbrown::hash_map::Entry::Vacant(rider_entry) = self.rider_map.entry(rider) {
554 let riders = mount_entry.or_insert(Riders::default());
555
556 if steering {
557 if riders.steerer.is_none() {
558 let id = self.links.insert(NpcLink {
559 mount,
560 rider,
561 is_steering: true,
562 });
563 riders.steerer = Some(id);
564 rider_entry.insert(id);
565 Ok(id)
566 } else {
567 Err(MountingError::HasSteerer)
568 }
569 } else {
570 let id = self.links.insert(NpcLink {
572 mount,
573 rider,
574 is_steering: false,
575 });
576 riders.riders.push(id);
577 rider_entry.insert(id);
578 Ok(id)
579 }
580 } else {
581 Err(MountingError::AlreadyRiding)
582 }
583 } else {
584 Err(MountingError::MountDead)
585 }
586 }
587
588 pub fn steer(
589 &mut self,
590 mount: NpcId,
591 rider: impl Into<Actor>,
592 ) -> Result<MountId, MountingError> {
593 self.add_mounting(mount, rider, true)
594 }
595
596 pub fn ride(
597 &mut self,
598 mount: NpcId,
599 rider: impl Into<Actor>,
600 ) -> Result<MountId, MountingError> {
601 self.add_mounting(mount, rider, false)
602 }
603
604 pub fn get_mount_link(&self, rider: impl Into<Actor>) -> Option<&NpcLink> {
605 self.rider_map
606 .get(&rider.into())
607 .and_then(|link| self.links.get(*link))
608 }
609
610 pub fn get_steerer_link(&self, mount: NpcId) -> Option<&NpcLink> {
611 self.mount_map
612 .get(mount)
613 .and_then(|mount| self.links.get(mount.steerer?))
614 }
615
616 pub fn get(&self, id: MountId) -> Option<&NpcLink> { self.links.get(id) }
617
618 pub fn ids(&self) -> impl Iterator<Item = MountId> + '_ { self.links.keys() }
619
620 pub fn iter(&self) -> impl Iterator<Item = &NpcLink> + '_ { self.links.values() }
621
622 pub fn iter_mounts(&self) -> impl Iterator<Item = NpcId> + '_ { self.mount_map.keys() }
623}
624
625impl From<HopSlotMap<MountId, NpcLink>> for NpcLinks {
626 fn from(mut value: HopSlotMap<MountId, NpcLink>) -> Self {
627 let mut from_map = slotmap::SecondaryMap::new();
628 let mut to_map = HashMap::with_capacity(value.len());
629 let mut delete = Vec::new();
630 for (id, link) in value.iter() {
631 if let Some(entry) = from_map.entry(link.mount) {
632 let riders = entry.or_insert(Riders::default());
633 if link.is_steering {
634 if let Some(old) = riders.steerer.replace(id) {
635 error!("Replaced steerer {old:?} with {id:?}");
636 }
637 } else {
638 riders.riders.push(id);
639 }
640 } else {
641 delete.push(id);
642 }
643 to_map.insert(link.rider, id);
644 }
645 for id in delete {
646 value.remove(id);
647 }
648 Self {
649 links: value,
650 mount_map: from_map,
651 rider_map: to_map,
652 }
653 }
654}
655
656impl From<NpcLinks> for HopSlotMap<MountId, NpcLink> {
657 fn from(other: NpcLinks) -> Self { other.links }
658}
659slotmap::new_key_type! {
660 pub struct MountId;
661}
662
663#[derive(Clone, Serialize, Deserialize)]
664pub struct MountData {
665 is_steering: bool,
666}
667
668#[derive(Clone, Serialize, Deserialize)]
669pub struct Npcs {
670 pub uid_counter: u64,
671 pub npcs: HopSlotMap<NpcId, Npc>,
672 pub mounts: NpcLinks,
673 #[serde(skip, default = "construct_npc_grid")]
676 pub npc_grid: Grid<GridCell>,
677 #[serde(skip)]
678 pub character_map: HashMap<Vec2<i32>, Vec<(CharacterId, Vec3<f32>)>>,
679}
680
681impl Default for Npcs {
682 fn default() -> Self {
683 Self {
684 uid_counter: 0,
685 npcs: Default::default(),
686 mounts: Default::default(),
687 npc_grid: construct_npc_grid(),
688 character_map: Default::default(),
689 }
690 }
691}
692
693fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) }
694
695#[derive(Debug)]
696pub enum MountingError {
697 MountDead,
698 RiderDead,
699 HasSteerer,
700 AlreadyRiding,
701 MountIsRiding,
702 RiderIsMounted,
703 MountSelf,
704}
705
706impl Npcs {
707 pub fn create_npc(&mut self, mut npc: Npc) -> NpcId {
708 npc.uid = self.uid_counter;
709 self.uid_counter += 1;
710 self.npcs.insert(npc)
711 }
712
713 pub fn nearby(
717 &self,
718 this_npc: Option<NpcId>,
719 wpos: Vec3<f32>,
720 radius: f32,
721 ) -> impl Iterator<Item = Actor> + '_ {
722 let chunk_pos = wpos.xy().as_().wpos_to_cpos();
723 let r_sqr = radius * radius;
724 LOCALITY
725 .into_iter()
726 .flat_map(move |neighbor| {
727 self.npc_grid.get(chunk_pos + neighbor).map(move |cell| {
728 cell.npcs
729 .iter()
730 .copied()
731 .filter(move |npc| {
732 self.npcs
733 .get(*npc)
734 .is_some_and(|npc| npc.wpos.distance_squared(wpos) < r_sqr)
735 && Some(*npc) != this_npc
736 })
737 .map(Actor::Npc)
738 })
739 })
740 .flatten()
741 .chain(
742 self.character_map
743 .get(&chunk_pos)
744 .map(|characters| {
745 characters.iter().filter_map(move |(character, c_wpos)| {
746 if c_wpos.distance_squared(wpos) < r_sqr {
747 Some(Actor::Character(*character))
748 } else {
749 None
750 }
751 })
752 })
753 .into_iter()
754 .flatten(),
755 )
756 }
757}
758
759impl Deref for Npcs {
760 type Target = HopSlotMap<NpcId, Npc>;
761
762 fn deref(&self) -> &Self::Target { &self.npcs }
763}
764
765impl DerefMut for Npcs {
766 fn deref_mut(&mut self) -> &mut Self::Target { &mut self.npcs }
767}