Skip to main content

banished_game/people/
immigration.rs

1//! Immigration: ImmigrationComponent (the town hall) and the nomads it calls (NomadComponent). See
2//! docs/IMMIGRATION.md.
3//!
4//! Once the town is big enough and has its market and trading post, the town hall now and then (in
5//! spring) calls a band of nomads from a map edge. They walk to it and wait; the player takes them
6//! in (they become citizens, in families of four, bringing a disease risk) or they leave, at the
7//! latest in the summer.
8//!
9//! Original: `FUN_14004f020`, `FUN_1400507d0`.
10// vtable 0x14015fc50; vtable 0x140160140
11
12use banished_formats::game::CharacterDescription;
13
14use crate::behaviour::character::{Action, Character, MoveTo};
15use crate::scheduler::ticks;
16use crate::town::Sim;
17use crate::work::construction::Callbacks;
18
19/// `DAT_140178934`.
20const PERIOD: f32 = 300.0;
21/// Event type 14: nomads arrived.
22const ARRIVED: i32 = 0xe;
23/// `Map::Reachable` mask of the edge search.
24const MASK: u32 = 0xe07;
25
26/// A resolved ImmigrationDescription.
27///
28/// Original: `FUN_14004f150`, `FUN_14004f300`.
29#[derive(Debug, Clone, Default, PartialEq)]
30pub struct ImmigrationTemplate {
31    // +0x20
32    /// The nomad template.
33    pub nomad: usize,
34    // +0x28
35    /// The population the town needs.
36    pub population: i32,
37    // +0x30
38    /// Templates the town must have placed (Market, TradingPost).
39    pub required: Vec<usize>,
40    // +0x40, +0x44
41    /// The band's size as a share of the population.
42    pub share: (f32, f32),
43    // +0x48, +0x4c; +0x48 + RandFloat × +0x4c
44    /// Months until the next band: the first value plus a random share of the second.
45    pub wait: (f32, f32),
46    // +0x50, +0x54
47    /// The months a band may come in.
48    pub months: (i32, i32),
49    // +0x58
50    /// From this month waiting nomads leave (not serialized; 7).
51    pub leave: i32,
52    // +0x5c
53    /// Their map-edge margin (not serialized; 10).
54    pub margin: i32,
55    // +0x60, +0x64
56    /// The visitors' disease risk.
57    pub risk: f32,
58    /// How many months the risk lasts.
59    pub risk_months: i32,
60}
61
62// +0x48; +0x50; +0x58
63/// ImmigrationComponent: until a band may come, the nomads arrived, the band.
64#[derive(Debug, Clone, Default, PartialEq, Eq)]
65pub struct Immigration {
66    /// The time until a band may come.
67    pub timer: i64,
68    /// The nomads that arrived.
69    pub arrived: i32,
70    /// The band.
71    pub nomads: Vec<usize>,
72}
73
74// +0x20; +0x28; +0x30
75/// A nomad description: the citizen template nomads become, the men's and women's material
76/// (for presentation: `StartingTown::nomad_materials`), and the character description.
77///
78/// Original: `FUN_140050880`.
79#[derive(Debug, Clone)]
80pub struct NomadKind {
81    /// The character description.
82    pub character: CharacterDescription,
83}
84
85// +0x28; +0x30; +0x3c; +0x48; +0x50
86/// NomadComponent: its state (0 walking to the town hall, 1 waiting, 2 leaving), the town hall,
87/// where it came from, where it waits, until it wanders, its model variant (0 men, 1 women).
88pub struct Nomad {
89    /// The template.
90    pub template: usize,
91    /// The character.
92    pub character: Character,
93    /// The state: 0 walking to the town hall, 1 waiting, 2 leaving.
94    pub state: i8,
95    /// The town hall.
96    pub town_hall: Option<usize>,
97    /// Where it came from.
98    pub spawn: [f32; 3],
99    /// Where it waits.
100    pub target: [f32; 3],
101    /// The time until it wanders.
102    pub timer: i64,
103    /// The model variant: 0 men, 1 women.
104    pub variant: usize,
105}
106
107impl Sim<'_, '_> {
108    fn immigration_desc(&self, e: usize) -> Option<&ImmigrationTemplate> {
109        self.site_templates[self.sites.get(&e)?.template]
110            .immigration
111            .as_ref()
112    }
113
114    fn immigration(&mut self, e: usize) -> Option<&mut Immigration> {
115        self.sites.get_mut(&e)?.immigration.as_mut()
116    }
117
118    /// The calendar's month (`seconds / 3600 × 12`, past 11 is 0).
119    fn calendar_month(&self) -> i32 {
120        let m = (self.calendar / 3600.0 * 12.0) as i32;
121        if 11 < m {
122            0
123        } else {
124            m
125        }
126    }
127
128    /// `FUN_14004ed50`: the town has its population and a placed entity of every required
129    /// template (the town manager's zones by template, `+0x160`).
130    fn immigration_wanted(&self, e: usize) -> bool {
131        let Some(d) = self.immigration_desc(e) else {
132            return false;
133        };
134        if self.population < d.population {
135            return false;
136        }
137        d.required.iter().all(|&t| {
138            self.sites.iter().any(|(&s, site)| {
139                !site.destroyed && self.placed[s].alive && self.placed[s].template == t
140            })
141        })
142    }
143
144    /// State 2 enter and update (`FUN_14004dbf0`, `FUN_14004db80`).
145    pub(crate) fn immigration_idle(&mut self, e: usize) {
146        if self.immigration_wanted(e) {
147            self.machine(e).request(0xf);
148        }
149    }
150
151    /// State 0xf enter (`FUN_14004db00`): `(+0x48 + RandFloat × +0x4c)` × 300 s.
152    pub(crate) fn immigration_wait(&mut self, e: usize) {
153        let Some(d) = self.immigration_desc(e).cloned() else {
154            return;
155        };
156        let f = self.rng.rand_float();
157        let t = ticks((f * d.wait.1 + d.wait.0) * PERIOD);
158        if let Some(i) = self.immigration(e) {
159            i.timer = t;
160        }
161    }
162
163    /// State 0xf update (`FUN_14004da30`): when the wait is over, in the season, a band comes
164    /// (0x10) or the town no longer qualifies (2).
165    pub(crate) fn immigration_waiting(&mut self, e: usize, dt: i64) {
166        let Some(d) = self.immigration_desc(e).cloned() else {
167            return;
168        };
169        let Some(i) = self.immigration(e) else { return };
170        i.timer -= dt;
171        let due = i.timer < 1;
172        let month = self.calendar_month();
173        if due && d.months.0 <= month && month <= d.months.1 {
174            let next = if self.immigration_wanted(e) { 0x10 } else { 2 };
175            self.machine(e).request(next);
176        }
177    }
178
179    /// State 0x10 enter (`FUN_14004d9f0`) and update (`FUN_14004d9d0`): a new band (again
180    /// while none could be placed).
181    pub(crate) fn immigration_call(&mut self, e: usize, enter: bool) {
182        if enter {
183            if let Some(i) = self.immigration(e) {
184                i.arrived = 0;
185                i.nomads.clear();
186            }
187        } else if !self.immigration(e).is_some_and(|i| i.nomads.is_empty()) {
188            return;
189        }
190        self.spawn_band(e);
191    }
192
193    /// `FUN_14004ebd0(x, y, target)`: up to 50 cells within the margin of `(x, y)` that are
194    /// walkable and either by water or in the target's region.
195    fn edge(&mut self, d: &ImmigrationTemplate, x: i32, y: i32, target: i32) -> Option<i32> {
196        let n = self.size as i32;
197        for _ in 0..50 {
198            let cx = (x + self.rng.rand_tol(d.margin)).clamp(0, n - 1);
199            let cy = (y + self.rng.rand_tol(d.margin)).clamp(0, n - 1);
200            let cell = cy * n + cx;
201            let a = 1u32 << (self.cells.types[cell as usize].0 & 0x1f);
202            if a & MASK == 0 {
203                continue;
204            }
205            let b = 1u32 << (self.cells.types[target as usize].0 & 0x1f);
206            if (a | b) & 0x1e0 != 0 {
207                return Some(cell);
208            }
209            let (ra, rb) = (self.regions[cell as usize], self.regions[target as usize]);
210            if ra != 0 && ra == rb {
211                return Some(cell);
212            }
213        }
214        None
215    }
216
217    /// `FUN_14004e540`: a band from a random map edge (ten tries at a reachable cell): `Rand(2 ×
218    /// empty family slots) + population × share`, at least two, each walking from near that
219    /// edge to one of the town hall's create points.
220    fn spawn_band(&mut self, e: usize) {
221        let Some(d) = self.immigration_desc(e).cloned() else {
222            return;
223        };
224        let n = self.size as i32;
225        let target = self.cell_of(self.use_point(e, 0).0);
226        let mut tries = 0;
227        let (x, y, start) = loop {
228            let side = self.rng.rand_below(4);
229            let (x, y) = if side < 2 {
230                let x = if side == 0 {
231                    d.margin
232                } else {
233                    n - d.margin - 1
234                };
235                (x, self.rng.rand_below(n as u32) as i32)
236            } else {
237                let x = self.rng.rand_below(n as u32) as i32;
238                (
239                    x,
240                    if side == 2 {
241                        d.margin
242                    } else {
243                        n - d.margin - 1
244                    },
245                )
246            };
247            if let Some(start) = self.edge(&d, x, y, target) {
248                break (x, y, start);
249            }
250            tries += 1;
251            if 9 < tries {
252                return;
253            }
254        };
255        let population = self.population;
256        let f = self.rng.rand_float();
257        let free: i32 = self
258            .workforce
259            .residences
260            .iter()
261            .filter_map(|r| self.homes.get(r))
262            .map(|h| h.families.iter().filter(|f| f.is_empty()).count() as i32)
263            .sum();
264        let mut count = self.rng.rand_below((free * 2) as u32) as i32
265            + (population as f32 * (f * (d.share.1 - d.share.0) + d.share.0)) as i32;
266        if count < 2 {
267            count = 2;
268        }
269        let points: usize = self
270            .interact_entities(e)
271            .iter()
272            .map(|&x| self.templates[self.placed[x].template].create_points.len())
273            .sum();
274        for _ in 0..count {
275            let cell = self.edge(&d, x, y, target).unwrap_or(start);
276            let k = self.rng.rand_below(points as u32) as usize;
277            let dest = self.create_point(e, k);
278            let ground = self.ground_at(cell);
279            if let Some(id) = self.add_nomad(d.nomad, e, ground, dest) {
280                if let Some(i) = self.immigration(e) {
281                    i.nomads.push(id);
282                }
283            }
284        }
285        if let Some(i) = self.immigration(e) {
286            i.arrived = 0;
287        }
288    }
289
290    /// `FUN_1401282f0(interact, 2, k)`: create point `k` (the entity's own, then its parts').
291    fn create_point(&self, e: usize, mut k: usize) -> [f32; 3] {
292        for x in self.interact_entities(e) {
293            let points = &self.templates[self.placed[x].template].create_points;
294            if k < points.len() {
295                return self.world_point(x, points[k]);
296            }
297            k -= points.len();
298        }
299        self.world_point(e, [0.0; 3])
300    }
301
302    /// The nomad entity (`FUN_1400c8bf0`) and its setup (`FUN_140050990`): `Rand(2)` its
303    /// variant, placed at `spawn` turned `RandFloatTol × 180`; activation runs state 0 (walk to
304    /// `target`).
305    fn add_nomad(
306        &mut self,
307        template: usize,
308        town_hall: usize,
309        spawn: [f32; 3],
310        target: [f32; 3],
311    ) -> Option<usize> {
312        let kind = self.wildlife.nomad_kinds.get(&template)?.clone();
313        let variant = self.rng.rand_below(2) as usize;
314        let yaw = self.rng.rand_float_tol() * 180.0;
315        let id = self.placed.len();
316        let t = &self.templates[template];
317        self.placed.push(crate::nature::seeding::Placed {
318            template,
319            bucket: t.bucket,
320            cell: (
321                self.cell_of(spawn) % self.size as i32,
322                self.cell_of(spawn) / self.size as i32,
323            ),
324            footprint: (1, 1),
325            position: spawn,
326            yaw,
327            variant,
328            mesh: usize::MAX,
329            growth: 0.0,
330            growth_state: None,
331            spawn_state: None,
332            alive: true,
333            removal_requested: false,
334            animation: None,
335            scale: [1.0; 4],
336        });
337        let mut character = Character::new(kind.character, spawn, 1.0);
338        character.yaw = yaw;
339        character.tree_slot = self.wildlife.tree.insert(
340            crate::behaviour::character_tree::Who::Nomad(id),
341            spawn,
342            kind.character.radius,
343        );
344        self.wildlife.nomads.insert(
345            id,
346            Nomad {
347                template,
348                character,
349                state: 0,
350                town_hall: Some(town_hall),
351                spawn,
352                target,
353                timer: 0,
354                variant,
355            },
356        );
357        self.nomad_walk(id, target);
358        *self.models_dirty = true;
359        Some(id)
360    }
361
362    /// `FUN_1400504c0`: the nomad drops what it does and walks to `to`.
363    fn nomad_walk(&mut self, id: usize, to: [f32; 3]) {
364        let Some(nomad) = self.wildlife.nomads.get(&id) else {
365            return;
366        };
367        let Some(group) = self
368            .wildlife
369            .groups
370            .get(&(nomad.template, nomad.variant))
371            .cloned()
372        else {
373            return;
374        };
375        let walk = MoveTo::new(to, 1, 0, 0xe1f, 0, -1.0, self.rng);
376        let ch = &mut self.wildlife.nomads.get_mut(&id).unwrap().character;
377        let mut stack = std::mem::take(&mut ch.stack);
378        stack.clear(ch, &group, &mut []);
379        stack.push(ch, &group, Action::move_to(walk));
380        ch.stack = stack;
381    }
382
383    fn remove_nomad(&mut self, id: usize) {
384        if let Some(nomad) = self.wildlife.nomads.remove(&id) {
385            self.wildlife.tree.remove(nomad.character.tree_slot);
386            self.placed[id].alive = false;
387            *self.models_dirty = true;
388        }
389    }
390
391    /// The nomads' StateMachine updates: 0 at the town hall, it tells it (message 6) and waits; 1
392    /// waiting, it wanders now and then; 2 back where it came from, it is gone.
393    ///
394    /// Original: `FUN_140050320`, `FUN_14004ff60`, `FUN_14004fe90`.
395    pub fn update_nomads(&mut self, dt: i64) {
396        let ids: Vec<usize> = self.wildlife.nomads.keys().copied().collect();
397        for id in ids {
398            // CharacterComponent's scheduled notify (`FUN_14010e950`): MoveTo's follow check
399            // (`FUN_1400cde40`).
400            if let Some(nomad) = self.wildlife.nomads.get_mut(&id) {
401                let following = self.wildlife.tree.follows(nomad.character.tree_slot);
402                nomad.character.stack.notify_moves(following);
403            }
404            let Some(nomad) = self.wildlife.nomads.get(&id) else {
405                continue;
406            };
407            if nomad.character.stack.is_active() {
408                continue;
409            }
410            match nomad.state {
411                0 => match nomad.town_hall.filter(|t| self.sites.contains_key(t)) {
412                    None => self.remove_nomad(id),
413                    Some(t) => {
414                        self.immigration_message(t, 6);
415                        if let Some(n) = self.wildlife.nomads.get_mut(&id) {
416                            n.state = 1;
417                        }
418                    }
419                },
420                1 => self.nomad_wander(id, dt),
421                _ => self.remove_nomad(id),
422            }
423        }
424    }
425
426    /// `FUN_14004ff60`: every 30 ± 15 s, a ground cell (types 0..2) of the 16 × 16 around
427    /// where it waits, a third of a cell off its centre.
428    fn nomad_wander(&mut self, id: usize, dt: i64) {
429        let n = self.size as i32;
430        let nomad = self.wildlife.nomads.get_mut(&id).unwrap();
431        nomad.timer -= dt;
432        if 0 <= nomad.timer {
433            return;
434        }
435        let target = nomad.target;
436        let t = self.rng.rand_float_tol() * 15.0 + 30.0;
437        self.wildlife.nomads.get_mut(&id).unwrap().timer = ticks(t);
438        let c = self.cell_of(target);
439        let (x0, y0) = (c % n - 8, c / n - 8);
440        let mut cells = Vec::new();
441        for y in y0..y0 + 16 {
442            for x in x0..x0 + 16 {
443                if 0 <= x
444                    && x < n
445                    && 0 <= y
446                    && y < n
447                    && self.cells.types[(y * n + x) as usize].0 < 3
448                {
449                    cells.push(y * n + x);
450                }
451            }
452        }
453        if cells.is_empty() {
454            return;
455        }
456        let k = self.rng.rand_below(cells.len() as u32) as usize;
457        let dy = self.rng.rand_float_tol() * 0.33;
458        let dx = self.rng.rand_float_tol() * 0.33;
459        let g = self.ground_at(cells[k]);
460        self.nomad_walk(id, [g[0] + dx, g[1] + dy, g[2]]);
461    }
462
463    /// A nomad's state message: 1 (`FUN_14004fe00`, in 0 and 1) it leaves for where it came
464    /// from and forgets the town hall; 0 (`FUN_14004fd80`, in 1) it is gone.
465    fn nomad_message(&mut self, id: usize, msg: i32) {
466        let Some(nomad) = self.wildlife.nomads.get_mut(&id) else {
467            return;
468        };
469        match (nomad.state, msg) {
470            (0 | 1, 1) => {
471                nomad.state = 2;
472                let spawn = nomad.spawn;
473                self.nomad_walk(id, spawn);
474                if let Some(n) = self.wildlife.nomads.get_mut(&id) {
475                    n.town_hall = None;
476                }
477            }
478            (1, 0) => self.remove_nomad(id),
479            _ => {}
480        }
481    }
482
483    /// `FUN_14004dc60`: the band leaves (message 1 to each).
484    pub(crate) fn send_band_away(&mut self, e: usize) {
485        let band = self
486            .immigration(e)
487            .map(|i| std::mem::take(&mut i.nomads))
488            .unwrap_or_default();
489        for id in band {
490            self.nomad_message(id, 1);
491        }
492        if let Some(i) = self.immigration(e) {
493            i.arrived = 0;
494        }
495    }
496
497    /// State 0x11 enter (`FUN_14004d810`): event 14 at severity 2.
498    pub(crate) fn band_arrived(&mut self, e: usize) {
499        self.site_events.push((ARRIVED, 2, e));
500    }
501
502    /// State 0x11 update (`FUN_14004d760`): from the leaving month the band goes.
503    pub(crate) fn band_waiting(&mut self, e: usize) {
504        let Some(d) = self.immigration_desc(e) else {
505            return;
506        };
507        if d.leave <= self.calendar_month() {
508            self.send_band_away(e);
509            self.machine(e).request(2);
510        }
511    }
512
513    /// The ImmigrationComponent's state messages: 6 a nomad arrived (in 0x10); 8 turned away (in
514    /// 0x11). Message 7 (taking them in) needs the citizens:
515    /// `crate::behaviour::agents::Agents::admit_band`.
516    ///
517    /// Original: `FUN_14004d950`, `FUN_14004d680`, `FUN_14004d6f0`.
518    pub fn immigration_message(&mut self, e: usize, msg: i32) {
519        if self.immigration(e).is_none() || self.machine(e).index < 0 {
520            return;
521        }
522        match (self.machine(e).state, msg) {
523            (0x10, 6) => {
524                let i = self.immigration(e).unwrap();
525                i.arrived += 1;
526                if i.arrived == i.nomads.len() as i32 {
527                    self.machine(e).request(0x11);
528                }
529            }
530            (0x11, 8) => {
531                self.send_band_away(e);
532                self.machine(e).request(2);
533            }
534            _ => {}
535        }
536    }
537
538    // +0x60; +0x64
539    /// The visitors' disease risk (the larger of the town's and, for the longer of the town's and
540    /// months) and the band (each nomad and where it stands) for the citizens to be made. `None`
541    /// unless the band is waiting (0x11).
542    ///
543    /// Original: `FUN_14004ddb0` (town side).
544    pub fn take_band(&mut self, e: usize) -> Option<Vec<(usize, [f32; 3])>> {
545        if self.machine(e).index < 0 || self.machine(e).state != 0x11 {
546            return None;
547        }
548        let d = self.immigration_desc(e)?.clone();
549        let band = self.immigration(e)?.nomads.clone();
550        if self.visitors.0 < d.risk {
551            self.visitors.0 = d.risk;
552        }
553        let time = ticks(d.risk_months as f32 * PERIOD);
554        if self.visitors.1 < time {
555            self.visitors.1 = time;
556        }
557        Some(
558            band.iter()
559                .filter_map(|&id| Some((id, self.wildlife.nomads.get(&id)?.character.position)))
560                .collect(),
561        )
562    }
563
564    /// After the citizens are made: each nomad is gone (message 0) and the town hall goes back
565    /// to 2.
566    pub fn band_taken(&mut self, e: usize) {
567        let band = self
568            .immigration(e)
569            .map(|i| std::mem::take(&mut i.nomads))
570            .unwrap_or_default();
571        for id in band {
572            self.nomad_message(id, 0);
573        }
574        if let Some(i) = self.immigration(e) {
575            i.arrived = 0;
576        }
577        self.machine(e).request(2);
578    }
579}