Skip to main content

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}