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