banished_formats/game/goods.rs
1//! Goods and where they go: raw materials, storage, residences, markets, trade and values.
2
3use super::*;
4
5/// `RawMaterialDescription`: a storable resource.
6///
7/// Original: `FUN_14012e9f0`.
8#[derive(Debug, Clone)]
9pub struct RawMaterialDescription {
10 /// The icon's sprite sheet.
11 pub sprite_sheet: Option<String>,
12 /// The string table.
13 pub string_table: Option<String>,
14 // +0x30, +0x38
15 /// The name's key (`Log`).
16 pub name: String,
17 /// The short name's key (`LogSmall`).
18 pub name_small: String,
19 // +0x40; +0x44
20 /// Not identified yet.
21 pub value_40: i32,
22 /// Not identified yet.
23 pub value_44: i32,
24 // +0x48
25 /// Storage units per item.
26 ///
27 /// Original: `FUN_140132250`.
28 pub size: i32,
29 // +0x4c
30 /// Not identified yet.
31 pub value_4c: i32,
32 // +0x5c
33 /// The category flags (food, fuel, clothing and so on), which the game maps to its stock
34 /// table.
35 ///
36 /// Original: mapped by `FUN_14008f3a0`.
37 pub flags: u32,
38 // +0x50
39 /// Not identified yet.
40 pub value_50: f32,
41 // +0x54
42 /// Not identified yet.
43 pub value_54: i32,
44 // +0x58
45 /// Not identified yet.
46 pub flag_58: bool,
47}
48
49/// Read a [`RawMaterialDescription`] from the bytes of its resource.
50pub fn raw_material_description(data: &[u8]) -> Result<RawMaterialDescription> {
51 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
52 if h.type_id != type_id("RawMaterialDescription") {
53 return format_err(format!(
54 "expected RawMaterialDescription, found {:#010x}",
55 h.type_id
56 ));
57 }
58 r.block(|r, _| {
59 Ok(RawMaterialDescription {
60 sprite_sheet: r.external_ref()?,
61 string_table: r.external_ref()?,
62 name: r.string()?,
63 name_small: r.string()?,
64 value_40: r.i32()?,
65 value_44: r.i32()?,
66 size: r.i32()?,
67 value_4c: r.i32()?,
68 flags: r.u32()?,
69 value_50: r.f32()?,
70 value_54: r.i32()?,
71 flag_58: r.bool()?,
72 })
73 })
74 })?;
75 Ok(value)
76}
77
78/// `StorageDescription`.
79///
80/// Original: `FUN_140131e70`.
81#[derive(Debug, Clone)]
82pub struct StorageDescription {
83 // +0x28
84 /// Raw-material flags this storage accepts.
85 pub accepts: u32,
86 /// Whether the capacity is per footprint cell.
87 pub flag_20: bool,
88 /// Not identified yet.
89 pub flag_21: bool,
90 // +0x24
91 /// Capacity in storage units.
92 pub capacity: i32,
93 // +0x30
94 /// `(count, raw material)` entries.
95 pub items: Vec<(i32, Option<String>)>,
96}
97
98/// Read a [`StorageDescription`] from the bytes of its resource.
99pub fn storage_description(data: &[u8]) -> Result<StorageDescription> {
100 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
101 if h.type_id != type_id("StorageDescription") {
102 return format_err(format!(
103 "expected StorageDescription, found {:#010x}",
104 h.type_id
105 ));
106 }
107 r.block(|r, _| {
108 let accepts = r.u32()?;
109 let flag_20 = r.bool()?;
110 let flag_21 = r.bool()?;
111 let capacity = r.i32()?;
112 let items = (0..r.count()?)
113 .map(|_| Ok((r.i32()?, r.external_ref()?)))
114 .collect::<Result<_>>()?;
115 Ok(StorageDescription {
116 accepts,
117 flag_20,
118 flag_21,
119 capacity,
120 items,
121 })
122 })
123 })?;
124 Ok(value)
125}
126
127// +0x20
128/// The mask of a `StorageLocationDescription`: the kinds of goods a storage place accepts.
129///
130/// Original: `FUN_140132940` (version 2).
131pub fn storage_location_description(data: &[u8]) -> Result<u32> {
132 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
133 if h.type_id != type_id("StorageLocationDescription") {
134 return format_err("expected StorageLocationDescription");
135 }
136 r.block(|r, _| r.u32())
137 })?;
138 Ok(value)
139}
140
141// +0x20
142/// `AutoPickupDescription`:, whether the workplace's workers pick up the piles they make first.
143///
144/// Original: `FUN_1400454c0`.
145pub fn auto_pickup_description(data: &[u8]) -> Result<bool> {
146 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
147 if h.type_id != type_id("AutoPickupDescription") {
148 return format_err(format!(
149 "expected AutoPickupDescription, found {:#010x}",
150 h.type_id
151 ));
152 }
153 r.block(|r, _| r.bool())
154 })?;
155 Ok(value)
156}
157
158/// `ResidenceDescription` (version 2).
159///
160/// Original: `FUN_14012f8a0`.
161#[derive(Debug, Clone, Copy, PartialEq)]
162pub struct ResidenceDescription {
163 // +0x20; +0x50; +0x58
164 /// Family slots (ResidenceComponent, count).
165 pub families: i32,
166 // +0x24
167 /// Residents per family before it counts as full.
168 ///
169 /// Original: `FUN_14012f9c0`.
170 pub family_size: i32,
171 // +0x28
172 /// At or above this temperature the home is warm without fuel.
173 ///
174 /// Original: `FUN_140130100`.
175 pub warm_temperature: f32,
176 // +0x2c
177 /// Seconds one unit of firewood keeps the home warm.
178 ///
179 /// Original: `FUN_14012ee40`.
180 pub fuel_seconds: f32,
181}
182
183/// Read a [`ResidenceDescription`] from the bytes of its resource.
184pub fn residence_description(data: &[u8]) -> Result<ResidenceDescription> {
185 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
186 if h.type_id != type_id("ResidenceDescription") {
187 return format_err(format!(
188 "expected ResidenceDescription, found {:#010x}",
189 h.type_id
190 ));
191 }
192 r.block(|r, _| {
193 Ok(ResidenceDescription {
194 families: r.i32()?,
195 family_size: r.i32()?,
196 warm_temperature: r.f32()?,
197 fuel_seconds: r.f32()?,
198 })
199 })
200 })?;
201 Ok(value)
202}
203
204// +0x20; +0x28
205/// `MarketDescription`: vendors stop while the market is fuller than `threshold`; the categories (raw-material
206/// flags) and each one's share of the market's room.
207///
208/// Original: `FUN_14004f510`.
209#[derive(Debug, Clone, Default, PartialEq)]
210pub struct MarketDescription {
211 /// How full the market may be before vendors stop.
212 pub threshold: f32,
213 /// Each category's raw-material flags and share of the market's room.
214 pub quotas: Vec<(u32, f32)>,
215}
216
217/// Read a [`MarketDescription`] from the bytes of its resource.
218pub fn market_description(data: &[u8]) -> Result<MarketDescription> {
219 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
220 if h.type_id != type_id("MarketDescription") {
221 return format_err(format!(
222 "expected MarketDescription, found {:#010x}",
223 h.type_id
224 ));
225 }
226 r.block(|r, _| {
227 let threshold = r.f32()?;
228 let n = r.i32()?;
229 let quotas = (0..n.max(0))
230 .map(|_| Ok((r.u32()?, r.f32()?)))
231 .collect::<Result<_>>()?;
232 Ok(MarketDescription { threshold, quotas })
233 })
234 })?;
235 Ok(value)
236}
237
238// +0x20; +0x30; +0x40; +0x10; +0x14; +0x18
239/// A TradeDescription merchant (0x48 bytes): its name (string table, key), the templates it always
240/// offers (seeds, livestock), integers the game does not read, its lots before the town's divisor, the raw-material
241/// flags it sells, and more flag masks.
242///
243/// Original: `FUN_14005d510`.
244#[derive(Debug, Clone, Default, PartialEq, Eq)]
245pub struct Merchant {
246 /// The name: string table and key.
247 pub name: (Option<String>, String),
248 /// The templates it always offers (seeds, livestock).
249 pub items: Vec<Option<String>>,
250 /// Integers the game does not read.
251 pub values: Vec<i32>,
252 /// Its lots, before the town's divisor.
253 pub lots: i32,
254 /// The raw-material flags it sells.
255 pub sells: u32,
256 /// More flag masks; not identified yet.
257 pub flags: (u32, u32),
258}
259
260// +0; +0xc; +0x10
261/// A TradeDescription row by population (0x14 bytes): towns below `population` people see a merchant
262/// every `(months + RandFloatTol × range)` × 300 s, and divide the merchant's lots and each lot's amount.
263///
264/// Original: `FUN_14005d480`.
265#[derive(Debug, Clone, Copy, Default, PartialEq)]
266pub struct TradeRow {
267 /// The population this row applies below.
268 pub population: i32,
269 /// The base time between merchants, in units of 300 seconds.
270 pub months: f32,
271 /// The random part of that time.
272 pub range: f32,
273 /// What the merchant's lots are divided by.
274 pub lot_divisor: i32,
275 /// What each lot's amount is divided by.
276 pub amount_divisor: i32,
277}
278
279/// A `TradeDescription`: the trading post's boat, merchants and trade window.
280///
281/// Original: `FUN_14005d150`, `FUN_14005d400`.
282#[derive(Debug, Clone, Default, PartialEq)]
283pub struct TradeDescription {
284 // +0x30
285 /// The trader boat template.
286 pub boat: Option<String>,
287 // +0x48
288 /// The merchants that can come.
289 pub merchants: Vec<Merchant>,
290 // +0x58
291 /// The rows of the trade table, by population.
292 pub rows: Vec<TradeRow>,
293 // +0x20
294 /// The trade window's sprite sheet.
295 pub sprites: Option<String>,
296 // +0x28
297 /// The trade window's string table.
298 pub strings: Option<String>,
299 // +0x38
300 /// The trade window's categories: flags and two string keys each.
301 pub categories: Vec<(u32, String, String)>,
302 // +0x68
303 /// Not identified yet (0.33 in the game data).
304 pub value_68: f32,
305 // +0x6c
306 /// Not identified yet (3 in the game data).
307 pub value_6c: i32,
308}
309
310/// Read a [`TradeDescription`] from the bytes of its resource.
311pub fn trade_description(data: &[u8]) -> Result<TradeDescription> {
312 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
313 if h.type_id != type_id("TradeDescription") {
314 return format_err(format!(
315 "expected TradeDescription, found {:#010x}",
316 h.type_id
317 ));
318 }
319 r.block(|r, _| {
320 let boat = r.external_ref()?;
321 let merchants = (0..r.count()?)
322 .map(|_| {
323 let name = (r.external_ref()?, r.string()?);
324 let items = (0..r.count()?)
325 .map(|_| r.external_ref())
326 .collect::<Result<_>>()?;
327 let values = (0..r.count()?).map(|_| r.i32()).collect::<Result<_>>()?;
328 let lots = r.i32()?;
329 let sells = r.u32()?;
330 let flags = (r.u32()?, r.u32()?);
331 Ok(Merchant {
332 name,
333 items,
334 values,
335 lots,
336 sells,
337 flags,
338 })
339 })
340 .collect::<Result<_>>()?;
341 let rows = (0..r.count()?)
342 .map(|_| {
343 Ok(TradeRow {
344 population: r.i32()?,
345 months: r.f32()?,
346 range: r.f32()?,
347 lot_divisor: r.i32()?,
348 amount_divisor: r.i32()?,
349 })
350 })
351 .collect::<Result<_>>()?;
352 let sprites = r.external_ref()?;
353 let strings = r.external_ref()?;
354 let categories = (0..r.count()?)
355 .map(|_| Ok((r.u32()?, r.string()?, r.string()?)))
356 .collect::<Result<_>>()?;
357 Ok(TradeDescription {
358 boat,
359 merchants,
360 rows,
361 sprites,
362 strings,
363 categories,
364 value_68: r.f32()?,
365 value_6c: r.i32()?,
366 })
367 })
368 })?;
369 Ok(value)
370}
371
372/// A `ValueDescription`: what one unit of a good is worth in trade, and how it is shown.
373///
374/// Original: `FUN_14005d6c0`, `FUN_14005d7e0`.
375#[derive(Debug, Clone, Default, PartialEq, Eq)]
376pub struct ValueDescription {
377 // +0x20/+0x28
378 /// The icon: sprite sheet and sprite name.
379 pub icon: (Option<String>, String),
380 // +0x30/+0x38
381 /// The name: string table and key.
382 pub name: (Option<String>, String),
383 // +0x40
384 /// The value of one unit.
385 pub value: i32,
386 // +0x44
387 /// A merchant's lot, before the town's divisor (100).
388 pub lot: i32,
389 // +0x48
390 /// The smallest lot.
391 pub min_lot: i32,
392}
393
394/// Read a [`ValueDescription`] from the bytes of its resource.
395pub fn value_description(data: &[u8]) -> Result<ValueDescription> {
396 let (_, value) = crs::parse(data, |r: &mut Reader, h| {
397 if h.type_id != type_id("ValueDescription") {
398 return format_err(format!(
399 "expected ValueDescription, found {:#010x}",
400 h.type_id
401 ));
402 }
403 r.block(|r, _| {
404 Ok(ValueDescription {
405 icon: (r.external_ref()?, r.string()?),
406 name: (r.external_ref()?, r.string()?),
407 value: r.i32()?,
408 lot: r.i32()?,
409 min_lot: r.i32()?,
410 })
411 })
412 })?;
413 Ok(value)
414}