banished_game/nature/
growth.rs1use crate::random::Random;
7use crate::scheduler::{seconds, ticks};
8use banished_formats::game::GrowthDescription;
9
10const MONTH_SECONDS: f32 = 300.0;
12const FREEZING: f32 = 32.0;
13const FROZEN_WEIGHT: f32 = -10.0;
14const EPSILON: f32 = 0.001;
16const OLD_WEIGHT: f32 = 0.99;
17const NEW_WEIGHT: f32 = 0.009_999_99;
18
19#[derive(Debug, Clone, PartialEq)]
21pub struct Growth {
22 pub maximum: f32,
25 pub timer: i64,
28 pub smoothed: f32,
31 pub scheduled: bool,
33 pub smoothing: bool,
35 pub allow_negative: bool,
37}
38
39#[derive(Debug, Default)]
41pub struct GrowthTick {
42 pub spawn_attempts: usize,
44 pub map_adjustment: Option<f32>,
48}
49
50impl Growth {
51 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 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 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 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 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
168pub 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
183pub fn adjust_map_byte(previous: u8, adjustment: f32) -> u8 {
187 let fraction = previous as f32 / 255.0; 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}