1use crate::work::stock::{stock_table, RawMaterial, Storage, STOCK_SLOTS};
5
6const WARNING_COOLDOWN: i64 = 120_000_000;
8
9pub mod kind {
11 pub const STARVED: i32 = 0;
13 pub const FROZE: i32 = 1;
15 pub const OLD_AGE: i32 = 2;
17 pub const BORN: i32 = 8;
19 pub const ADULT: i32 = 9;
21 pub const STUDENT: i32 = 10;
23 pub const LOW_LOGS: i32 = 17;
25 pub const NEAR_CAPACITY_GOODS: i32 = 23;
27 pub const NEAR_CAPACITY_RAW: i32 = 24;
29 pub const TORNADO: i32 = 30;
31 pub const FIRE: i32 = 31;
33}
34
35#[derive(Debug, Clone, PartialEq)]
39pub struct EventEntry {
40 pub kind: i32,
42 pub severity: i32,
44 pub color: u32,
48 pub month: usize,
50 pub year: i32,
52 pub args: [String; 3],
54 pub cell: i32,
58 pub category: i32,
62 pub icon: i32,
64 pub text: Option<String>,
69}
70
71#[derive(Debug, Clone)]
73pub struct EventLog {
74 pub entries: Vec<EventEntry>,
76 pub notify: [bool; 6],
80 pub slow_request: bool,
82}
83
84impl Default for EventLog {
85 fn default() -> Self {
86 EventLog {
87 entries: Vec::new(),
88 notify: [false, true, true, true, true, true],
89 slow_request: false,
90 }
91 }
92}
93
94pub fn severity_color(severity: i32) -> u32 {
98 match severity {
99 1 => 0xffff_ff00,
100 2 => 0xff80_80ff,
101 3 => 0xffff_6060,
102 4 => 0xffea_7517,
103 _ => 0xffff_ffff,
104 }
105}
106
107pub fn category(kind: i32) -> i32 {
111 match kind {
112 0..=7 => 0,
113 0xb => 1,
114 0xc => 2,
115 0xd => 5,
116 0xe => 8,
117 0x11..=0x16 => 6,
118 0x17 | 0x18 => 7,
119 0x19 => 9,
120 0x1a | 0x1c => 10,
121 0x1b | 0x1d => 11,
122 0x1e => 4,
123 0x1f => 3,
124 _ => -1,
125 }
126}
127
128impl EventLog {
129 #[allow(clippy::too_many_arguments)]
133 pub fn add(
134 &mut self,
135 kind: i32,
136 severity: i32,
137 cell: i32,
138 month: usize,
139 year: i32,
140 args: [String; 3],
141 auto_slow: bool,
142 ) {
143 let icon = match kind {
144 0x1a | 0x1c => 12,
145 0x1b | 0x1d => 13,
146 _ => -1,
147 };
148 self.entries.push(EventEntry {
149 text: None,
150 kind,
151 severity,
152 color: severity_color(severity),
153 month,
154 year,
155 args,
156 cell,
157 category: category(kind),
158 icon,
159 });
160 if kind > 10 && (kind < 0xd || (0x1e..=0x1f).contains(&kind)) && auto_slow {
161 self.slow_request = true;
162 }
163 }
164}
165
166#[derive(Debug, Clone)]
169pub struct StockWarnings {
170 pub count: [i32; STOCK_SLOTS],
175 pub previous: [i32; STOCK_SLOTS],
177 pub limits: [i32; STOCK_SLOTS],
180 pub cooldown: [i64; STOCK_SLOTS],
183 pub capacity: [(i32, i32, i64); 2],
186}
187
188impl Default for StockWarnings {
189 fn default() -> Self {
190 let mut limits = [1000; STOCK_SLOTS];
191 limits[..12].copy_from_slice(&[200, 200, 200, 200, 50, 5000, 100, 50, 500, 200, 200, 1000]);
192 StockWarnings {
193 count: [0; STOCK_SLOTS],
194 previous: [0; STOCK_SLOTS],
195 limits,
196 cooldown: [0; STOCK_SLOTS],
197 capacity: [(0, 0, 0); 2],
198 }
199 }
200}
201
202const RAW_FLAGS: u32 = 0x1c40;
204
205impl StockWarnings {
206 pub fn recompute(&mut self, storages: &[Storage], materials: &[RawMaterial]) {
210 self.previous = self.count;
211 self.count = stock_table(storages, materials);
212 let mut used = [0i32; 2];
213 let mut cap = [0i32; 2];
214 for s in storages {
215 let (mut goods, mut raw) = (false, false);
216 for item in &s.items {
217 let m = &materials[item.material];
218 let size = m.size * item.count;
219 if m.flags & RAW_FLAGS == 0 {
220 used[0] += size;
221 goods = true;
222 } else {
223 used[1] += size;
224 raw = true;
225 }
226 }
227 if raw {
228 cap[1] += s.capacity;
229 }
230 if goods {
231 cap[0] += s.capacity;
232 }
233 }
234 for i in 0..2 {
235 self.capacity[i].0 = used[i];
236 self.capacity[i].1 = cap[i];
237 }
238 }
239
240 pub fn check(&mut self, dt: i64) -> Vec<i32> {
246 let mut out = Vec::new();
247 for i in 0..STOCK_SLOTS {
248 self.cooldown[i] -= dt;
249 if self.cooldown[i] >= 1 {
250 continue;
251 }
252 self.cooldown[i] = 0;
253 if i >= 6 {
254 continue;
255 }
256 let (mut count, mut previous, mut limit) =
257 (self.count[i], self.previous[i], self.limits[i]);
258 if i == 3 {
260 count += self.count[9];
261 previous += self.previous[9];
262 limit += self.limits[9];
263 }
264 if count < limit / 20 && limit / 20 <= previous {
265 out.push(kind::LOW_LOGS + i as i32);
266 self.cooldown[i] = WARNING_COOLDOWN;
267 }
268 }
269 for (i, (used, cap, cooldown)) in self.capacity.iter_mut().enumerate() {
270 *cooldown -= dt;
271 if *cooldown >= 1 {
272 continue;
273 }
274 *cooldown = 0;
275 if (*cap / 20) * 19 < *used {
276 out.push(kind::NEAR_CAPACITY_GOODS + i as i32);
277 *cooldown = WARNING_COOLDOWN;
278 }
279 }
280 out
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn low_reserve_fires_on_crossing_only() {
290 let mut w = StockWarnings::default();
291 w.previous[0] = 10;
292 w.count[0] = 9;
293 assert_eq!(w.check(0), vec![kind::LOW_LOGS]);
294 assert!(w.check(1_000_000).is_empty());
296 w.cooldown[0] = 0;
298 w.previous[0] = 5;
299 w.count[0] = 4;
300 assert!(w.check(0).is_empty());
301 }
302
303 #[test]
304 fn colours_and_categories() {
305 assert_eq!(severity_color(3), 0xffff6060);
306 assert_eq!(category(17), 6);
307 assert_eq!(category(23), 7);
308 }
309}