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