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