1use std::collections::BTreeMap;
18
19use banished_formats::game::{CitizenDescription, Disease, ResidenceDescription};
20
21use crate::behaviour::activities::TownView;
22use crate::behaviour::agents::CitizenState;
23use crate::pathfind::WALKABLE;
24use crate::people::citizens::Citizen;
25use crate::random::Random;
26use crate::scheduler::{seconds, ticks};
27use crate::work::stock::{RawMaterial, Storage};
28
29const MINUTE: f32 = 300.0;
31const HOUR: f32 = 3600.0;
33
34#[derive(Debug, Clone, Default)]
36pub struct LifeRules {
37 fields: BTreeMap<u32, f32>,
38 ints: BTreeMap<u32, i32>,
39 pub services: [Option<usize>; 3],
43}
44
45pub mod service {
48 pub const SCHOOL: usize = 0;
50 pub const CHAPEL: usize = 1;
52 pub const DOCTOR: usize = 2;
54}
55
56impl LifeRules {
57 pub fn new(d: &CitizenDescription) -> LifeRules {
59 let mut r = LifeRules::default();
60 for (o, v) in [(0x94, 1.0), (0x98, 0.5), (0x9c, 12.0)] {
63 r.fields.insert(o, v);
64 r.ints.insert(o, v as i32);
65 }
66 for &o in d.fields.keys() {
67 r.fields.insert(o, d.f(o));
68 r.ints.insert(o, d.i(o));
69 }
70 r
71 }
72
73 pub fn f(&self, o: u32) -> f32 {
75 self.fields.get(&o).copied().unwrap_or(0.0)
76 }
77
78 pub fn i(&self, o: u32) -> i32 {
80 self.ints.get(&o).copied().unwrap_or(0)
81 }
82
83 pub fn adult_age(&self) -> f32 {
85 self.f(0x44)
86 }
87
88 pub fn hunger_warning(&self) -> f32 {
90 (self.f(0xd0) - self.f(0xcc)) * 0.5 + self.f(0xcc)
91 }
92
93 pub fn cold_warning(&self) -> f32 {
95 (self.f(0xf0) - self.f(0xec)) * 0.25 + self.f(0xec)
96 }
97}
98
99pub mod reason {
103 pub const STARVED: i32 = 0;
105 pub const FROZE: i32 = 1;
107 pub const OLD_AGE: i32 = 2;
109 pub const DISEASE: i32 = 3;
111 pub const ORPHANED: i32 = 4;
115 pub const TORNADO: i32 = 5;
119 pub const CHILDBIRTH: i32 = 6;
121 pub const ACCIDENT: i32 = 7;
124 pub const BORN: i32 = 8;
126 pub const ADULT: i32 = 9;
128 pub const STUDENT: i32 = 10;
130 pub const REPLACED: i32 = 0xf;
134 pub const NOT_REPLACED: i32 = 0x10;
136}
137
138#[derive(Debug, Clone, PartialEq)]
142pub struct LifeEvent {
143 pub kind: i32,
145 pub severity: i32,
147 pub citizen: usize,
149 pub extra: Option<(Option<String>, String)>,
151}
152
153#[derive(Debug, Clone, PartialEq)]
155pub struct Home {
156 pub desc: ResidenceDescription,
158 pub storage: usize,
160 pub families: Vec<Vec<usize>>,
165 pub fuel: i64,
168 pub status: u32,
170}
171
172impl Home {
173 pub fn new(desc: ResidenceDescription, storage: usize) -> Home {
175 Home {
176 desc,
177 storage,
178 families: vec![Vec::new(); desc.families.max(1) as usize],
179 fuel: 0,
180 status: 0,
181 }
182 }
183
184 pub fn occupants(&self) -> usize {
188 self.families.iter().map(Vec::len).sum()
189 }
190
191 pub fn full(&self, slot: usize) -> bool {
196 self.families
197 .get(slot)
198 .is_some_and(|f| f.len() as i32 >= self.desc.family_size)
199 }
200
201 pub fn warm(&self, temperature: f32) -> bool {
205 self.desc.warm_temperature <= temperature || self.fuel > 0
206 }
207
208 pub fn families_living(&self) -> i32 {
212 self.families.iter().filter(|f| !f.is_empty()).count() as i32
213 }
214}
215
216pub fn stored(storage: &Storage, materials: &[RawMaterial], bit: u32) -> i32 {
221 storage
222 .items
223 .iter()
224 .filter(|i| materials[i.material].flags >> bit & 1 != 0)
225 .map(|i| i.count - i.reserved)
226 .sum()
227}
228
229pub(crate) fn family_order(citizens: &[Citizen], a: usize, b: usize) -> std::cmp::Ordering {
231 let key = |c: usize| {
232 let c = &citizens[c];
233 (c.parent.is_some() as u32) << 12 | (c.male as u32) << 8
234 };
235 key(a).cmp(&key(b))
236}
237
238pub struct People<'a, 'b> {
240 pub citizens: &'a mut Vec<Citizen>,
242 pub states: &'a mut Vec<CitizenState>,
244 pub registry: &'a mut Vec<usize>,
247 pub homes: &'a mut BTreeMap<usize, Home>,
249 pub rules: &'a LifeRules,
251 pub town: &'a TownView<'b>,
253 pub layers: &'a [[u8; 12]],
256 pub rng: &'a mut Random,
258 pub temperature: f32,
260 pub diseases: &'a [Disease],
262 pub town_disease: f32,
265 pub storages: &'a mut [Storage],
267 pub materials: &'a [RawMaterial],
269 pub idle: &'a [bool],
271 pub events: &'a mut Vec<LifeEvent>,
273 pub dying: &'a mut Vec<(usize, i32)>,
277 pub newborn: &'a mut dyn FnMut(&mut Random, &Citizen, bool) -> Citizen,
282 pub born: &'a mut Vec<usize>,
285 pub attendance: &'a mut BTreeMap<usize, crate::work::construction::Attendance>,
288 pub attendance_list: &'a [usize],
290 pub workforce: &'a mut crate::work::professions::Workforce,
293 pub professions: &'a crate::work::professions::Professions,
295 pub sites: &'a BTreeMap<usize, crate::work::construction::Site>,
297 pub cemeteries: &'a [usize],
300 pub tombstones: &'a BTreeMap<usize, i64>,
302 pub burials: &'a mut Vec<(usize, i32)>,
304 pub sectors: &'a mut crate::sectors::SectorGraph,
306 pub terrain: &'a crate::terrain::HeightGrid<'b>,
308 pub tree: &'a crate::behaviour::character_tree::CharacterTree,
311}
312
313impl People<'_, '_> {
314 fn adult(&self, c: usize) -> bool {
315 self.citizens[c].age >= self.rules.adult_age()
316 }
317
318 fn cell(&self, c: usize) -> i32 {
319 self.town.cell_of(self.citizens[c].position)
320 }
321
322 fn dying(&self, c: usize) -> bool {
323 self.dying.iter().any(|&(d, _)| d == c)
324 }
325
326 fn report(
328 &mut self,
329 c: usize,
330 kind: i32,
331 severity: i32,
332 extra: Option<(Option<String>, String)>,
333 ) {
334 self.events.push(LifeEvent {
335 kind,
336 severity,
337 citizen: c,
338 extra,
339 });
340 }
341
342 fn search_workplace(&mut self, c: usize) {
345 let town = self.town;
346 let citizens = &*self.citizens;
347 let cell = |i: usize| town.cell_of(citizens[i].position);
348 let professions = self.professions;
349 let anywhere = |p: usize| professions.works_anywhere(p);
350 let sites = self.sites;
351 let priority = |e: usize| {
352 sites
353 .get(&e)
354 .and_then(|s| s.work_priority)
355 .unwrap_or(i32::MAX)
356 };
357 let mut search = crate::behaviour::activities::TownSearch {
358 town,
359 cell: &cell,
360 anywhere: &anywhere,
361 priority: &priority,
362 rng: &mut *self.rng,
363 };
364 let (p, current) = (self.states[c].profession, self.states[c].workplace);
365 if current.is_none() || self.workforce.should_move(c, p, current, &mut search) {
366 let wp = self.workforce.find_workplace(c, p, &mut search);
367 if wp != current {
368 self.workforce.move_worker(c, current, wp);
369 self.states[c].workplace = wp;
370 }
371 }
372 }
373
374 pub fn reaches(&mut self, c: usize, entity: usize) -> bool {
378 let cell = self.cell(c);
379 self.town.can_reach(cell, entity, self.rng)
380 }
381
382 pub fn set_home(&mut self, c: usize, home: Option<usize>, slot: i32) {
386 let s = &self.states[c];
387 if s.home == home && s.home_slot == slot {
388 return;
389 }
390 if let Some(old) = s.home {
391 let old_slot = s.home_slot;
392 self.remove_resident(old, old_slot, c);
393 }
394 self.states[c].home = home;
395 self.states[c].home_slot = slot;
396 if let Some(new) = home {
397 self.add_resident(new, slot, c);
398 }
399 for d in self.states[c].dependents.clone() {
400 self.set_home(d, home, slot);
401 }
402 }
403
404 fn add_resident(&mut self, home: usize, slot: i32, c: usize) {
406 let citizens = &*self.citizens;
407 if let Some(h) = self.homes.get_mut(&home) {
408 if let Some(f) = h.families.get_mut(slot.max(0) as usize) {
409 f.push(c);
410 f.sort_by(|&a, &b| family_order(citizens, a, b));
411 }
412 }
413 }
414
415 fn remove_resident(&mut self, home: usize, slot: i32, c: usize) {
417 if let Some(h) = self.homes.get_mut(&home) {
418 if let Some(f) = h.families.get_mut(slot.max(0) as usize) {
419 if let Some(i) = f.iter().position(|&x| x == c) {
420 f.remove(i);
421 }
422 }
423 if h.occupants() == 0 {
424 crate::people::status::set_icon(&mut h.status, 6, false, false);
425 crate::people::status::set_icon(&mut h.status, 7, false, false);
426 }
427 }
428 }
429
430 pub fn set_guardian(&mut self, c: usize, guardian: Option<usize>) {
434 if self.citizens[c].parent == guardian {
435 return;
436 }
437 if let Some(g) = self.citizens[c].parent {
438 self.remove_dependent(g, c);
439 }
440 if let Some(g) = guardian {
441 self.add_dependent(g, c);
442 }
443 }
444
445 fn add_dependent(&mut self, g: usize, c: usize) {
447 self.states[g].dependents.push(c);
448 self.citizens[c].parent = Some(g);
449 let (home, slot) = (self.states[g].home, self.states[g].home_slot);
450 self.set_home(c, home, slot);
451 }
452
453 fn remove_dependent(&mut self, g: usize, c: usize) {
455 self.states[g].dependents.retain(|&d| d != c);
456 self.set_home(c, None, -1);
457 self.citizens[c].parent = None;
458 }
459
460 fn rank(&mut self, a: i32, b: i32) -> i32 {
462 let (size, terrain, floors) = (self.town.size, self.terrain, self.town.floors);
463 let ground = move |cell: i32| crate::town::floor_ground(floors, size, terrain, cell);
464 let mut g = crate::behaviour::activities::StorageGeometry {
465 town: self.town,
466 sectors: self.sectors,
467 ground: &ground,
468 };
469 crate::work::stock::StorageSearchMap::rank(&mut g, a, b)
470 }
471
472 fn accepts(&self, e: usize, c: usize) -> bool {
474 let Some(a) = self.attendance.get(&e) else {
475 return false;
476 };
477 if a.attendees.len() as i32 >= a.capacity {
478 return false;
479 }
480 if self
481 .workforce
482 .workplaces
483 .get(&e)
484 .is_none_or(|w| w.workers.is_empty())
485 {
486 return false;
487 }
488 let to = self.town.point_cell(e);
489 to >= 0 && self.town.reachable(WALKABLE, self.cell(c), to)
490 }
491
492 fn find_service(&mut self, c: usize, service: usize, exclude: Option<usize>) -> Option<usize> {
495 let profession = self.rules.services[service]?;
496 let home = self.states[c]
497 .home
498 .map_or(-1, |h| self.town.entrance(h, self.rng));
499 let mut best = -1;
500 let mut found = None;
501 for &e in self.attendance_list {
502 if Some(e) == exclude
504 || self.workforce.workplaces.get(&e).and_then(|w| w.profession) != Some(profession)
505 || !self.accepts(e, c)
506 {
507 continue;
508 }
509 let entrance = self.town.entrance(e, self.rng);
510 let d = if home >= 0 {
511 self.rank(entrance, home)
512 } else {
513 1
514 };
515 if best < 0 || d < best {
516 best = d;
517 found = Some(e);
518 }
519 }
520 found
521 }
522
523 pub fn join_service(&mut self, c: usize, service: usize, exclude: Option<usize>) -> bool {
528 let Some(e) = self.find_service(c, service, exclude) else {
529 return false;
530 };
531 if let Some(a) = self.attendance.get_mut(&e) {
532 a.attendees.push(c);
533 }
534 let s = &mut self.states[c];
535 match service {
536 service::SCHOOL => {
537 s.school = Some(e);
538 s.schooled = true;
539 }
540 service::CHAPEL => s.priest = Some(e),
541 _ => s.doctor = Some(e),
542 }
543 true
544 }
545
546 pub fn leave_service(&mut self, c: usize, service: usize, again: bool) {
550 let slot = match service {
551 service::SCHOOL => self.states[c].school.take(),
552 service::CHAPEL => self.states[c].priest.take(),
553 _ => self.states[c].doctor.take(),
554 };
555 let Some(e) = slot else { return };
556 if let Some(a) = self.attendance.get_mut(&e) {
557 a.attendees.retain(|&x| x != c);
558 }
559 if again && service == service::SCHOOL && !self.join_service(c, service, None) {
560 self.states[c].school = None;
561 }
562 }
563
564 pub fn lesson(&mut self, c: usize) {
569 let r = self.rules;
570 let s = &mut self.states[c];
571 self.citizens[c].education += 1;
572 if self.citizens[c].education < r.i(0x118) && seconds(s.school_time) <= r.f(0x11c) * HOUR {
573 return;
574 }
575 s.flags |= 0x400;
576 self.citizens[c].education = r.i(0x118);
577 self.citizens[c].educated = true;
578 self.leave_service(c, service::SCHOOL, false);
579 self.report(c, reason::ADULT, 0, None);
580 }
581
582 pub fn treated(&mut self, c: usize) {
586 if self.states[c].disease.is_some() {
587 self.states[c].disease_left -= ticks(self.rules.f(0x12c) * MINUTE);
588 }
589 }
590
591 pub fn die(&mut self, c: usize, reason: i32, extra: Option<(Option<String>, String)>) -> bool {
599 if self.dying(c) {
600 return true;
601 }
602 let from = match self.states[c].home {
603 Some(h) => self.town.point_cell(h),
604 None => self.cell(c),
605 };
606 let bit = match self.find_cemetery(from) {
607 Some(cemetery) => {
608 if let Some(slot) = self.free_grave(cemetery) {
609 let cell = self.sites[&cemetery].graves[slot];
610 self.burials.push((cemetery, cell));
611 }
612 0x40
613 }
614 None if reason == reason::OLD_AGE => 2,
615 None => 0x80,
616 };
617 if let Some(g) = self.citizens[c].parent {
618 self.states[g].flags |= bit;
619 }
620 if let Some(s) = self.citizens[c].spouse {
621 self.states[s].flags |= bit;
622 for d in self.states[s].dependents.clone() {
623 self.states[d].flags |= bit;
624 }
625 }
626 for d in self.states[c].dependents.clone() {
627 self.states[d].flags |= bit;
628 }
629 self.report(c, reason, 1, extra);
630 self.dying.push((c, reason));
631 true
632 }
633
634 fn find_cemetery(&mut self, cell: i32) -> Option<usize> {
637 let mut best = None;
638 let mut nearest = i32::MAX;
639 let n = self.town.size as i32;
640 for &e in self.cemeteries {
641 let to = self.town.point_cell(e);
642 let mut rank = self.rank(cell, to);
643 if rank < 0 {
644 let (dx, dy) = ((cell % n - to % n) as f32, (cell / n - to / n) as f32);
645 rank = n + (dx * dx + dy * dy).sqrt() as i32;
646 }
647 if rank < nearest
648 && self.town.reachable(WALKABLE, cell, to)
649 && self.free_grave(e).is_some()
650 {
651 best = Some(e);
652 nearest = rank;
653 }
654 }
655 best
656 }
657
658 fn free_grave(&mut self, cemetery: usize) -> Option<usize> {
660 let graves = &self.sites.get(&cemetery)?.graves;
661 let count = graves.len();
662 let start = self.rng.rand_below(count as u32) as usize;
663 (0..count).map(|i| (start + i) % count).find(|&i| {
664 let cell = graves[i];
665 let taken = self.town.objects[cell as usize]
666 .is_some_and(|o| self.tombstones.contains_key(&o))
667 || self.burials.iter().any(|&(_, b)| b == cell);
668 !taken
669 })
670 }
671
672 pub fn bury(&mut self, c: usize) {
678 self.leave_service(c, service::SCHOOL, false);
679 self.leave_service(c, service::DOCTOR, false);
680 self.leave_service(c, service::CHAPEL, false);
681 self.set_home(c, None, -1);
682 if let Some(g) = self.citizens[c].parent {
683 self.remove_dependent(g, c);
684 }
685 match self.citizens[c].spouse {
686 None => {
687 let adult = self.rules.adult_age();
688 for d in self.states[c].dependents.clone() {
689 if self.citizens[d].age <= adult {
690 self.die(d, reason::ORPHANED, None);
691 } else {
692 self.set_guardian(d, None);
693 self.set_home(d, None, -1);
694 self.leave_service(d, service::SCHOOL, false);
695 }
696 }
697 }
698 Some(p) => {
699 for d in self.states[c].dependents.clone() {
700 self.set_guardian(d, Some(p));
701 }
702 }
703 }
704 if let Some(p) = self.citizens[c].spouse.take() {
705 self.citizens[p].spouse = None;
706 }
707 self.registry.retain(|&x| x != c);
708 }
709
710 pub fn update(&mut self, c: usize, dt: i64) {
714 let r = self.rules;
715 let secs = seconds(dt);
716 let adult_age = r.adult_age();
717 let was_child = self.citizens[c].age < adult_age || self.states[c].school.is_some();
718 let age = secs * r.f(0x40) + self.citizens[c].age;
719 self.citizens[c].age = age;
720 {
721 let s = &mut self.states[c];
722 s.health_timer += dt;
723 s.happiness_timer += dt;
724 s.clothing_remaining -= dt;
725 if s.school.is_some() {
726 s.school_time += dt;
727 }
728 s.disease_clock += dt;
729 s.accident_timer += dt;
730 if s.clothing_remaining < 0 {
731 s.clothing = 0;
732 s.clothing_remaining = 0;
733 }
734 }
735 if was_child && adult_age <= age && self.states[c].school.is_none() {
737 let student = !self.states[c].schooled && self.join_service(c, service::SCHOOL, None);
738 self.report(
739 c,
740 if student {
741 reason::STUDENT
742 } else {
743 reason::ADULT
744 },
745 0,
746 None,
747 );
748 }
749 let s = &mut self.states[c];
750 if s.sick && s.doctor.is_some() {
751 s.hunger = 0.0;
752 s.cold = 0.0;
753 }
754 s.hunger += secs * r.f(0xc0) / HOUR;
755 if r.f(0xf4) <= self.temperature {
756 s.cold = 0.0;
757 } else {
758 let rate = r.f(0xdc + 4 * s.clothing.clamp(0, 2) as u32);
759 s.cold += secs * rate / MINUTE;
760 }
761 if let Some(p) = self.states[c].accident {
764 let extra = self
765 .professions
766 .list
767 .get(p)
768 .map(|p| (p.string_table.clone(), p.death.clone()));
769 if self.die(c, reason::ACCIDENT, extra) {
770 return;
771 }
772 }
773 let s = &mut self.states[c];
774 if r.f(0xd0) <= s.hunger {
775 s.hunger = r.f(0xd0);
776 if self.idle[c] && self.die(c, reason::STARVED, None) {
777 return;
778 }
779 }
780 let s = &mut self.states[c];
781 if r.f(0xf0) <= s.cold {
782 s.cold = r.f(0xf0);
783 if self.idle[c] && self.die(c, reason::FROZE, None) {
784 return;
785 }
786 }
787 if self.citizens[c].lifespan <= self.citizens[c].age && self.die(c, reason::OLD_AGE, None) {
788 return;
789 }
790 if !self.disease(c, dt) {
791 let extra = self.states[c]
792 .disease
793 .and_then(|d| self.diseases.get(d))
794 .map(|d| (d.table.clone(), d.name.clone()));
795 if self.die(c, reason::DISEASE, extra) {
796 return;
797 }
798 }
799 if self.states[c].disease.is_some() {
800 self.states[c].flags |= 1;
801 if self.states[c].doctor.is_none() {
802 self.join_service(c, service::DOCTOR, None);
803 }
804 }
805 if self.states[c].priest.is_none() {
806 self.join_service(c, service::CHAPEL, None);
807 }
808 let warn_hunger = r.hunger_warning();
809 let warn_cold = r.cold_warning();
810 {
811 let s = &mut self.states[c];
812 crate::people::status::set_icon(&mut s.status, 0, s.home.is_none(), false);
813 crate::people::status::set_icon(&mut s.status, 1, warn_hunger < s.hunger, false);
814 crate::people::status::set_icon(&mut s.status, 2, warn_cold < s.cold, false);
815 let unplaced = !self.professions.works_anywhere(s.profession) && s.workplace.is_none();
818 crate::people::status::set_icon(&mut s.status, 3, unplaced, false);
819 crate::people::status::set_icon(&mut s.status, 5, s.disease.is_some(), false);
820 if warn_hunger < s.hunger {
821 s.health_events |= 1;
822 s.flags |= 1;
823 }
824 if warn_cold < s.cold {
825 s.health_events |= 2;
826 s.flags |= 1;
827 }
828 }
829 let interval = ticks(r.f(0x78) * MINUTE);
830 while interval > 0 && interval < self.states[c].health_timer {
831 self.states[c].health_timer -= interval;
832 self.health(c);
833 }
834 let interval = ticks(r.f(0xa0) * MINUTE);
835 while interval > 0 && interval < self.states[c].happiness_timer {
836 self.states[c].happiness_timer -= interval;
837 self.happiness(c);
838 }
839 if self.idle[c] {
841 if let Some(home) = self.states[c].home {
842 if !self.reaches(c, home) {
843 self.set_home(c, None, -1);
844 }
845 }
846 }
847 let child = self.citizens[c].age < adult_age || self.states[c].school.is_some();
848 if child && self.idle[c] && self.states[c].home.is_none() {
850 if let Some(g) = self.citizens[c].parent {
851 if let Some(home) = self.states[g].home {
852 if self.reaches(c, home) {
853 let slot = self.states[g].home_slot;
854 self.set_home(c, Some(home), slot);
855 }
856 }
857 }
858 }
859 if !child && self.idle[c] {
860 self.search_workplace(c);
861 if self.states[c].home.is_none() || self.citizens[c].parent.is_some() {
862 if self.states[c].home.is_none() {
863 self.states[c].flags |= 0x20;
864 }
865 let homeless = self.states[c].home.is_none();
866 if let Some((home, slot)) = self.find_home(c, homeless) {
867 if self.citizens[c].parent.is_some() {
868 self.states[c].flags |= 0x2000;
869 }
870 self.set_guardian(c, None);
871 self.set_home(c, Some(home), slot);
872 if let Some(p) = self.citizens[c].spouse {
873 self.set_home(p, Some(home), slot);
874 }
875 }
876 }
877 if let Some(home) = self.states[c].home {
879 if self.homes.get(&home).is_some_and(|h| h.families.len() > 1) {
880 if let Some((home, slot)) = self.find_home(c, false) {
881 self.set_home(c, Some(home), slot);
882 if let Some(p) = self.citizens[c].spouse {
883 self.set_home(p, Some(home), slot);
884 }
885 }
886 }
887 }
888 if self.citizens[c].parent.is_none()
890 && self.states[c].home.is_some()
891 && self.citizens[c].spouse.is_none()
892 && r.f(0x48) <= self.citizens[c].age
893 {
894 if let Some(p) = self.find_partner(c) {
895 self.citizens[c].spouse = Some(p);
896 self.states[c].flags |= 0x200;
897 self.states[p].flags |= 0x200;
898 self.set_guardian(p, None);
899 self.citizens[p].spouse = Some(c);
900 let (home, slot) = (self.states[c].home, self.states[c].home_slot);
901 self.set_home(p, home, slot);
902 }
903 }
904 }
905 self.pregnancy(c, secs);
906 }
907
908 fn pregnancy(&mut self, c: usize, secs: f32) {
910 let r = self.rules;
911 if self.citizens[c].male
912 || self.citizens[c].age < r.adult_age()
913 || self.states[c].school.is_some()
914 {
915 return;
916 }
917 let s = &mut self.states[c];
918 if (r.hunger_warning() < s.hunger || r.cold_warning() < s.cold) && 0.0 < s.reproduction {
919 s.reproduction = 0.0;
920 }
921 let t = s.reproduction;
922 if t <= 0.0 {
923 if t < 0.0 {
924 s.reproduction = t + secs;
925 if 0.0 <= t + secs {
926 s.reproduction = 0.0;
927 }
928 }
929 } else {
930 s.reproduction = t - secs;
931 if t - secs <= 0.0 {
932 let health = s.health;
933 let threshold = (health / r.f(0x70)) * (r.f(0x64) - r.f(0x68)) + r.f(0x68);
934 if threshold <= self.rng.rand_float() {
935 self.give_birth(c);
936 let cooldown = self.rng.rand_float() * r.f(0x60) + r.f(0x5c);
937 self.states[c].reproduction = -cooldown;
938 } else if self.die(c, reason::CHILDBIRTH, None) {
939 return;
940 }
941 }
942 }
943 let s = &self.states[c];
944 if self.citizens[c].spouse.is_some() && s.home.is_some() && s.reproduction == 0.0 {
945 let age = self.citizens[c].age;
946 if r.f(0x50) <= age && age <= r.f(0x54) {
947 let full = s
948 .home
949 .and_then(|h| self.homes.get(&h))
950 .is_some_and(|h| h.full(s.home_slot.max(0) as usize));
951 if !full {
952 self.states[c].reproduction = r.f(0x58);
953 }
954 }
955 }
956 }
957
958 fn give_birth(&mut self, mother: usize) {
960 let r = self.rules;
961 let (mut young, mut boys) = (0, 0);
964 for &i in self.registry.iter() {
965 if self.citizens[i].spouse.is_none() && self.citizens[i].age <= r.f(0x4c) {
966 if self.citizens[i].male {
967 boys += 1;
968 }
969 young += 1;
970 }
971 }
972 let male = if young != 0 {
973 let x = self.rng.rand_float();
974 boys as f32 / young as f32 <= x
975 } else {
976 self.rng.rand_below(2) == 1
977 };
978 let mut child = (self.newborn)(self.rng, &self.citizens[mother], male);
979 child.position = self.citizens[mother].position;
980 child.age = 0.0;
981 let id = self.citizens.len();
982 self.citizens.push(child);
983 self.states.push(CitizenState::newborn(r));
984 self.add_dependent(mother, id);
985 self.registry.push(id);
986 self.born.push(id);
987 self.report(id, reason::BORN, 0, None);
988 self.states[mother].flags |= 0x100;
989 if let Some(f) = self.citizens[mother].spouse {
990 self.states[f].flags |= 0x100;
991 }
992 }
993
994 fn find_partner(&self, c: usize) -> Option<usize> {
997 let r = self.rules;
998 let me = &self.citizens[c];
999 self.registry.iter().copied().find(|&i| {
1000 let o = &self.citizens[i];
1001 o.male != me.male
1002 && o.spouse.is_none()
1003 && r.f(0x48) < o.age
1004 && (o.age - me.age).abs() <= r.f(0x4c)
1005 && !self.dying(i)
1006 })
1007 }
1008
1009 fn find_home(&mut self, c: usize, shared: bool) -> Option<(usize, i32)> {
1013 if let Some(p) = self.citizens[c].spouse {
1014 if let Some(home) = self.states[p].home {
1015 if self.reaches(c, home)
1016 && self.homes.get(&home).is_some_and(|h| h.families.len() < 2)
1017 {
1018 return Some((home, self.states[p].home_slot));
1019 }
1020 }
1021 }
1022 if let Some(mine) = self.states[c].home {
1023 let single = self.homes.get(&mine).is_some_and(|h| h.families.len() == 1);
1026 let adult = self.rules.adult_age();
1027 for &o in self.registry.iter() {
1028 if o == c || self.citizens[o].age < adult || self.states[o].school.is_some() {
1029 continue;
1030 }
1031 let Some(h) = self.states[o].home else {
1032 return None;
1033 };
1034 if single && self.homes.get(&h).is_some_and(|h| h.families.len() > 1) {
1035 return None;
1036 }
1037 }
1038 }
1039 let cell = self.cell(c);
1040 let mut fallback = None;
1041 for e in self.workforce.residences.clone() {
1042 let Some(home) = self.homes.get(&e) else {
1043 continue;
1044 };
1045 if !shared && home.families.len() >= 2 {
1046 continue;
1047 }
1048 for (slot, family) in home.families.iter().enumerate() {
1049 if !family.is_empty() {
1050 continue;
1051 }
1052 let entrance = self.town.point_cell(e);
1053 if entrance < 0 || !self.town.reachable(WALKABLE, cell, entrance) {
1054 continue;
1055 }
1056 if home.families.len() == 1 {
1057 return Some((e, slot as i32));
1058 }
1059 if fallback.is_none() {
1060 fallback = Some((e, slot as i32));
1061 }
1062 }
1063 }
1064 fallback
1065 }
1066
1067 fn health(&mut self, c: usize) {
1069 let r = self.rules;
1070 let adult = self.adult(c) && self.states[c].school.is_none();
1071 let s = &mut self.states[c];
1072 let e = s.health_events;
1073 let mut n = (e >> 8 & 1) + (e >> 6 & 1) + (e >> 5 & 1) + (e >> 4 & 1);
1074 if e & 0x200 != 0 {
1075 n += 1;
1076 }
1077 if e & 8 != 0 && e & 4 != 0 && 3 < n {
1078 s.health += 0.5;
1079 }
1080 if e & 8 != 0 && e & 0x200 == 0 && n < 3 {
1081 s.health -= 1.0 - n as f32 / 3.0;
1082 }
1083 if e & 1 != 0 {
1084 s.health -= 1.0;
1085 }
1086 if e & 2 != 0 {
1087 s.health -= 1.0;
1088 }
1089 if e & 4 == 0 && adult {
1090 s.health -= 0.25;
1091 }
1092 if s.health < 0.0 {
1093 s.health = 0.0;
1094 }
1095 s.health = s.health.min(r.f(0x70));
1096 s.health_events = 0;
1097 }
1098
1099 fn happiness(&mut self, c: usize) {
1102 let r = self.rules;
1103 let home_bonus = self.states[c].home.map(|h| {
1104 let cell = self.town.entrance(h, self.rng);
1107 let l = usize::try_from(cell)
1108 .ok()
1109 .and_then(|i| self.layers.get(i))
1110 .copied()
1111 .unwrap_or_default();
1112 let v = (l[..5].iter().filter(|&&b| b != 0).count() as i32 - l[5] as i32).max(0);
1113 if self.homes.get(&h).is_some_and(|h| h.families.len() > 1) {
1114 v >> 2
1115 } else {
1116 v
1117 }
1118 });
1119 let s = &mut self.states[c];
1120 let f = s.flags;
1121 let mut h = s.happiness;
1122 if f & 2 != 0 {
1123 h -= 8.0;
1124 }
1125 if f & 0x40 != 0 {
1126 h -= 2.0;
1127 }
1128 if f & 0x80 != 0 {
1129 h -= 4.0;
1130 }
1131 if f & 8 != 0 {
1132 h -= 1.0;
1133 }
1134 if f & 0x10 != 0 {
1135 h -= 1.0;
1136 }
1137 for bit in [8, 9, 10, 11, 12, 13] {
1138 if f >> bit & 1 != 0 {
1139 h += 1.0;
1140 }
1141 }
1142 if f >> 0x12 & 1 != 0 {
1143 h += 0.5;
1144 }
1145 h = h.max(0.0).min(r.f(0xa4));
1146 let mut target = 0.0;
1147 if f & 4 == 0 {
1148 target += 1.0;
1149 }
1150 if f & 0x20 == 0 {
1151 target += 1.0;
1152 }
1153 if f & 1 == 0 {
1154 target += 1.0;
1155 }
1156 if f >> 0x11 & 1 != 0 {
1157 target += if s.field_84 < r.i(0xb0) { 0.5 } else { 1.0 };
1161 }
1162 if f >> 0x10 & 1 != 0 {
1163 let foods = s.foods.len() as i32;
1164 if r.i(0xa8) < foods {
1165 if r.i(0xac) <= foods {
1166 target += 1.0;
1167 }
1168 } else {
1169 target -= 1.0;
1170 }
1171 }
1172 if let Some(b) = home_bonus {
1173 target += b as f32;
1174 }
1175 if s.priest.is_some() {
1176 target += 1.0;
1177 }
1178 let target = target * 0.125 * r.f(0xa4);
1179 if h < target {
1180 h += 0.5;
1181 } else if target < h {
1182 h = (h as f64 - 0.5) as f32;
1184 }
1185 s.happiness = h.max(0.0).min(r.f(0xa4));
1186 }
1187
1188 fn disease(&mut self, c: usize, dt: i64) -> bool {
1190 let r = self.rules;
1191 match self.states[c].disease {
1192 None => {
1193 let s = &mut self.states[c];
1194 s.immunity = (s.immunity - dt).max(0);
1195 let weak = 1.0 - s.health / r.f(0x70);
1196 let a = (r.f(0x84) - r.f(0x7c)) * weak + r.f(0x7c);
1197 let b = (r.f(0x88) - r.f(0x80)) * weak + r.f(0x80);
1198 let a = (r.f(0x8c) - a) * self.town_disease + a;
1199 let b = (r.f(0x90) - b) * self.town_disease + b;
1200 if b <= 0.0 {
1202 s.disease_clock = 0;
1203 }
1204 let elapsed = s.disease_clock;
1205 s.disease_clock = 0;
1206 let span = a * 12.0 * MINUTE * b;
1207 let x = self.rng.rand_float();
1208 if x < seconds(elapsed) / span {
1209 self.outbreak(c);
1210 }
1211 true
1212 }
1213 Some(d) => {
1214 let s = &mut self.states[c];
1215 s.disease_left -= dt;
1216 s.spread -= dt;
1217 if s.spread < 1 {
1218 self.spread(c);
1219 let interval = self.diseases.get(d).map_or(0.0, |d| d.spread_interval);
1220 self.states[c].spread = ticks(interval * MINUTE);
1221 }
1222 if self.states[c].disease_left < 1 {
1223 return self.recover(c);
1224 }
1225 true
1226 }
1227 }
1228 }
1229
1230 fn outbreak(&mut self, c: usize) {
1232 if self
1233 .registry
1234 .iter()
1235 .any(|&i| self.states[i].disease.is_some())
1236 {
1237 return;
1238 }
1239 let mut total = 0.0;
1240 let cumulative: Vec<f32> = self
1241 .diseases
1242 .iter()
1243 .map(|d| {
1244 total += d.weight;
1245 total
1246 })
1247 .collect();
1248 let x = self.rng.rand_float() * total;
1249 let mut low = 0.0;
1250 for (i, &high) in cumulative.iter().enumerate() {
1251 if low <= x && x < high {
1252 self.infect(c, i);
1253 break;
1254 }
1255 low = high;
1256 }
1257 }
1258
1259 fn infect(&mut self, c: usize, d: usize) {
1262 let s = &self.states[c];
1263 if s.disease.is_some() || 0 < s.immunity {
1264 return;
1265 }
1266 let at_work = s.workplace.is_some()
1267 && self
1268 .professions
1269 .list
1270 .get(s.profession)
1271 .is_some_and(|p| p.immune_at_work);
1272 if at_work {
1273 return;
1274 }
1275 let first = !self
1276 .registry
1277 .iter()
1278 .any(|&i| self.states[i].disease.is_some());
1279 if first {
1280 let extra = self
1281 .diseases
1282 .get(d)
1283 .map(|d| (d.table.clone(), d.name.clone()));
1284 self.report(c, 0xb, 3, extra);
1285 }
1286 let r = self.rules;
1287 let x = self.rng.rand_float_tol();
1288 let s = &mut self.states[c];
1289 s.disease = Some(d);
1290 s.sick = true;
1291 s.disease_left = ticks((x * r.f(0x98) + r.f(0x94)) * MINUTE);
1292 s.spread = ticks(self.diseases.get(d).map_or(0.0, |d| d.spread_interval) * MINUTE);
1293 self.join_service(c, service::DOCTOR, None);
1294 }
1295
1296 fn spread(&mut self, c: usize) {
1300 let Some(d) = self.states[c].disease else {
1301 return;
1302 };
1303 let Some(disease) = self.diseases.get(d).cloned() else {
1304 return;
1305 };
1306 let x = self.rng.rand_float();
1307 if disease.spread_chance < x {
1308 return;
1309 }
1310 let p = self.citizens[c].position;
1311 let near: Vec<usize> = self
1312 .tree
1313 .within(p, disease.spread_radius, |w| {
1314 matches!(w, crate::behaviour::character_tree::Who::Citizen(_))
1315 })
1316 .into_iter()
1317 .filter_map(|w| match w {
1318 crate::behaviour::character_tree::Who::Citizen(i) if i != c => Some(i),
1319 _ => None,
1320 })
1321 .collect();
1322 for i in near {
1323 if self.states.get(i).is_some_and(|s| s.disease.is_none()) {
1324 self.infect(i, d);
1325 }
1326 }
1327 }
1328
1329 fn recover(&mut self, c: usize) -> bool {
1331 let r = self.rules;
1332 let x = self.rng.rand_float();
1333 let s = &self.states[c];
1334 let doctor = if s.doctor.is_some() { r.f(0x128) } else { 1.0 };
1335 let recovery = s
1336 .disease
1337 .and_then(|d| self.diseases.get(d))
1338 .map_or(1.0, |d| d.recovery);
1339 let lives = 1.0 / recovery / doctor < x;
1340 if lives {
1341 let s = &mut self.states[c];
1342 s.spread = 0;
1343 s.immunity = ticks(r.f(0x9c) * MINUTE);
1344 s.disease = None;
1345 s.sick = false;
1346 }
1347 self.leave_service(c, service::DOCTOR, false);
1349 lives
1350 }
1351}
1352
1353pub fn warm_at(s: &mut CitizenState, home: Option<&Home>, temperature: f32) {
1357 if home.is_some_and(|h| h.warm(temperature)) {
1358 s.cold = 0.0;
1359 }
1360}
1361
1362pub fn eat(
1368 s: &mut CitizenState,
1369 storage: &mut Storage,
1370 materials: &[RawMaterial],
1371 rng: &mut Random,
1372 counters: &mut crate::work::tracker::StockCounters,
1373) {
1374 while 0.0 < s.hunger {
1375 let Some(m) = storage.find_item(materials, 0, rng) else {
1376 return;
1377 };
1378 s.flags |= 0x10000;
1379 if !s.foods.contains(&m) {
1380 s.foods.push(m);
1381 }
1382 storage.remove(m, 1);
1383 let flags = materials[m].flags;
1384 if let Some(slot) = crate::work::produce::production_group(flags) {
1385 counters.used[slot] += 1;
1386 }
1387 if s.health_events & 8 == 0 {
1388 s.health_events = s.health_events & 0xfffffe8f | 8;
1389 }
1390 for (bit, event) in [(0, 0x10), (1, 0x20), (2, 0x40), (4, 0x100)] {
1391 if flags >> bit & 1 != 0 {
1392 s.health_events |= event;
1393 }
1394 }
1395 s.hunger -= 1.0;
1396 if s.hunger < 0.0 {
1397 s.hunger = 0.0;
1398 return;
1399 }
1400 }
1401}
1402
1403pub fn update_home(
1412 home: &mut Home,
1413 storages: &mut [Storage],
1414 materials: &[RawMaterial],
1415 temperature: f32,
1416 dt: i64,
1417 counters: &mut crate::work::tracker::StockCounters,
1418) {
1419 home.fuel -= dt;
1420 let storage = &mut storages[home.storage];
1421 if 0 < stored(storage, materials, 5) {
1422 crate::people::status::set_icon(&mut home.status, 6, false, false);
1423 }
1424 if 0 < stored(storage, materials, 6) {
1425 crate::people::status::set_icon(&mut home.status, 7, false, false);
1426 }
1427 if temperature < home.desc.warm_temperature && home.fuel < 0 && !home.families.is_empty() {
1428 let fuel = storage
1429 .items
1430 .iter()
1431 .find(|i| materials[i.material].flags >> 6 & 1 != 0 && 0 < i.count - i.reserved)
1432 .map(|i| i.material);
1433 if let Some(m) = fuel {
1434 home.fuel = ticks(home.desc.fuel_seconds);
1435 storage.remove(m, 1);
1436 if let Some(slot) = crate::work::produce::production_group(materials[m].flags) {
1437 counters.used[slot] += 1;
1438 }
1439 }
1440 }
1441}