Skip to main content

banished_game/nature/
seeding.rs

1//! New Game resource seeding: the start structures' draws and the procedural rock, ore and tree
2//! pass with spawned herbs, mushrooms and berries. See docs/RESOURCE-SEEDING.md.
3//!
4//! The original builds full component entities here. This port keeps only what the pass reads and
5//! writes — the map cells it stamps, the growth and model choices and every draw from the game's
6//! shared random stream, in the original order. It also runs scheduled growth, periodic child
7//! placement and natural-resource removal; see docs/GAME-LOOP-STEP-18.md.
8//!
9//! Original: `FUN_1400a87d0`, `FUN_1400a7490`, `FUN_1400a6780`.
10
11use banished_formats::game::{
12    GrowthDescription, MapDescription, ModelDescription, NaturalResourceDescription,
13    PlacedStructure,
14};
15
16use crate::behaviour::animation::Playback;
17use crate::nature::growth::{adjust_map_byte, Growth};
18use crate::nature::spawn::Spawn;
19use crate::noise::PerlinNoise;
20use crate::random::Random;
21use crate::scheduler::{ticks, EntityScheduler};
22use crate::terrain::{CellType, HeightGrid};
23use banished_formats::types::AnimationGroup;
24use std::sync::Arc;
25
26/// The descriptions of one template that seeding reads.
27#[derive(Debug, Clone)]
28pub struct Template {
29    /// `Template\X.rsc:resource`.
30    pub reference: String,
31    // +0x20
32    /// ComponentDescription: scheduled bucket; 6 means unscheduled.
33    pub bucket: usize,
34    /// The MapDescription: the footprint on the map.
35    pub map: Option<MapDescription>,
36    /// The ModelDescription: the model's variants.
37    pub model: Option<ModelDescription>,
38    /// ModelDescription variant animation groups, cached in the original ExternalFactory.
39    pub animations: Vec<Option<Arc<AnimationGroup>>>,
40    /// The `AnimAudioEvent`s its clips fire: event resource, SoundEffect and probability.
41    pub audio_events: Vec<(String, Option<String>, f32)>,
42    /// The NaturalResourceDescription (trees, rocks, crops).
43    pub natural: Option<NaturalResourceDescription>,
44    /// The GrowthDescription.
45    pub growth: Option<GrowthDescription>,
46    /// FruitDescription (orchard trees).
47    pub fruit: Option<crate::production::orchard::FruitTemplate>,
48    // Compared by pointer in FUN_140126100.
49    /// The reference of the growth description, to compare two resources' growth.
50    pub growth_reference: Option<String>,
51    /// Spawn entries: (index into the template table, lo, hi).
52    pub spawn: Vec<(usize, f32, f32)>,
53    /// Scheduled component callbacks, in the ComponentDescription's construction order.
54    pub updates: Vec<ScheduledComponent>,
55    // CreatePlacedDescription +0x44/+0x48
56    /// `CreatePlacedDescription`: building footprint size.
57    pub size: Option<(i32, i32)>,
58    // +0x28; +0x24; +0x20
59    /// `StorageDescription`: accepted flags, capacity, capacity per footprint cell.
60    pub storage: Option<(u32, i32, bool)>,
61    /// Has a `StorageLocationDescription` (a town storage the starting goods go to).
62    pub storage_location: bool,
63    /// The `StorageLocationDescription`'s accepted flags.
64    pub storage_location_flags: u32,
65    /// Has a StorageDisplayComponent (its stored goods are drawn on its cells).
66    pub storage_display: bool,
67    /// A market's radius.
68    pub market_radius: Option<i32>,
69    /// Whether it is a market (a town storage with a radius).
70    pub market_location: bool,
71    /// Whether it has an InteractDescription.
72    pub has_interact: bool,
73    /// InteractDescription "use" points in model space (its point list's category 0).
74    pub use_points: Vec<[f32; 3]>,
75    // +0x20
76    /// A natural resource's pile template (NaturalResourceDescription).
77    pub pile: Option<usize>,
78    // +0x40; +0x44
79    /// A pile template's raw material (its RawMaterialDescription).
80    pub pile_material: Option<usize>,
81    /// A pile template's yields: (uneducated, educated).
82    pub pile_yield: (i32, i32),
83    /// InteractDescription "build" points in model space (category 1): where builders stand
84    /// and materials are delivered.
85    pub build_points: Vec<[f32; 3]>,
86    /// The "use" points' yaws, in degrees.
87    pub use_yaws: Vec<f32>,
88    /// The "create" points (category 2) where a workplace leaves its output.
89    pub create_points: Vec<[f32; 3]>,
90    /// The InteractDescription's other named points (name, position, yaw): the trading
91    /// post's "boat".
92    pub named_points: Vec<(String, [f32; 3], f32)>,
93    /// FloorDescription: its picking mesh's triangles in model space (bridge pieces, docks,
94    /// porches).
95    pub floor: Option<std::sync::Arc<Vec<[[f32; 3]; 3]>>>,
96    // +0x28
97    /// FloorDescription: the floor is added on activation (bridge pieces); the others are added
98    /// when their MultiObject is built.
99    ///
100    /// Original: `FUN_140124810`, `FUN_14012aa30`.
101    pub floor_on_activate: bool,
102    /// TerraformDescription (tunnels).
103    pub terraform: Option<crate::world::terraform::TerraformTemplate>,
104    /// TornadoDescription.
105    pub tornado: Option<crate::disasters::tornado::TornadoTemplate>,
106    /// QuarryDescription.
107    pub quarry: Option<crate::production::quarry::QuarryTemplate>,
108    /// Has a TradeComponent (the trading post).
109    pub trading_post: bool,
110    /// Has a TrackerComponent (workplaces whose production the window shows).
111    pub tracker: bool,
112    // FUN_1400e02d0 sets only the MapComponent.
113    /// Whether this is a fenced area's template (it has a `createfenced` component). Its model is
114    /// never placed, so it stays at the origin with no rotation; `Placed::yaw` is its rotation on
115    /// the map.
116    pub fenced_area: bool,
117}
118
119/// The kinds of scheduled component the port runs for placed entities.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum ScheduledComponent {
122    /// The growth update.
123    Growth,
124    /// The spawn update.
125    Spawn,
126}
127
128/// A placed entity.
129#[derive(Debug, Clone, PartialEq)]
130pub struct Placed {
131    /// Index into the template table.
132    pub template: usize,
133    // +0x1f
134    /// Entity: actual registration bucket, which can differ from its own template.
135    pub bucket: usize,
136    /// Map cell.
137    pub cell: (i32, i32),
138    // +0x2c/+0x2e
139    /// MapComponent, after rotation; profile yards have variable dimensions.
140    pub footprint: (i32, i32),
141    /// Map position (cells from the terrain centre) and ground height.
142    pub position: [f32; 3],
143    /// The heading, in degrees.
144    pub yaw: f32,
145    /// The model variant.
146    pub variant: usize,
147    /// The mesh.
148    pub mesh: usize,
149    // NaturalResourceComponent +0x28; the constructor FUN_140036eb0 sets 1.0.
150    /// How grown the resource is (1.0 by default).
151    pub growth: f32,
152    /// The growth component, if any.
153    pub growth_state: Option<Growth>,
154    /// The spawn component, if any.
155    pub spawn_state: Option<Spawn>,
156    /// Whether it is still in the world.
157    pub alive: bool,
158    /// NaturalResourceComponent flag 0x200000.
159    ///
160    /// Original: `FUN_14012bc50`.
161    pub removal_requested: bool,
162    /// The animation it plays, if any.
163    pub animation: Option<Playback>,
164    // MeshInstance +0x134
165    /// Mesh instance scale (`MeshInstance`).
166    pub scale: [f32; 4],
167}
168
169impl Placed {
170    // +0x148
171    /// The entity transform: `FromYawPitchOffset(yaw, 0, position)`, or for a fenced area, whose
172    /// model is never placed, the identity at its position.
173    pub fn model(&self, t: &Template) -> crate::math::Transform {
174        if t.fenced_area {
175            crate::math::Transform {
176                q: [0.0, 0.0, 0.0, 1.0],
177                p: self.position,
178            }
179        } else {
180            crate::math::Transform::of_model(self.yaw, self.position)
181        }
182    }
183}
184
185/// Stamps for [`Cells::types_revision`], unique across every `Cells`.
186static TYPES_REVISION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
187
188fn next_types_revision() -> u64 {
189    TYPES_REVISION.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
190}
191
192// Game +0xf8
193/// The gameplay map cells seeding reads and writes (`Game`, 48-byte cells).
194pub struct Cells {
195    /// The map's size in cells.
196    pub size: usize,
197    // +0x29
198    /// The type of each cell. Change it only with [`Cells::set_type`], which keeps [`Cells::types_revision`] current.
199    pub types: Vec<CellType>,
200    /// A new stamp whenever `types` may have changed (a new map, any `set_type`), so readers
201    /// such as the path regions can skip comparing the whole map when it has not.
202    pub types_revision: u64,
203    // +0x08
204    /// Entity on the cell (index into the placed list).
205    pub objects: Vec<Option<usize>>,
206    // +0x00; +0x08; used by FUN_1400a4aa0.
207    /// The entity occupying each cell, for placement checks (separate from the objects stamped
208    /// into the cell).
209    pub occupants: Vec<Option<usize>>,
210    // +0x28
211    /// Placement admission flag, deliberately unnamed.
212    pub flags: Vec<u8>,
213    // +0x10
214    /// External ComponentDescription reference from a growth transition.
215    pub previous_template: Vec<Option<usize>>,
216    // +0x2c; zero in FUN_14001b3f0's constructor.
217    /// A byte per cell that growth adjusts.
218    pub growth_bytes: Vec<u8>,
219    // +0x1c..+0x27
220    /// Per-cell layer counters: 0..=5 the HappinessComponent kinds, 8 the fishing docks.
221    ///
222    /// Original: `FUN_1400a4e60`.
223    pub layers: Vec<[u8; 12]>,
224    // +0x90
225    /// Map: registrations per layer.
226    pub layer_totals: [i16; 12],
227    // +0x2a
228    /// The road kind the cell has or will have (0 stone, 1 dirt; `0xff` none).
229    pub road_kind: Vec<u8>,
230    // +0x2b
231    /// The road kind built (`0x80` while it is being built).
232    pub road_built: Vec<u8>,
233    /// Bumped whenever a road is planned, taken back or finished (the road decals' rebuild).
234    ///
235    /// Original: `FUN_1400b4840`.
236    pub road_revision: u32,
237    // +0x2d
238    /// The floors (bridge decks) that characters walk on.
239    pub floors: crate::world::floor::Floors,
240    /// Cells that turned walkable or unwalkable, in the order they did: the path sectors' marks,
241    /// which every type write in the original makes on the spot.
242    ///
243    /// Original: `FUN_1400ae160`.
244    pub sector_marks: Vec<u32>,
245}
246
247impl Cells {
248    // +0x00; +0x08
249    /// The entity a cell answers for, the occupant if there is one, else the object. EditTool
250    /// selection, the removal tool and the job code all read cells this way.
251    ///
252    /// Original: `FUN_1400a5750`.
253    pub fn entity_at(&self, i: usize) -> Option<usize> {
254        self.occupants[i].or(self.objects[i])
255    }
256
257    /// The cells of a map of `size × size` cells with these natural types.
258    pub fn new(size: usize, types: Vec<CellType>) -> Cells {
259        Cells {
260            size,
261            types_revision: next_types_revision(),
262            objects: vec![None; types.len()],
263            occupants: vec![None; types.len()],
264            flags: vec![0; types.len()],
265            previous_template: vec![None; types.len()],
266            growth_bytes: vec![0; types.len()],
267            layers: vec![[0; 12]; types.len()],
268            layer_totals: [0; 12],
269            road_kind: vec![0xff; types.len()],
270            road_built: vec![0; types.len()],
271            floors: crate::world::floor::Floors::new(types.len()),
272            types,
273            road_revision: 0,
274            sector_marks: Vec::new(),
275        }
276    }
277
278    // +0x29; each writer tests the masks 0xe1f and 0x1e0 before FUN_1400ae160.
279    /// Write a cell's type. When the cell turns walkable or unwalkable, its path sector is marked
280    /// for rebuilding.
281    pub fn set_type(&mut self, i: usize, ty: CellType) {
282        let walk = |t: CellType| (1u32 << (t.0 & 0x1f)) & crate::pathfind::WALKABLE != 0;
283        if walk(self.types[i]) != walk(ty) {
284            self.sector_marks.push(i as u32);
285        }
286        self.types[i] = ty;
287        self.types_revision = next_types_revision();
288    }
289
290    /// Add `delta` to `layer` on every cell within `radius` of `(cx, cy)` (a byte, wrapping) and to
291    /// the layer's total.
292    ///
293    /// Original: `FUN_1400a4e60`.
294    pub fn add_layer(&mut self, layer: usize, cx: i32, cy: i32, radius: i32, delta: i32) {
295        let n = self.size as i32;
296        for y in cy - radius..=cy + radius {
297            for x in cx - radius..=cx + radius {
298                let (dx, dy) = (cx - x, cy - y);
299                if x < 0 || y < 0 || x >= n || y >= n || dx * dx + dy * dy > radius * radius {
300                    continue;
301                }
302                let b = &mut self.layers[(n * y + x) as usize][layer];
303                *b = b.wrapping_add(delta as u8);
304            }
305        }
306        self.layer_totals[layer] = self.layer_totals[layer].wrapping_add(delta as i16);
307    }
308}
309
310/// `DAT_140178814`, `DAT_14017884c`.
311const HALF: f32 = 0.5;
312const ONE: f32 = 1.0;
313/// `DAT_140178920`: yaw range (degrees) of the jittered transform.
314pub(crate) const YAW_RANGE: f32 = 180.0;
315/// `FUN_1400560e0` spot check: normal z and height tolerance (`DAT_140178840`, `DAT_140178948`).
316const SPAWN_NORMAL: f32 = 0.98;
317const SPAWN_HEIGHT_TOLERANCE: f32 = 1000.0;
318
319/// What the growth and spawn updates need: the cells, the templates, the random numbers, and the
320/// lists to fill.
321pub struct Seeder<'a> {
322    /// The object templates.
323    pub templates: &'a [Template],
324    /// The ground's heights.
325    pub terrain: &'a HeightGrid<'a>,
326    /// The gameplay map.
327    pub cells: &'a mut Cells,
328    /// The game's random numbers.
329    pub rng: &'a mut Random,
330    /// The placed objects.
331    pub placed: &'a mut Vec<Placed>,
332    /// The entities to remove, in the order they asked. They are removed after all buckets.
333    ///
334    /// Original: drained by `FUN_1400c9560`.
335    pub removed: &'a mut Vec<usize>,
336    /// `AnimAudioEvent`s that passed their probability: owning entity and the SoundEffect to play
337    /// there.
338    ///
339    /// Original: `FUN_14008d310`.
340    pub animation_events: &'a mut Vec<(usize, String)>,
341}
342
343impl<'a> Seeder<'a> {
344    fn n(&self) -> i32 {
345        self.cells.size as i32
346    }
347
348    /// Map position of a cell centre: `cell − (n/2 − 0.5)`.
349    fn cell_position(&self, index: i32) -> (f32, f32) {
350        let n = self.n();
351        let c = (n >> 1) as f32 - HALF;
352        ((index % n) as f32 - c, (index / n) as f32 - c)
353    }
354
355    /// `FUN_1400a48b0(.., 0, normal)`: the ground, or a floor above it, and the normal's z.
356    fn ground(&self, x: f32, y: f32) -> (f32, f32) {
357        let mut normal = [0.0f32; 3];
358        let h = self
359            .cells
360            .floors
361            .ground_normal(self.cells.size, &self.terrain, x, y, &mut normal);
362        (h, normal[2])
363    }
364
365    // Game +0xd0 |= 1
366    /// The profile's start structures, relative to the start cell.
367    ///
368    /// Each is built in "instant" mode (`Game |= 1`), so the build component shows the last
369    /// (finished) mesh. Component setup draws nothing from the stream except the model's variant;
370    /// buildings do not stamp the map on activation.
371    ///
372    /// Original: `FUN_1400a7490`, `FUN_14010cfb0`.
373    pub fn place_structures(
374        &mut self,
375        structures: &[(PlacedStructure, Option<usize>)],
376        sx: i32,
377        sy: i32,
378    ) {
379        let n = self.n();
380        let corner = |s: &Self, x: i32, y: i32| {
381            let i = n * y + x;
382            let half = (n >> 1) as f32;
383            let (px, py) = ((i % n) as f32 - half, (i / n) as f32 - half);
384            let (z, _) = s.ground(px, py);
385            [px, py, z]
386        };
387        for (record, template) in structures {
388            let Some(t) = *template else { continue };
389            // Size from the template, otherwise the record's two integers; swapped when turned
390            // by ±90 or 270 degrees.
391            let (mut w, mut h) = self.templates[t].size.unwrap_or((record.a, record.b));
392            if matches!(record.rotation, 90 | -90 | 270) {
393                std::mem::swap(&mut w, &mut h);
394            }
395            let (x, y) = (sx + record.x, sy + record.y);
396            let p0 = corner(self, x, y);
397            let p1 = corner(self, x + w, y + h);
398            let position = [
399                (p1[0] + p0[0]) * HALF,
400                (p1[1] + p0[1]) * HALF,
401                (p1[2] + p0[2]) * HALF,
402            ];
403            self.construct(t);
404            // The yard is an entity with storage/map components but no model. Retain it;
405            // only model-bearing structures consume the model-variant draw.
406            let (variant, mesh) = self.templates[t].model.as_ref().map_or((0, 0), |model| {
407                let variant = self.rng.rand_below(model.variants.len() as u32) as usize;
408                let mesh = model
409                    .variants
410                    .get(variant)
411                    .map_or(0, |v| v.meshes.len().saturating_sub(1));
412                (variant, mesh)
413            });
414            self.placed.push(Placed {
415                template: t,
416                bucket: self.templates[t].bucket,
417                cell: (x, y),
418                footprint: (w, h),
419                position,
420                yaw: record.rotation.rem_euclid(360) as f32,
421                variant,
422                mesh,
423                growth: 0.0,
424                growth_state: None,
425                spawn_state: None,
426                alive: true,
427                removal_requested: false,
428                animation: None,
429                scale: [ONE; 4],
430            });
431        }
432    }
433
434    /// `FUN_1400c8bf0` setup pass for a natural resource: the model component picks a variant
435    /// (`FUN_140129f90`) and the growth component its maximum (`FUN_140125bc0`), in template
436    /// order (map, model, naturalresource, growth, spawn).
437    fn construct(&mut self, template: usize) -> (usize, usize, f32) {
438        let t = &self.templates[template];
439        let (mut variant, mut mesh) = (0usize, 0usize);
440        if let Some(m) = &t.model {
441            variant = m.variant.max(0) as usize;
442            if m.random_variant {
443                variant = self.rng.rand_below(m.variants.len() as u32) as usize;
444            }
445            mesh = m.mesh.max(0) as usize;
446        }
447        let mut max_growth = 1.0;
448        if let Some(g) = &t.growth {
449            max_growth = if g.max_base <= 0.0 {
450                -1.0
451            } else {
452                self.rng.rand_float_tol() * g.max_range + g.max_base
453            };
454        }
455        (variant, mesh, max_growth)
456    }
457
458    /// `FUN_14012ba10`: jitter the entity inside its cell and pick a yaw.
459    fn position(&mut self, template: usize, index: i32) -> ([f32; 3], f32) {
460        let scale = self.templates[template]
461            .natural
462            .as_ref()
463            .map_or(0.0, |n| n.position_scale);
464        let (x, y) = self.cell_position(index);
465        let (z, _) = self.ground(x, y);
466        let dy = self.rng.rand_float_tol();
467        let dx = self.rng.rand_float_tol();
468        let position = [dx * scale + x, dy * scale + y, z + 0.0];
469        let yaw = self.rng.rand_float_tol() * YAW_RANGE;
470        (position, yaw)
471    }
472
473    /// `FUN_140126280`: set the growth from a 0..1 fraction of the maximum and draw the timer.
474    fn set_growth(
475        &mut self,
476        template: usize,
477        max_growth: f32,
478        fraction: f32,
479    ) -> (Growth, f32, [f32; 4]) {
480        Growth::initialise(
481            self.templates[template].growth.as_ref().unwrap(),
482            max_growth,
483            fraction,
484            self.rng,
485        )
486    }
487
488    /// `FUN_1400c9d70` → `FUN_140129840`: register and stamp the map footprint (natural
489    /// resources: one cell of the description's type).
490    fn register(&mut self, placed: Placed) -> usize {
491        let id = self.placed.len();
492        if let Some(map) = self.templates[placed.template].map.clone() {
493            if map.stamp && !map.no_stamp {
494                let i = (placed.cell.1 * self.n() + placed.cell.0) as usize;
495                // FUN_1400a30f0 queues the old stamped entity's removal before replacing it.
496                if let Some(old) = self.cells.objects[i] {
497                    self.remove(old);
498                }
499                self.cells.set_type(i, CellType(map.cell_type as u8));
500                self.cells.objects[i] = Some(id);
501            }
502        }
503        self.placed.push(placed);
504        id
505    }
506
507    /// `FUN_140126100`: at most `max_neighbours` occupied cells and `max_same` of the same
508    /// growth type among the 8 neighbours. Cached external-reference identities replace the
509    /// original GrowthDescription pointer comparison (including descriptions shared by templates).
510    pub(crate) fn density_ok(&self, template: usize, x: i32, y: i32) -> bool {
511        let Some(growth) = &self.templates[template].growth else {
512            return true;
513        };
514        let n = self.n();
515        let (mut occupied, mut same) = (0, 0);
516        for cy in y - 1..y + 2 {
517            for cx in x - 1..x + 2 {
518                if (cx == x && cy == y) || cx < 0 || cy < 0 || cx >= n || cy >= n {
519                    continue;
520                }
521                if let Some(o) = self.cells.objects[(cy * n + cx) as usize] {
522                    occupied += 1;
523                    if self.templates[self.placed[o].template].growth_reference
524                        == self.templates[template].growth_reference
525                    {
526                        same += 1;
527                    }
528                }
529            }
530        }
531        !(growth.max_same < same || growth.max_neighbours < occupied)
532    }
533
534    /// `FUN_1400a4aa0(map, x, y, 4, 0.98, 1000, 0, …)`: buildable ground with no entity and a
535    /// flat enough surface.
536    fn spawn_spot_ok(&self, x: i32, y: i32) -> bool {
537        let n = self.n();
538        if x < 0 || y < 0 || x >= n || y >= n {
539            return false;
540        }
541        let i = y * n + x;
542        if self.cells.types[i as usize] != CellType::GROUND {
543            return false;
544        }
545        if self.cells.occupants[i as usize].is_some() || self.cells.flags[i as usize] != 0 {
546            return false;
547        }
548        let (px, py) = self.cell_position(i);
549        let mut normal = [0.0f32; 3];
550        let h =
551            self.cells
552                .floors
553                .ground_normal(self.cells.size, &self.terrain, px, py, &mut normal);
554        SPAWN_NORMAL <= normal[2] && (h - 0.0).abs() <= SPAWN_HEIGHT_TOLERANCE
555    }
556
557    /// `FUN_140056620`: spawn children around a newly grown parent.
558    fn spawn(&mut self, parent: usize) {
559        let template = self.placed[parent].template;
560        let growth = self.placed[parent].growth;
561        for (entry, (child, lo, hi)) in self.templates[template]
562            .spawn
563            .clone()
564            .into_iter()
565            .enumerate()
566        {
567            if !(lo <= growth && growth <= hi) {
568                continue;
569            }
570            let step = self.templates[child]
571                .growth
572                .as_ref()
573                .map_or(1.0, |g| g.max_base);
574            let attempts = (growth / step) as i32 + 1;
575            for _ in 0..attempts {
576                self.spawn_child(parent, entry, true, false);
577            }
578        }
579    }
580
581    /// Startup uses random_growth=true; scheduled spawning uses false. The child is registered in
582    /// the PARENT TEMPLATE's bucket (not its own template's bucket).
583    ///
584    /// Original: `FUN_1400560e0`.
585    pub fn spawn_child(
586        &mut self,
587        parent: usize,
588        entry: usize,
589        random_growth: bool,
590        disabled: bool,
591    ) -> Option<usize> {
592        if disabled {
593            return None;
594        }
595        let parent_template = self.placed[parent].template;
596        let child = self.templates[parent_template].spawn[entry].0;
597        let (px, py) = self.placed[parent].cell;
598        let Some(g) = self.templates[child].growth.clone() else {
599            return None;
600        };
601        if self.rng.rand_float() > ONE / g.spawn_one_in as f32 {
602            return None;
603        }
604        let x = px + self.rng.rand_tol(g.spawn_radius);
605        let y = py + self.rng.rand_tol(g.spawn_radius);
606        let n = self.n();
607        if x < 0 || y < 0 || x >= n || y >= n || !self.spawn_spot_ok(x, y) {
608            return None;
609        }
610        if !self.density_ok(child, x, y) {
611            return None;
612        }
613        let (variant, mesh, max_growth) = self.construct(child);
614        let (position, yaw) = self.position(child, n * y + x);
615        let (state, growth, scale) = self.set_growth(child, max_growth, 0.0);
616        let id = self.register(Placed {
617            template: child,
618            bucket: self.templates[parent_template].bucket,
619            cell: (x, y),
620            footprint: (1, 1),
621            position,
622            yaw,
623            variant,
624            mesh,
625            growth,
626            growth_state: Some(state),
627            spawn_state: self.spawn_state(child),
628            alive: true,
629            removal_requested: false,
630            animation: None,
631            scale,
632        });
633        if random_growth {
634            let fraction = self.rng.rand_float();
635            let (state, growth, scale) = self.set_growth(child, max_growth, fraction);
636            self.placed[id].growth = growth;
637            self.placed[id].scale = scale;
638            self.placed[id].growth_state = Some(state);
639        }
640        Some(id)
641    }
642
643    /// CreateNaturalResource's start on a free cell: clone the template, place it in the cell,
644    /// growth 0, then a random model variant and mesh, and register it.
645    ///
646    /// Original: `FUN_1400cbb20`, `FUN_1400c8bf0`, `FUN_14012ba10`, `FUN_140126280`,
647    /// `FUN_14012a8a0`.
648    pub fn plant(&mut self, template: usize, cell: i32) -> usize {
649        let (_, _, max_growth) = self.construct(template);
650        let (position, yaw) = self.position(template, cell);
651        let (state, growth, scale) = self.set_growth(template, max_growth, 0.0);
652        let variants = self.templates[template]
653            .model
654            .as_ref()
655            .map_or(1, |m| m.variants.len().max(1));
656        let variant = self.rng.rand_below(variants as u32) as usize;
657        let meshes = self.templates[template]
658            .model
659            .as_ref()
660            .and_then(|m| m.variants.get(variant))
661            .map_or(1, |v| v.meshes.len().max(1));
662        let mesh = self.rng.rand_below(meshes as u32) as usize;
663        let n = self.n();
664        self.register(Placed {
665            template,
666            bucket: self.templates[template].bucket,
667            cell: (cell % n, cell / n),
668            footprint: (1, 1),
669            position,
670            yaw,
671            variant,
672            mesh,
673            growth,
674            growth_state: Some(state),
675            spawn_state: self.spawn_state(template),
676            alive: true,
677            removal_requested: false,
678            animation: None,
679            scale,
680        })
681    }
682
683    fn spawn_state(&self, template: usize) -> Option<Spawn> {
684        self.templates[template]
685            .updates
686            .contains(&ScheduledComponent::Spawn)
687            .then(|| Spawn::new(self.templates[template].spawn.len()))
688    }
689
690    /// Port of FUN_140125070: reproduce using the parent's template and model variant. Unlike
691    /// FUN_1400560e0's startup children, this path initializes the child's growth at zero and
692    /// selects a mesh after the timer draw; it never draws a random initial growth fraction.
693    fn periodic_spawn(&mut self, parent: usize) -> Option<usize> {
694        let template = self.placed[parent].template;
695        let g = self.templates[template].growth.as_ref()?;
696        if self.rng.rand_float() > ONE / g.spawn_one_in as f32 {
697            return None;
698        }
699        let (px, py) = self.placed[parent].cell;
700        let x = px + self.rng.rand_tol(g.spawn_radius);
701        let y = py + self.rng.rand_tol(g.spawn_radius);
702        if !self.spawn_spot_ok(x, y) || !self.density_ok(template, x, y) {
703            return None;
704        }
705        let variant = self.placed[parent].variant;
706        let (_, _, maximum) = self.construct(template);
707        let (position, yaw) = self.position(template, y * self.n() + x);
708        let (state, growth, scale) = self.set_growth(template, maximum, 0.0);
709        let meshes = self.templates[template]
710            .model
711            .as_ref()?
712            .variants
713            .get(variant)?
714            .meshes
715            .len();
716        let mesh = self.rng.rand_below(meshes as u32) as usize;
717        Some(self.register(Placed {
718            template,
719            bucket: self.templates[template].bucket,
720            cell: (x, y),
721            footprint: (1, 1),
722            position,
723            yaw,
724            variant,
725            mesh,
726            growth,
727            growth_state: Some(state),
728            spawn_state: self.spawn_state(template),
729            alive: true,
730            removal_requested: false,
731            animation: None,
732            scale,
733        }))
734    }
735
736    /// Port of FUN_140125a30 and FUN_1400a33b0 for one-cell natural-resource removal. Keep
737    /// handles stable; the scheduler swaps its bucket entries separately at the end of the tick.
738    fn remove(&mut self, id: usize) {
739        let p = &mut self.placed[id];
740        if !p.alive {
741            return;
742        }
743        p.alive = false;
744        self.removed.push(id);
745        let (x, y) = p.cell;
746        let i = y as usize * self.cells.size + x as usize;
747        if let Some(d) = &self.templates[p.template].growth {
748            if p.growth != 0.0 {
749                let factor = if self.cells.previous_template[i] == Some(p.template) {
750                    d.f3c
751                } else {
752                    d.f40
753                };
754                self.cells.growth_bytes[i] =
755                    adjust_map_byte(self.cells.growth_bytes[i], p.growth.min(1.0) * factor);
756                self.cells.previous_template[i] = Some(p.template);
757            }
758        }
759        if self.cells.objects[i] == Some(id) {
760            let ty = self.terrain.natural_cell_type(self.cells.size, i);
761            self.cells.set_type(i, ty);
762            self.cells.objects[i] = None;
763        }
764    }
765
766    // +0xd0
767    /// One iteration of the placed entities' growth components. `disabled` is the game's bit 10,
768    /// which stops spawning and the change at full growth, but not growth or the decay in frost.
769    /// The callbacks of other components are not installed yet.
770    ///
771    /// Original: `FUN_1400c9560`, then `FUN_140125520`.
772    pub fn iterate(
773        &mut self,
774        scheduler: &mut EntityScheduler<usize>,
775        dt: f32,
776        temperature: f32,
777        disabled: bool,
778    ) -> bool {
779        self.iterate_with(scheduler, dt, temperature, disabled, &mut |_, _, _, _| {
780            false
781        })
782    }
783
784    /// [`Seeder::iterate`] with the scheduled update of entities without growth (`visit`
785    /// returns whether the models changed).
786    pub fn iterate_with(
787        &mut self,
788        scheduler: &mut EntityScheduler<usize>,
789        dt: f32,
790        temperature: f32,
791        disabled: bool,
792        visit: &mut dyn FnMut(&mut Seeder, &mut EntityScheduler<usize>, usize, i64) -> bool,
793    ) -> bool {
794        scheduler.advance(dt);
795        // FUN_1400c9560's bit-0x04 component phase precedes scheduled growth. Within a tree
796        // the ModelComponent (FUN_140129e30) runs before NaturalResource (FUN_14012b110).
797        let mut changed = self.animate(ticks(dt));
798        for bucket in 0..5 {
799            for (_, id, elapsed) in scheduler.visit_bucket(bucket) {
800                // The citizens (bucket 1) are visited for the agents (`take_visits`).
801                if id >= crate::scheduler::EXTRA {
802                    continue;
803                }
804                changed |= self.scheduled_growth(scheduler, id, elapsed, temperature, disabled);
805                // SpawnComponent (`FUN_140056430`) follows Growth in the template's list.
806                changed |= self.scheduled_spawn(scheduler, id, elapsed, disabled);
807                // The entity's other scheduled components (FruitComponent after Growth,
808                // wildlife).
809                if self.placed[id].alive {
810                    changed |= visit(self, scheduler, id, elapsed);
811                }
812            }
813        }
814        // FUN_1400c9560 drains removal requests only after all five buckets.
815        for id in self.removed.drain(..) {
816            scheduler.remove(id);
817        }
818        changed
819    }
820
821    /// SpawnComponent's scheduled update (`FUN_140056430`) for one visited entity: each entry
822    /// whose growth band holds the parent's growth runs its clock, and every child growth
823    /// period (`+0x28 × 300`) it tries a child (`FUN_1400560e0`, without random growth).
824    fn scheduled_spawn(
825        &mut self,
826        scheduler: &mut EntityScheduler<usize>,
827        id: usize,
828        elapsed: i64,
829        disabled: bool,
830    ) -> bool {
831        let p = &self.placed[id];
832        if !p.alive || p.spawn_state.is_none() {
833            return false;
834        }
835        let growth = p.growth;
836        let entries: Vec<(f32, f32, f32)> = self.templates[p.template]
837            .spawn
838            .iter()
839            .map(|&(child, lo, hi)| {
840                let period = self.templates[child].growth.as_ref().map_or(0.0, |g| g.f28);
841                (lo, hi, period)
842            })
843            .collect();
844        let attempts = self.placed[id]
845            .spawn_state
846            .as_mut()
847            .unwrap()
848            .update(growth, elapsed, &entries);
849        let mut changed = false;
850        for (entry, count) in attempts {
851            for _ in 0..count {
852                if let Some(child) = self.spawn_child(id, entry, false, disabled) {
853                    let handle = scheduler.register(child, self.placed[child].bucket);
854                    debug_assert_eq!(handle, child);
855                    changed = true;
856                }
857            }
858        }
859        changed
860    }
861
862    /// GrowthComponent's scheduled update for one visited entity; true when the models changed.
863    fn scheduled_growth(
864        &mut self,
865        scheduler: &mut EntityScheduler<usize>,
866        id: usize,
867        elapsed: i64,
868        temperature: f32,
869        disabled: bool,
870    ) -> bool {
871        let mut changed = false;
872        {
873            let p = &mut self.placed[id];
874            if !p.alive {
875                return changed;
876            }
877            let Some(state) = &mut p.growth_state else {
878                return changed;
879            };
880            let d = self.templates[p.template].growth.as_ref().unwrap();
881            let result = state.update(d, &mut p.growth, elapsed, temperature, disabled);
882            if !disabled {
883                for _ in 0..result.spawn_attempts {
884                    if let Some(child) = self.periodic_spawn(id) {
885                        let handle = scheduler
886                            .register(child, self.templates[self.placed[child].template].bucket);
887                        debug_assert_eq!(handle, child);
888                        changed = true;
889                    }
890                }
891            }
892            if let Some(adjustment) = result.map_adjustment {
893                let p = &self.placed[id];
894                let i = (p.cell.1 * self.n() + p.cell.0) as usize;
895                self.cells.growth_bytes[i] =
896                    adjust_map_byte(self.cells.growth_bytes[i], adjustment);
897                self.cells.previous_template[i] = Some(p.template);
898                if self.templates[p.template]
899                    .natural
900                    .as_ref()
901                    .is_some_and(|n| n.flag)
902                {
903                    // FUN_14012bc50 -> FUN_14012b910 -> FUN_14012a5c0: use the selected
904                    // model variant's animation group, index 2, start time zero.
905                    let p = &mut self.placed[id];
906                    p.removal_requested = true;
907                    p.animation = self.templates[p.template]
908                        .animations
909                        .get(p.variant)
910                        .and_then(Option::as_ref)
911                        .and_then(|g| g.clips.get(2))
912                        .map(|_| Playback::new(2, 0));
913                } else {
914                    self.remove(id);
915                    changed = true;
916                }
917            }
918        }
919        changed
920    }
921
922    /// A single post-iteration smoothing phase.
923    ///
924    /// Original: `FUN_1400c98a0`, `FUN_140125370`.
925    pub fn smooth(&mut self) -> bool {
926        let mut changed = false;
927        for p in self.placed.iter_mut() {
928            if !p.alive {
929                continue;
930            }
931            if let Some(state) = &mut p.growth_state {
932                let d = self.templates[p.template].growth.as_ref().unwrap();
933                changed |= state.smooth(d, p.growth, &mut p.scale);
934            }
935        }
936        changed
937    }
938
939    /// Port of FUN_140129e30 -> Runtime MeshInstance::Update (0x180103090), followed by
940    /// FUN_14012b110's completion/removal callback. The clock advances per simulation iteration,
941    /// while the published pose uses the old clock; pause runs neither callback.
942    fn animate(&mut self, dt: i64) -> bool {
943        let mut finished = Vec::new();
944        let mut changed = false;
945        for (id, p) in self.placed.iter_mut().enumerate() {
946            if !p.alive {
947                continue;
948            }
949            let Some(playback) = &mut p.animation else {
950                if p.removal_requested {
951                    finished.push(id);
952                }
953                continue;
954            };
955            let Some(clip) = self.templates[p.template]
956                .animations
957                .get(p.variant)
958                .and_then(Option::as_ref)
959                .and_then(|g| g.clips.get(playback.clip))
960            else {
961                continue;
962            };
963            if !playback.is_animating(clip) {
964                continue;
965            }
966            for event in playback.update(clip, dt) {
967                let Some(reference) = &clip.events[event].resource else {
968                    continue;
969                };
970                // `AnimAudioEvent` slot +0x40 (`FUN_14008d310`): `RandFloat()` from the game
971                // stream against the probability, then `PlayEffect3D(effect, 4)` at the entity.
972                let audio = self.templates[p.template]
973                    .audio_events
974                    .iter()
975                    .find(|(r, _, _)| r == reference);
976                if let Some((_, effect, probability)) = audio {
977                    if self.rng.rand_float() <= *probability {
978                        if let Some(effect) = effect {
979                            self.animation_events.push((id, effect.clone()));
980                        }
981                    }
982                }
983            }
984            changed = true;
985            if !playback.is_animating(clip) && p.removal_requested {
986                finished.push(id);
987            }
988        }
989        for id in finished {
990            self.remove(id);
991            changed = true;
992        }
993        changed
994    }
995
996    fn perlin(&mut self, persistence: f32, octaves: f32) -> PerlinNoise {
997        let a = self.rng.rand_tol(0x7ff_ffff);
998        let b = self.rng.rand_tol(0x7ff_ffff);
999        PerlinNoise::new(persistence, octaves, b, a)
1000    }
1001
1002    /// Seed every template of the profile's list over the map, away from the start `(sx, sy)` by
1003    /// more than `sqrt(radius_sq)` cells.
1004    ///
1005    /// Original: `FUN_1400a6780`.
1006    pub fn seed(&mut self, list: &[usize], sx: i32, sy: i32, radius_sq: i32) {
1007        // Three fields the pass builds and never reads.
1008        for _ in 0..3 {
1009            self.perlin(ONE, ONE);
1010        }
1011        let n = self.n();
1012        for &template in list {
1013            let Some(nr) = self.templates[template].natural.clone() else {
1014                continue;
1015            };
1016            let variants = self.templates[template]
1017                .model
1018                .as_ref()
1019                .map_or(0, |m| m.variants.len());
1020            let clusters: Vec<PerlinNoise> = if nr.cluster {
1021                (0..variants)
1022                    .map(|_| self.perlin(nr.cluster_persistence, nr.cluster_octaves))
1023                    .collect()
1024            } else {
1025                Vec::new()
1026            };
1027            let field = self.perlin(nr.noise_persistence, nr.noise_octaves);
1028            let inv = ONE / nr.noise_scale;
1029            for y in 0..n {
1030                for x in 0..n {
1031                    let v = field.generate(x as f32 * inv, y as f32 * inv);
1032                    if nr.noise_min > v * HALF + HALF {
1033                        continue;
1034                    }
1035                    if self.rng.rand_float() > nr.chance {
1036                        continue;
1037                    }
1038                    let index = n * y + x;
1039                    if self.cells.objects[index as usize].is_some()
1040                        || (y - sy) * (y - sy) + (x - sx) * (x - sx) < radius_sq
1041                    {
1042                        continue;
1043                    }
1044                    let (cx, cy) = self.cell_position(index);
1045                    let (h, up) = self.ground(cx, cy);
1046                    if h < nr.height_min || (nr.height_max <= h && h != nr.height_max) {
1047                        continue;
1048                    }
1049                    if up < nr.normal_min {
1050                        continue;
1051                    }
1052                    if !self.density_ok(template, x, y) {
1053                        continue;
1054                    }
1055                    let (mut variant, mut mesh, max_growth) = self.construct(template);
1056                    let (position, yaw) = self.position(template, index);
1057                    let (mut growth, mut scale, mut growth_state) = (1.0, [ONE; 4], None);
1058                    if self.templates[template].growth.is_some() {
1059                        let fraction = self.rng.rand_float();
1060                        let (state, value, output) =
1061                            self.set_growth(template, max_growth, fraction);
1062                        growth = value;
1063                        scale = output;
1064                        growth_state = Some(state);
1065                    }
1066                    let id = self.register(Placed {
1067                        template,
1068                        bucket: self.templates[template].bucket,
1069                        cell: (x, y),
1070                        footprint: (1, 1),
1071                        position,
1072                        yaw,
1073                        variant,
1074                        mesh,
1075                        growth,
1076                        growth_state,
1077                        spawn_state: self.spawn_state(template),
1078                        alive: true,
1079                        removal_requested: false,
1080                        animation: None,
1081                        scale,
1082                    });
1083                    if !self.templates[template].spawn.is_empty() {
1084                        self.spawn(id);
1085                    }
1086                    if nr.cluster {
1087                        let inv = ONE / nr.cluster_scale;
1088                        variant = 0;
1089                        for (k, c) in clusters.iter().enumerate() {
1090                            let v = c.generate(x as f32 * inv, y as f32 * inv);
1091                            if nr.cluster_threshold <= v * HALF + HALF {
1092                                variant = k;
1093                                break;
1094                            }
1095                        }
1096                        if self.rng.rand_below(nr.random_variant_one_in as u32) == 0 {
1097                            variant = self.rng.rand_below(variants as u32) as usize;
1098                        }
1099                        let meshes = self.templates[template].model.as_ref().unwrap().variants
1100                            [variant]
1101                            .meshes
1102                            .len();
1103                        mesh = self.rng.rand_below(meshes as u32) as usize;
1104                        self.placed[id].variant = variant;
1105                        self.placed[id].mesh = mesh;
1106                    }
1107                }
1108            }
1109        }
1110    }
1111}
1112
1113#[cfg(test)]
1114mod tests;