Skip to main content

banished_game/nature/
growth.rs

1//! GrowthComponent's scheduled and visual phases. Description field names remain numeric where the
2//! original names are unknown.
3//!
4//! Original: `FUN_140125520`, `FUN_140125370`.
5
6use crate::random::Random;
7use crate::scheduler::{seconds, ticks};
8use banished_formats::game::GrowthDescription;
9
10/// DAT_140178934 / DAT_1401788d0 / DAT_14017898c.
11const MONTH_SECONDS: f32 = 300.0;
12const FREEZING: f32 = 32.0;
13const FROZEN_WEIGHT: f32 = -10.0;
14/// DAT_1401787b8 / DAT_140178844 / DAT_1401787c0.
15const EPSILON: f32 = 0.001;
16const OLD_WEIGHT: f32 = 0.99;
17const NEW_WEIGHT: f32 = 0.009_999_99;
18
19/// The growth state of a natural resource (a tree, a crop, a herb).
20#[derive(Debug, Clone, PartialEq)]
21pub struct Growth {
22    // GrowthComponent +0x4c; drawn by FUN_140125bc0.
23    /// The growth at which the resource is fully grown (a random value).
24    pub maximum: f32,
25    // +0x40
26    /// System::Time, not an accumulated f32.
27    pub timer: i64,
28    // +0x48; +0x28
29    /// Visible value, separate from NaturalResourceComponent.
30    pub smoothed: f32,
31    /// Whether the scheduled update still runs (false once fully grown).
32    pub scheduled: bool,
33    /// Whether the visible value still moves towards the growth.
34    pub smoothing: bool,
35    /// NaturalResourceComponent capability bit 0x100000; false on newly seeded resources.
36    pub allow_negative: bool,
37}
38
39/// What one growth update changed.
40#[derive(Debug, Default)]
41pub struct GrowthTick {
42    /// How many times it tried to spawn.
43    pub spawn_attempts: usize,
44    /// A value passed on when the growth ends; its meaning is not identified yet.
45    ///
46    /// Original: the argument of `FUN_140125940`.
47    pub map_adjustment: Option<f32>,
48}
49
50impl Growth {
51    /// Set the growth, the visible scale and the random timer, without another random draw for
52    /// the maximum (seeding has drawn it already).
53    ///
54    /// Original: `FUN_140126280`.
55    pub fn initialise(
56        d: &GrowthDescription,
57        maximum: f32,
58        fraction: f32,
59        rng: &mut Random,
60    ) -> (Self, f32, [f32; 4]) {
61        let value = fraction * maximum;
62        let smoothed = value.min(1.0);
63        let timer = ticks(rng.rand_float() * d.timer_months as f32 * MONTH_SECONDS);
64        (
65            Self {
66                maximum,
67                timer,
68                smoothed,
69                scheduled: true,
70                smoothing: false,
71                allow_negative: false,
72            },
73            value,
74            scale(smoothed, d.tall),
75        )
76    }
77
78    // +0x18; checked against the SSE disassembly.
79    /// One scheduled update of a growing resource. The season comes from the environment's
80    /// temperature, not from the renderer's autumn and leaf values.
81    ///
82    /// Original: `FUN_140125520`.
83    pub fn update(
84        &mut self,
85        d: &GrowthDescription,
86        value: &mut f32,
87        dt: i64,
88        temperature: f32,
89        disabled: bool,
90    ) -> GrowthTick {
91        if !self.scheduled {
92            return GrowthTick::default();
93        }
94        let rate = 1.0 / (d.f28 * MONTH_SECONDS);
95        let weight = if d.b2c {
96            let distance = (temperature - d.f30).abs() / d.f34;
97            if !(0.0..=1.0).contains(&distance) {
98                if temperature <= FREEZING {
99                    FROZEN_WEIGHT
100                } else {
101                    0.0
102                }
103            } else {
104                1.0 - distance * distance
105            }
106        } else {
107            1.0
108        };
109        let next = *value + seconds(dt) * rate * weight;
110        let mut result = GrowthTick::default();
111        if next < 0.0 && !self.allow_negative {
112            result.map_adjustment = Some(d.f40);
113        } else {
114            *value = next;
115            self.smoothing = true;
116            // Both comparisons in the original are strict. Preserve the float remainder's
117            // conversion back to System::Time only when an interval was actually consumed.
118            if d.b44 && next > d.threshold {
119                self.timer += dt;
120                let mut time = seconds(self.timer);
121                let interval = d.timer_months as f32 * MONTH_SECONDS;
122                // Deviation: malformed zero/negative intervals must not hang the browser.
123                if interval > 0.0 && time > interval {
124                    while time > interval {
125                        result.spawn_attempts += 1;
126                        time -= interval;
127                    }
128                    self.timer = ticks(time);
129                }
130            }
131            if self.maximum > 0.0 && next > self.maximum && !self.allow_negative && !disabled {
132                result.map_adjustment = Some(next.min(1.0) * d.f40);
133            }
134        }
135        if result.map_adjustment.is_some() {
136            *value = 0.0;
137            self.scheduled = false;
138            self.smoothing = false;
139        }
140        result
141    }
142
143    /// One smoothing step per active rendered update, including pause. Returns whether the instance
144    /// scale changed.
145    ///
146    /// Original: `FUN_140125370`.
147    pub fn smooth(&mut self, d: &GrowthDescription, value: f32, output: &mut [f32; 4]) -> bool {
148        if !self.smoothing {
149            return false;
150        }
151        let target = value.min(1.0).max(d.f60);
152        let interpolating = (target - self.smoothed).abs() >= EPSILON;
153        self.smoothed = if interpolating {
154            self.smoothed * OLD_WEIGHT + target * NEW_WEIGHT
155        } else {
156            target
157        };
158        let next = scale(self.smoothed, d.tall);
159        let changed = *output != next;
160        *output = next;
161        if !interpolating {
162            self.smoothing = false;
163        }
164        changed
165    }
166}
167
168// DAT_1401787f4: the width divisor.
169/// The visible scale of a growing resource for its growth value: `[x, y, z, w]`. A `tall`
170/// resource reaches its full width at growth 0.15 and then only grows in height; others scale
171/// evenly.
172///
173/// Original: `FUN_140126280`, `FUN_140125370`.
174pub fn scale(value: f32, tall: bool) -> [f32; 4] {
175    if tall {
176        let width = (value / 0.15).clamp(0.0, 1.0);
177        [width, width, value, 1.0]
178    } else {
179        [value; 4]
180    }
181}
182
183/// Retain the squared byte-fraction arithmetic and integer truncation.
184///
185/// Original: `FUN_140125940`.
186pub fn adjust_map_byte(previous: u8, adjustment: f32) -> u8 {
187    let fraction = previous as f32 / 255.0; // DAT_14017892c
188    let next = if adjustment >= 0.0 {
189        fraction * fraction * adjustment
190    } else {
191        fraction - adjustment
192    };
193    ((next * 255.0) as i32).clamp(0, 255) as u8
194}