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}