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
190#[derive(Copy, Clone)]
192pub struct DialogueSession {
193 pub target: Actor,
194 pub id: DialogueId,
195}
196
197pub struct Brain {
198 pub action: Box<dyn Action<(), !>>,
199}
200
201#[derive(Serialize, Deserialize)]
202pub struct Npc {
203 pub uid: u64,
204 pub seed: u32,
206 pub wpos: Vec3<f32>,
208 pub dir: Vec2<f32>,
209
210 pub body: comp::Body,
211 pub role: Role,
212 pub home: Option<SiteId>,
213 pub faction: Option<FactionId>,
214 pub health_fraction: f32,
216
217 pub known_reports: HashSet<ReportId>,
219
220 #[serde(default)]
221 pub personality: Personality,
222 #[serde(default)]
223 pub sentiments: Sentiments,
224
225 #[serde(default)]
228 pub hiring: Option<(Actor, Time)>,
229
230 #[serde(skip)]
232 pub chunk_pos: Option<Vec2<i32>>,
233 #[serde(skip)]
234 pub current_site: Option<SiteId>,
235
236 #[serde(skip)]
237 pub controller: Controller,
238 #[serde(skip)]
239 pub inbox: VecDeque<NpcInput>,
240
241 #[serde(skip)]
246 pub mode: SimulationMode,
247
248 #[serde(skip)]
249 pub brain: Option<Brain>,
250}
251
252impl Clone for Npc {
253 fn clone(&self) -> Self {
254 Self {
255 uid: self.uid,
256 seed: self.seed,
257 wpos: self.wpos,
258 dir: self.dir,
259 role: self.role.clone(),
260 home: self.home,
261 faction: self.faction,
262 health_fraction: self.health_fraction,
263 known_reports: self.known_reports.clone(),
264 body: self.body,
265 personality: self.personality,
266 sentiments: self.sentiments.clone(),
267 hiring: self.hiring,
268 chunk_pos: None,
270 current_site: Default::default(),
271 controller: Default::default(),
272 inbox: Default::default(),
273 mode: Default::default(),
274 brain: Default::default(),
275 }
276 }
277}
278
279impl Npc {
280 pub const PERM_ENTITY_CONFIG: u32 = 1;
281 const PERM_NAME: u32 = 0;
282
283 pub fn new(seed: u32, wpos: Vec3<f32>, body: comp::Body, role: Role) -> Self {
284 Self {
285 uid: 0,
287 seed,
288 wpos,
289 dir: Vec2::unit_x(),
290 body,
291 personality: Default::default(),
292 sentiments: Default::default(),
293 hiring: None,
294 role,
295 home: None,
296 faction: None,
297 health_fraction: 1.0,
298 known_reports: Default::default(),
299 chunk_pos: None,
300 current_site: None,
301 controller: Default::default(),
302 inbox: Default::default(),
303 mode: SimulationMode::Simulated,
304 brain: None,
305 }
306 }
307
308 pub fn is_dead(&self) -> bool { self.health_fraction <= 0.0 }
309
310 pub fn with_personality(mut self, personality: Personality) -> Self {
312 self.personality = personality;
313 self
314 }
315
316 pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
328 self.home = home.into();
329 self
330 }
331
332 pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
334 self.faction = faction.into();
335 self
336 }
337
338 pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed.wrapping_add(perm)) }
339
340 pub fn get_name(&self) -> String { name::generate(&mut self.rng(Self::PERM_NAME)) }
343
344 pub fn profession(&self) -> Option<Profession> {
345 match &self.role {
346 Role::Civilised(profession) => profession.clone(),
347 Role::Monster | Role::Wild | Role::Vehicle => None,
348 }
349 }
350
351 pub fn cleanup(&mut self, reports: &Reports) {
352 self.sentiments
356 .cleanup(crate::data::sentiment::NPC_MAX_SENTIMENTS);
357 self.known_reports
359 .retain(|report| reports.contains_key(*report));
360 }
363}
364
365#[derive(Default, Clone, Serialize, Deserialize)]
366pub struct GridCell {
367 pub npcs: Vec<NpcId>,
368}
369
370#[derive(Clone, Serialize, Deserialize, Debug)]
371pub struct NpcLink {
372 pub mount: NpcId,
373 pub rider: Actor,
374 pub is_steering: bool,
375}
376
377#[derive(Clone, Default, Serialize, Deserialize)]
378struct Riders {
379 steerer: Option<MountId>,
380 riders: Vec<MountId>,
381}
382
383#[derive(Clone, Default, Serialize, Deserialize)]
384#[serde(
385 from = "HopSlotMap<MountId, NpcLink>",
386 into = "HopSlotMap<MountId, NpcLink>"
387)]
388pub struct NpcLinks {
389 links: HopSlotMap<MountId, NpcLink>,
390 mount_map: slotmap::SecondaryMap<NpcId, Riders>,
391 rider_map: HashMap<Actor, MountId>,
392}
393
394impl NpcLinks {
395 pub fn remove_mount(&mut self, mount: NpcId) {
396 if let Some(riders) = self.mount_map.remove(mount) {
397 for link in riders
398 .riders
399 .into_iter()
400 .chain(riders.steerer)
401 .filter_map(|link_id| self.links.get(link_id))
402 {
403 self.rider_map.remove(&link.rider);
404 }
405 }
406 }
407
408 fn remove_rider(&mut self, id: MountId, link: &NpcLink) {
410 if let Some(riders) = self.mount_map.get_mut(link.mount) {
411 if link.is_steering && riders.steerer == Some(id) {
412 riders.steerer = None;
413 } else if let Some((i, _)) = riders.riders.iter().enumerate().find(|(_, i)| **i == id) {
414 riders.riders.remove(i);
415 }
416
417 if riders.steerer.is_none() && riders.riders.is_empty() {
418 self.mount_map.remove(link.mount);
419 }
420 }
421 }
422
423 pub fn remove_link(&mut self, link_id: MountId) {
424 if let Some(link) = self.links.remove(link_id) {
425 self.rider_map.remove(&link.rider);
426 self.remove_rider(link_id, &link);
427 }
428 }
429
430 pub fn dismount(&mut self, rider: impl Into<Actor>) {
431 if let Some(id) = self.rider_map.remove(&rider.into()) {
432 if let Some(link) = self.links.remove(id) {
433 self.remove_rider(id, &link);
434 }
435 }
436 }
437
438 pub fn add_mounting(
441 &mut self,
442 mount: NpcId,
443 rider: impl Into<Actor>,
444 steering: bool,
445 ) -> Result<MountId, MountingError> {
446 let rider = rider.into();
447 if Actor::Npc(mount) == rider {
448 return Err(MountingError::MountSelf);
449 }
450 if let Actor::Npc(rider) = rider
451 && self.mount_map.contains_key(rider)
452 {
453 return Err(MountingError::RiderIsMounted);
454 }
455 if self.rider_map.contains_key(&Actor::Npc(mount)) {
456 return Err(MountingError::MountIsRiding);
457 }
458 if let Some(mount_entry) = self.mount_map.entry(mount) {
459 if let hashbrown::hash_map::Entry::Vacant(rider_entry) = self.rider_map.entry(rider) {
460 let riders = mount_entry.or_insert(Riders::default());
461
462 if steering {
463 if riders.steerer.is_none() {
464 let id = self.links.insert(NpcLink {
465 mount,
466 rider,
467 is_steering: true,
468 });
469 riders.steerer = Some(id);
470 rider_entry.insert(id);
471 Ok(id)
472 } else {
473 Err(MountingError::HasSteerer)
474 }
475 } else {
476 let id = self.links.insert(NpcLink {
478 mount,
479 rider,
480 is_steering: false,
481 });
482 riders.riders.push(id);
483 rider_entry.insert(id);
484 Ok(id)
485 }
486 } else {
487 Err(MountingError::AlreadyRiding)
488 }
489 } else {
490 Err(MountingError::MountDead)
491 }
492 }
493
494 pub fn steer(
495 &mut self,
496 mount: NpcId,
497 rider: impl Into<Actor>,
498 ) -> Result<MountId, MountingError> {
499 self.add_mounting(mount, rider, true)
500 }
501
502 pub fn ride(
503 &mut self,
504 mount: NpcId,
505 rider: impl Into<Actor>,
506 ) -> Result<MountId, MountingError> {
507 self.add_mounting(mount, rider, false)
508 }
509
510 pub fn get_mount_link(&self, rider: impl Into<Actor>) -> Option<&NpcLink> {
511 self.rider_map
512 .get(&rider.into())
513 .and_then(|link| self.links.get(*link))
514 }
515
516 pub fn get_steerer_link(&self, mount: NpcId) -> Option<&NpcLink> {
517 self.mount_map
518 .get(mount)
519 .and_then(|mount| self.links.get(mount.steerer?))
520 }
521
522 pub fn get(&self, id: MountId) -> Option<&NpcLink> { self.links.get(id) }
523
524 pub fn ids(&self) -> impl Iterator<Item = MountId> + '_ { self.links.keys() }
525
526 pub fn iter(&self) -> impl Iterator<Item = &NpcLink> + '_ { self.links.values() }
527
528 pub fn iter_mounts(&self) -> impl Iterator<Item = NpcId> + '_ { self.mount_map.keys() }
529}
530
531impl From<HopSlotMap<MountId, NpcLink>> for NpcLinks {
532 fn from(mut value: HopSlotMap<MountId, NpcLink>) -> Self {
533 let mut from_map = slotmap::SecondaryMap::new();
534 let mut to_map = HashMap::with_capacity(value.len());
535 let mut delete = Vec::new();
536 for (id, link) in value.iter() {
537 if let Some(entry) = from_map.entry(link.mount) {
538 let riders = entry.or_insert(Riders::default());
539 if link.is_steering {
540 if let Some(old) = riders.steerer.replace(id) {
541 error!("Replaced steerer {old:?} with {id:?}");
542 }
543 } else {
544 riders.riders.push(id);
545 }
546 } else {
547 delete.push(id);
548 }
549 to_map.insert(link.rider, id);
550 }
551 for id in delete {
552 value.remove(id);
553 }
554 Self {
555 links: value,
556 mount_map: from_map,
557 rider_map: to_map,
558 }
559 }
560}
561
562impl From<NpcLinks> for HopSlotMap<MountId, NpcLink> {
563 fn from(other: NpcLinks) -> Self { other.links }
564}
565slotmap::new_key_type! {
566 pub struct MountId;
567}
568
569#[derive(Clone, Serialize, Deserialize)]
570pub struct MountData {
571 is_steering: bool,
572}
573
574#[derive(Clone, Serialize, Deserialize)]
575pub struct Npcs {
576 pub uid_counter: u64,
577 pub npcs: HopSlotMap<NpcId, Npc>,
578 pub mounts: NpcLinks,
579 #[serde(skip, default = "construct_npc_grid")]
582 pub npc_grid: Grid<GridCell>,
583 #[serde(skip)]
584 pub character_map: HashMap<Vec2<i32>, Vec<(CharacterId, Vec3<f32>)>>,
585}
586
587impl Default for Npcs {
588 fn default() -> Self {
589 Self {
590 uid_counter: 0,
591 npcs: Default::default(),
592 mounts: Default::default(),
593 npc_grid: construct_npc_grid(),
594 character_map: Default::default(),
595 }
596 }
597}
598
599fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) }
600
601#[derive(Debug)]
602pub enum MountingError {
603 MountDead,
604 RiderDead,
605 HasSteerer,
606 AlreadyRiding,
607 MountIsRiding,
608 RiderIsMounted,
609 MountSelf,
610}
611
612impl Npcs {
613 pub fn create_npc(&mut self, mut npc: Npc) -> NpcId {
614 npc.uid = self.uid_counter;
615 self.uid_counter += 1;
616 self.npcs.insert(npc)
617 }
618
619 pub fn nearby(
623 &self,
624 this_npc: Option<NpcId>,
625 wpos: Vec3<f32>,
626 radius: f32,
627 ) -> impl Iterator<Item = Actor> + '_ {
628 let chunk_pos = wpos.xy().as_().wpos_to_cpos();
629 let r_sqr = radius * radius;
630 LOCALITY
631 .into_iter()
632 .flat_map(move |neighbor| {
633 self.npc_grid.get(chunk_pos + neighbor).map(move |cell| {
634 cell.npcs
635 .iter()
636 .copied()
637 .filter(move |npc| {
638 self.npcs
639 .get(*npc)
640 .is_some_and(|npc| npc.wpos.distance_squared(wpos) < r_sqr)
641 && Some(*npc) != this_npc
642 })
643 .map(Actor::Npc)
644 })
645 })
646 .flatten()
647 .chain(
648 self.character_map
649 .get(&chunk_pos)
650 .map(|characters| {
651 characters.iter().filter_map(move |(character, c_wpos)| {
652 if c_wpos.distance_squared(wpos) < r_sqr {
653 Some(Actor::Character(*character))
654 } else {
655 None
656 }
657 })
658 })
659 .into_iter()
660 .flatten(),
661 )
662 }
663}
664
665impl Deref for Npcs {
666 type Target = HopSlotMap<NpcId, Npc>;
667
668 fn deref(&self) -> &Self::Target { &self.npcs }
669}
670
671impl DerefMut for Npcs {
672 fn deref_mut(&mut self) -> &mut Self::Target { &mut self.npcs }
673}