Skip to main content

banished_formats/game/
particles.rs

1//! Particle systems, including the tornado's.
2
3use super::*;
4
5/// A particle system: an emitter of quads that face the camera (smoke, fire, dust, snow). Times are
6/// in microseconds. See docs/PARTICLES.md.
7///
8/// Original: `Graphics::ParticleSystem::Serialize`, Runtime `0x1800a5c70`, after the
9/// `Graphics::Particle` block.
10///
11/// Original: Runtime `0x180106790`.
12#[derive(Debug, Clone, PartialEq)]
13pub struct ParticleSystem {
14    // +0x20; +0x24
15    /// Particle: whether it is instanced.
16    pub instanced: bool,
17    /// Particle: the shared instance count.
18    pub instances: u32,
19    // +0x28
20    /// Particle: the most particles alive.
21    pub max_particles: u32,
22    // +0x30
23    /// Particle: the MaterialInstance.
24    pub material: Option<String>,
25    // +0x48; +0x4c × +0x50
26    /// Particles per second.
27    pub rate: f32,
28    /// The change of the rate per second squared, multiplied by `rate_scale`.
29    pub rate_change: f32,
30    /// The factor of `rate_change`.
31    pub rate_scale: f32,
32    // +0x58
33    /// How long it emits (0 for ever).
34    pub duration: i64,
35    // +0x60
36    /// The emission direction (model space).
37    pub direction: [f32; 3],
38    // +0x78, +0x6c
39    /// The spawn box's size: each axis spans `± axis × size / 2`.
40    pub spawn_size: f32,
41    /// The spawn box's axes.
42    pub spawn_box: [f32; 3],
43    // +0x7c, +0x80
44    /// The emission cone's angles (degrees).
45    pub cone: [f32; 2],
46    // +0x84, +0x88
47    /// Speed (base, ± range).
48    pub speed: [f32; 2],
49    // +0x8c; +0x90
50    /// How strongly the force acts.
51    pub force_scale: f32,
52    /// The share of the velocity kept at each update.
53    pub damping: f32,
54    // +0x94, +0x98
55    /// Rotation (degrees, base ± range).
56    pub rotation: [f32; 2],
57    // +0x9c, +0xa0
58    /// Spin (degrees/s, base ± range, either way).
59    pub spin: [f32; 2],
60    // +0xa4, +0xa8
61    /// The change of the spin.
62    pub spin_change: f32,
63    /// The share of the spin kept at each update.
64    pub spin_damping: f32,
65    // +0xac, +0xb0
66    /// Life (seconds, base ± range).
67    pub life: [f32; 2],
68    // +0xb4, +0xb8
69    /// The strength of the force.
70    pub force_amount: f32,
71    /// The force's direction (for example the wind).
72    pub force: [f32; 3],
73    // +0xc4, +0xc8; +0xcc; +0xd0
74    /// The size (base ± range).
75    pub size: [f32; 2],
76    /// How fast the size grows.
77    pub growth: f32,
78    /// The share of the growth kept at each update.
79    pub growth_damping: f32,
80    // +0xdc, +0xe0
81    /// Start and end colours (BGRA bytes).
82    pub colors: [u32; 2],
83    // +0xd4, +0xd8
84    /// The part of life the colour moves over.
85    pub color_span: [f32; 2],
86    // +0xe8, +0xf0
87    /// How long it fades in.
88    pub fade_in: i64,
89    /// How long it fades out.
90    pub fade_out: i64,
91}
92
93/// Read a [`ParticleSystem`] from the bytes of its resource.
94pub fn particle_system(data: &[u8]) -> Result<ParticleSystem> {
95    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
96        if h.type_id != type_id("ParticleSystem") {
97            return format_err(format!(
98                "expected ParticleSystem, found {:#010x}",
99                h.type_id
100            ));
101        }
102        let (instanced, instances, max_particles, material) =
103            r.block(|r, _| Ok((r.bool()?, r.u32()?, r.u32()?, r.external_ref()?)))?;
104        r.block(|r, _| {
105            let rate = r.f32()?;
106            let rate_change = r.f32()?;
107            let rate_scale = r.f32()?;
108            let duration = r.i64()?;
109            let direction = r.f32s::<3>()?;
110            let spawn_size = r.f32()?;
111            let spawn_box = r.f32s::<3>()?;
112            let f = r.f32s::<15>()?;
113            let force = r.f32s::<3>()?;
114            let g = r.f32s::<4>()?;
115            let colors = [r.u32()?, r.u32()?];
116            let color_span = r.f32s::<2>()?;
117            Ok(ParticleSystem {
118                instanced,
119                instances,
120                max_particles,
121                material: material.clone(),
122                rate,
123                rate_change,
124                rate_scale,
125                duration,
126                direction,
127                spawn_size,
128                spawn_box,
129                cone: [f[0], f[1]],
130                speed: [f[2], f[3]],
131                force_scale: f[4],
132                damping: f[5],
133                rotation: [f[6], f[7]],
134                spin: [f[8], f[9]],
135                spin_change: f[10],
136                spin_damping: f[11],
137                life: [f[12], f[13]],
138                force_amount: f[14],
139                force,
140                size: [g[0], g[1]],
141                growth: g[2],
142                growth_damping: g[3],
143                colors,
144                color_span,
145                fade_in: r.i64()?,
146                fade_out: r.i64()?,
147            })
148        })
149    })?;
150    Ok(value)
151}
152
153/// The Tornado particle class (`Graphics::Particle` subclass): particles spiralling up a funnel.
154/// Colours are BGRA bytes, times `System::Time` microseconds. See docs/PARTICLES.md.
155///
156/// Original: `FUN_1400d76e0`, `FUN_1400d7940`.
157#[derive(Debug, Clone, PartialEq)]
158pub struct TornadoParticle {
159    // +0x28; +0x30
160    /// The most particles alive at once.
161    pub max_particles: u32,
162    /// The material the particles are drawn with.
163    pub material: Option<String>,
164    // +0x48; +0x4c; +0x50
165    /// The scale of the spiral's radius.
166    pub radius: f32,
167    /// The rise over a particle's life.
168    pub rise: f32,
169    /// The wobble's radius.
170    pub wobble: f32,
171    // +0x54; +0x58
172    /// The wobble's turn, in degrees a second.
173    pub wobble_turn: f32,
174    /// The spiral's turn over a particle's life.
175    pub spiral_turn: f32,
176    // +0x5c; +0x60; +0x64
177    /// Particles made per second.
178    pub rate: f32,
179    /// A particle's life in seconds (base ± range).
180    pub life: [f32; 2],
181    // +0x68, +0x6c; +0x70
182    /// The smallest and largest size.
183    pub size: [f32; 2],
184    /// The size per unit of radius.
185    pub size_scale: f32,
186    // +0x74, +0x78
187    /// The start and end colours.
188    pub colors: [u32; 2],
189    // +0x80, +0x88
190    /// How long it fades in.
191    pub fade_in: i64,
192    /// How long it fades out.
193    pub fade_out: i64,
194}
195
196/// Read a [`TornadoParticle`] from the bytes of its resource.
197pub fn tornado_particle(data: &[u8]) -> Result<TornadoParticle> {
198    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
199        if h.type_id != type_id("Tornado") {
200            return format_err(format!("expected Tornado, found {:#010x}", h.type_id));
201        }
202        let (_, _, max_particles, material) =
203            r.block(|r, _| Ok((r.bool()?, r.u32()?, r.u32()?, r.external_ref()?)))?;
204        r.block(|r, _| {
205            let f = r.f32s::<9>()?;
206            let colors = [r.u32()?, r.u32()?];
207            let fade_in = r.i64()?;
208            let fade_out = r.i64()?;
209            let wobble_turn = r.f32()?;
210            let size_scale = r.f32()?;
211            Ok(TornadoParticle {
212                max_particles,
213                material: material.clone(),
214                radius: f[0],
215                rise: f[1],
216                wobble: f[2],
217                wobble_turn,
218                spiral_turn: f[3],
219                rate: f[4],
220                life: [f[5], f[6]],
221                size: [f[7], f[8]],
222                size_scale,
223                colors,
224                fade_in,
225                fade_out,
226            })
227        })
228    })?;
229    Ok(value)
230}
231
232/// One of a `ParticleDescription`'s systems (`0x28` bytes).
233#[derive(Debug, Clone, PartialEq)]
234pub struct ParticleSlot {
235    // +0x00
236    /// The ParticleSystem.
237    pub system: Option<String>,
238    // +0x08
239    /// The model node it sits on (empty: the model's root).
240    pub node: String,
241    // +0x10..+0x13; +0x11; +0x12
242    /// Flags: the system starts when the entity is activated, and follows entity events 2 (start)
243    /// and 3 (fade out).
244    pub flags: [bool; 4],
245    // +0x20; +0x18
246    /// How long the alpha fades in and out, in microseconds.
247    pub fade: i64,
248    /// How long the system runs before it is shown, in microseconds.
249    pub prewarm: i64,
250}
251
252// +0x20
253/// `ParticleDescription`: the entity's particle systems, then one more reference.
254///
255/// Original: `FUN_140053dc0`.
256#[derive(Debug, Clone, PartialEq)]
257pub struct ParticleDescription {
258    /// The particle systems.
259    pub slots: Vec<ParticleSlot>,
260    /// One more reference; not identified yet.
261    pub extra: Option<String>,
262}
263
264/// Read a [`ParticleDescription`] from the bytes of its resource.
265pub fn particle_description(data: &[u8]) -> Result<ParticleDescription> {
266    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
267        if h.type_id != type_id("ParticleDescription") {
268            return format_err(format!(
269                "expected ParticleDescription, found {:#010x}",
270                h.type_id
271            ));
272        }
273        r.block(|r, _| {
274            let slots = (0..r.count()?)
275                .map(|_| {
276                    Ok(ParticleSlot {
277                        system: r.external_ref()?,
278                        node: r.string()?,
279                        flags: [r.bool()?, r.bool()?, r.bool()?, r.bool()?],
280                        fade: r.i64()?,
281                        prewarm: r.i64()?,
282                    })
283                })
284                .collect::<Result<_>>()?;
285            Ok(ParticleDescription {
286                slots,
287                extra: r.external_ref()?,
288            })
289        })
290    })?;
291    Ok(value)
292}