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 #[default]
40 Simulated,
41 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 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 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 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 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 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 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 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#[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 pub seed: u32,
220 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 pub health_fraction: f32,
230
231 pub known_reports: HashSet<ReportId>,
233
234 #[serde(default)]
235 pub personality: Personality,
236 #[serde(default)]
237 pub sentiments: Sentiments,
238
239 #[serde(default)]
242 pub hiring: Option<(Actor, Time)>,
243
244 #[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 #[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 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 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 pub fn with_personality(mut self, personality: Personality) -> Self {
326 self.personality = personality;
327 self
328 }
329
330 pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
342 self.home = home.into();
343 self
344 }
345
346 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 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 self.sentiments
372 .cleanup(crate::data::sentiment::NPC_MAX_SENTIMENTS);
373 self.known_reports
375 .retain(|report| reports.contains_key(*report));
376 }
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 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 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 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 #[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 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}