Skip to main content

banished_formats/game/
entities.rs

1//! What every entity is made of: components, models, decals, characters and interaction points.
2
3use super::*;
4
5/// An entity template (`ComponentDescription`): the scheduling bucket and the ordered component
6/// descriptions (`model`, `naturalresource`, `growth`, …).
7///
8/// Original: `FUN_14011dfc0`.
9#[derive(Debug, Clone)]
10pub struct ComponentDescription {
11    // +0x20
12    /// Manager bucket (default 6 = unscheduled).
13    pub bucket: u32,
14    // +0x28
15    /// Description references.
16    pub descriptions: Vec<Option<String>>,
17}
18
19/// Read a [`ComponentDescription`] from the bytes of its resource.
20pub fn component_description(data: &[u8]) -> Result<ComponentDescription> {
21    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
22        if h.type_id != type_id("ComponentDescription") {
23            return format_err(format!(
24                "expected ComponentDescription, found {:#010x}",
25                h.type_id
26            ));
27        }
28        r.block(|r, _| {
29            let bucket = r.u32()?;
30            let descriptions = (0..r.count()?)
31                .map(|_| r.external_ref())
32                .collect::<Result<_>>()?;
33            Ok(ComponentDescription {
34                bucket,
35                descriptions,
36            })
37        })
38    })?;
39    Ok(value)
40}
41
42/// One model variant: an animation set and its alternative meshes.
43#[derive(Debug, Clone)]
44pub struct ModelVariant {
45    /// The animation set.
46    pub animations: Option<String>,
47    /// The alternative meshes.
48    pub meshes: Vec<Option<String>>,
49}
50
51/// A `ModelDescription`: the meshes and materials an entity is drawn with.
52///
53/// Original: `FUN_14012a290`; the model component's set-up `FUN_140129f90` reads it.
54#[derive(Debug, Clone)]
55pub struct ModelDescription {
56    // +0x20; +0x28
57    /// The variants, 0x18 bytes each.
58    pub variants: Vec<ModelVariant>,
59    // +0x30
60    /// Variant used unless `random_variant`.
61    pub variant: i32,
62    // +0x34
63    /// Mesh index within the variant (negative = no mesh).
64    pub mesh: i32,
65    // +0x38
66    /// Pick the variant with `Rand(variant count)`.
67    pub random_variant: bool,
68}
69
70/// Read a [`ModelDescription`] from the bytes of its resource.
71pub fn model_description(data: &[u8]) -> Result<ModelDescription> {
72    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
73        if h.type_id != type_id("ModelDescription") {
74            return format_err(format!(
75                "expected ModelDescription, found {:#010x}",
76                h.type_id
77            ));
78        }
79        r.block(|r, _| {
80            let mut variants = Vec::new();
81            for _ in 0..r.count()? {
82                let animations = r.external_ref()?;
83                let meshes = (0..r.count()?)
84                    .map(|_| r.external_ref())
85                    .collect::<Result<_>>()?;
86                variants.push(ModelVariant { animations, meshes });
87            }
88            Ok(ModelDescription {
89                variants,
90                variant: r.i32()?,
91                mesh: r.i32()?,
92                random_variant: r.bool()?,
93            })
94        })
95    })?;
96    Ok(value)
97}
98
99/// `DecalDescription`: the terrain decal a DecalComponent puts over its entity's map rectangle
100/// (`Template\X.rsc:decal`).
101///
102/// Original: `FUN_140120e50`.
103#[derive(Debug, Clone, PartialEq)]
104pub struct DecalDescription {
105    // +0x20
106    /// The alpha until the entity is built.
107    pub alpha: f32,
108    // +0x28
109    /// Materials; the component's picks one.
110    pub materials: Vec<Option<String>>,
111    // +0x48
112    /// The texture's size in rectangle units.
113    pub scale: f32,
114    // +0x38
115    /// A texture rectangle (`u0, v0, u1, v1`) per model variant.
116    pub rects: Vec<[f32; 4]>,
117    // +0x24
118    /// Tiled (the road atlas by the rectangle's edges) instead of one picture.
119    pub tiled: bool,
120    // +0x25
121    /// Not identified yet.
122    pub flag: bool,
123}
124
125/// Read a [`DecalDescription`] from the bytes of its resource.
126pub fn decal_description(data: &[u8]) -> Result<DecalDescription> {
127    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
128        if h.type_id != type_id("DecalDescription") {
129            return format_err(format!(
130                "expected DecalDescription, found {:#010x}",
131                h.type_id
132            ));
133        }
134        r.block(|r, _| {
135            let alpha = r.f32()?;
136            let materials = (0..r.count()?)
137                .map(|_| r.external_ref())
138                .collect::<Result<_>>()?;
139            let scale = r.f32()?;
140            let rects = (0..r.count()?)
141                .map(|_| r.f32s::<4>())
142                .collect::<Result<_>>()?;
143            Ok(DecalDescription {
144                alpha,
145                materials,
146                scale,
147                rects,
148                tiled: r.bool()?,
149                flag: r.bool()?,
150            })
151        })
152    })?;
153    Ok(value)
154}
155
156/// A `CharacterDescription`: how a walking entity moves (its speeds and turning).
157///
158/// Original: `FUN_14010f2a0`; the defaults come from `FUN_14010f3e0`, and the character update and
159/// `MoveTo` read it.
160///
161/// Original: `FUN_14010e990`, `FUN_1400cda40`.
162#[derive(Debug, Clone, Copy, PartialEq)]
163pub struct CharacterDescription {
164    // +0x20
165    /// (default 0.25).
166    pub radius: f32,
167    // +0x24
168    /// Added to the heading when the model is turned (default 0).
169    pub yaw_offset: f32,
170    // +0x28
171    /// Fraction of the remaining turn kept each update (default 0.85).
172    pub turn_keep: f32,
173    // +0x2c
174    /// Animation blend time passed to `PlayAnimation` (default 0.1).
175    pub blend: f32,
176    // +0x30
177    /// Speed scale while following another character (default 1).
178    pub follow_speed: f32,
179    // +0x34
180    /// Speed scale on cell types 1 and 9 (roads; default 1).
181    pub road_speed: f32,
182    // +0x38
183    /// Speed scale on cell type 0 (default 1).
184    pub type0_speed: f32,
185    // +0x3c / +0x40
186    /// Hold a fixed height instead of following the ground.
187    pub fixed_height: bool,
188    /// The fixed height.
189    pub height: f32,
190    // +0x44
191    /// Lowest height the ground clamp allows (default -10).
192    pub min_height: f32,
193}
194
195/// Read a [`CharacterDescription`] from the bytes of its resource.
196pub fn character_description(data: &[u8]) -> Result<CharacterDescription> {
197    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
198        if h.type_id != type_id("CharacterDescription") {
199            return format_err(format!(
200                "expected CharacterDescription, found {:#010x}",
201                h.type_id
202            ));
203        }
204        r.block(|r, _| {
205            Ok(CharacterDescription {
206                radius: r.f32()?,
207                yaw_offset: r.f32()?,
208                turn_keep: r.f32()?,
209                blend: r.f32()?,
210                follow_speed: r.f32()?,
211                road_speed: r.f32()?,
212                type0_speed: r.f32()?,
213                fixed_height: r.bool()?,
214                height: r.f32()?,
215                min_height: r.f32()?,
216            })
217        })
218    })?;
219    Ok(value)
220}
221
222/// A named point of a `Pathfinding::PointList` (position in model space, yaw in degrees).
223#[derive(Debug, Clone, PartialEq)]
224pub struct NamedPoint {
225    /// The name.
226    pub name: String,
227    /// The position, in model space.
228    pub position: [f32; 3],
229    /// The yaw, in degrees.
230    pub yaw: f32,
231}
232
233// +0x40; +0x30
234/// `Pathfinding::PointList`: a mesh name, a second string, path points and named points.
235///
236/// Original: `Pathfinding::PointList`, Runtime `0x180111f70`.
237#[derive(Debug, Clone)]
238pub struct PointList {
239    /// The mesh name.
240    pub mesh: String,
241    /// A second string; not identified yet.
242    pub source: String,
243    /// The path points.
244    pub path_points: Vec<NamedPoint>,
245    /// The named points.
246    pub points: Vec<NamedPoint>,
247}
248
249/// Read a [`PointList`] from the bytes of its resource.
250pub fn point_list(data: &[u8]) -> Result<PointList> {
251    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
252        if h.type_id != type_id("PointList") {
253            return format_err(format!("expected PointList, found {:#010x}", h.type_id));
254        }
255        r.block(|r, _| {
256            let mesh = r.string()?;
257            let source = r.string()?;
258            let list = |r: &mut Reader| -> Result<Vec<NamedPoint>> {
259                (0..r.count()?)
260                    .map(|_| {
261                        Ok(NamedPoint {
262                            name: r.string()?,
263                            position: r.f32s::<3>()?,
264                            yaw: r.f32()?,
265                        })
266                    })
267                    .collect()
268            };
269            let path_points = list(r)?;
270            let points = list(r)?;
271            Ok(PointList {
272                mesh,
273                source,
274                path_points,
275                points,
276            })
277        })
278    })?;
279    Ok(value)
280}
281
282// +0x28/+0x38/+0x48
283/// `InteractDescription`: the point list whose named points "use", "build" and "create" become the
284/// three interaction categories.
285///
286/// Original: `FUN_1401289d0`.
287#[derive(Debug, Clone)]
288pub struct InteractDescription {
289    /// The point list.
290    pub points: Option<String>,
291}
292
293/// Read a [`InteractDescription`] from the bytes of its resource.
294pub fn interact_description(data: &[u8]) -> Result<InteractDescription> {
295    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
296        if h.type_id != type_id("InteractDescription") {
297            return format_err(format!(
298                "expected InteractDescription, found {:#010x}",
299                h.type_id
300            ));
301        }
302        r.block(|r, _| {
303            Ok(InteractDescription {
304                points: r.external_ref()?,
305            })
306        })
307    })?;
308    Ok(value)
309}
310
311/// Interaction points of one category (0 use, 1 build, 2 create) in point-list order.
312pub fn interaction_points(list: &PointList, category: usize) -> Vec<&NamedPoint> {
313    let name = ["use", "build", "create"][category];
314    list.points.iter().filter(|p| p.name == name).collect()
315}
316
317impl Default for CharacterDescription {
318    fn default() -> Self {
319        CharacterDescription {
320            radius: 0.25,
321            yaw_offset: 0.0,
322            turn_keep: 0.85,
323            blend: 0.1,
324            follow_speed: 1.0,
325            road_speed: 1.0,
326            type0_speed: 1.0,
327            fixed_height: false,
328            height: 0.0,
329            min_height: -10.0,
330        }
331    }
332}