1use rakata_core::ResRef;
4use rakata_formats::{
5 gff_schema::{AbsentDefault, FieldLife, FieldSchema, GffType},
6 GffStruct, GffValue,
7};
8
9use super::object_id_or_invalid;
10use crate::gff_helpers::{
11 get_bool, get_f32, get_resref, get_string, get_u32, get_u8, upsert_field,
12};
13use crate::shared::ObjectId;
14use crate::uts::UtsSound;
15
16#[derive(Debug, Clone, PartialEq, Default)]
18pub struct GitSound {
19 pub template_resref: ResRef,
21 pub generated_type: u32,
23 pub x_position: f32,
25 pub y_position: f32,
27 pub z_position: f32,
29 pub object_id: ObjectId,
31}
32
33impl GitSound {
34 pub(crate) fn from_gff_struct(s: &GffStruct) -> Self {
35 Self {
36 template_resref: get_resref(s, "TemplateResRef").unwrap_or_default(),
37 generated_type: get_u32(s, "GeneratedType").unwrap_or(0),
38 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
39 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
40 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
41 object_id: object_id_or_invalid(s),
42 }
43 }
44
45 pub(crate) fn to_gff_struct(&self) -> GffStruct {
46 let mut s = GffStruct::new(6);
47 upsert_field(
48 &mut s,
49 "TemplateResRef",
50 GffValue::ResRef(self.template_resref),
51 );
52 upsert_field(
53 &mut s,
54 "GeneratedType",
55 GffValue::UInt32(self.generated_type),
56 );
57 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
58 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
59 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
60 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id.get()));
61 s
62 }
63}
64
65pub(crate) static SOUND_LIST_CHILDREN: &[FieldSchema] = &[
67 FieldSchema {
68 label: "ObjectId",
69 expected_type: GffType::UInt32,
70 life: FieldLife::Live,
71 required: false,
72 absent: AbsentDefault::Unverified,
73 children: None,
74 constraint: None,
75 },
76 FieldSchema {
77 label: "TemplateResRef",
78 expected_type: GffType::ResRef,
79 life: FieldLife::Live,
80 required: false,
81 absent: AbsentDefault::Unverified,
82 children: None,
83 constraint: None,
84 },
85 FieldSchema {
86 label: "GeneratedType",
87 expected_type: GffType::UInt32,
88 life: FieldLife::Live,
89 required: false,
90 absent: AbsentDefault::Unverified,
91 children: None,
92 constraint: None,
93 },
94 FieldSchema {
95 label: "XPosition",
96 expected_type: GffType::Single,
97 life: FieldLife::Live,
98 required: false,
99 absent: AbsentDefault::Unverified,
100 children: None,
101 constraint: None,
102 },
103 FieldSchema {
104 label: "YPosition",
105 expected_type: GffType::Single,
106 life: FieldLife::Live,
107 required: false,
108 absent: AbsentDefault::Unverified,
109 children: None,
110 constraint: None,
111 },
112 FieldSchema {
113 label: "ZPosition",
114 expected_type: GffType::Single,
115 life: FieldLife::Live,
116 required: false,
117 absent: AbsentDefault::Unverified,
118 children: None,
119 constraint: None,
120 },
121];
122
123#[derive(Debug, Clone, PartialEq, Default)]
145pub struct SavedSound {
146 pub tag: String,
148 pub object_id: ObjectId,
150 pub commandable: bool,
152
153 pub active: bool,
155 pub looping: bool,
157 pub continuous: bool,
159 pub random: bool,
161 pub random_position: bool,
163 pub positional: bool,
165
166 pub sounds: Vec<UtsSound>,
169 pub times: u8,
171 pub hours: u32,
173 pub interval: u32,
175 pub interval_variation: u32,
177 pub generated_type: u32,
179
180 pub volume: u8,
182 pub volume_variation: u8,
184 pub pitch_variation: f32,
186 pub fixed_variance: f32,
188 pub min_distance: f32,
190 pub max_distance: f32,
192 pub random_range_x: f32,
194 pub random_range_y: f32,
196
197 pub x_position: f32,
199 pub y_position: f32,
201 pub z_position: f32,
203}
204
205impl SavedSound {
206 pub fn new() -> Self {
208 Self::default()
209 }
210
211 pub fn from_struct(structure: &GffStruct) -> Self {
213 Self {
214 tag: get_string(structure, "Tag").unwrap_or_default(),
215 object_id: object_id_or_invalid(structure),
216 commandable: get_bool(structure, "Commandable").unwrap_or(false),
217
218 active: get_bool(structure, "Active").unwrap_or(false),
219 looping: get_bool(structure, "Looping").unwrap_or(false),
220 continuous: get_bool(structure, "Continuous").unwrap_or(false),
221 random: get_bool(structure, "Random").unwrap_or(false),
222 random_position: get_bool(structure, "RandomPosition").unwrap_or(false),
223 positional: get_bool(structure, "Positional").unwrap_or(false),
224
225 sounds: match structure.field("Sounds") {
226 Some(GffValue::List(entries)) => {
227 entries.iter().map(UtsSound::from_struct).collect()
228 }
229 _ => Vec::new(),
230 },
231 times: get_u8(structure, "Times").unwrap_or(0),
232 hours: get_u32(structure, "Hours").unwrap_or(0),
233 interval: get_u32(structure, "Interval").unwrap_or(0),
234 interval_variation: get_u32(structure, "IntervalVrtn").unwrap_or(0),
235 generated_type: get_u32(structure, "GeneratedType").unwrap_or(0),
236
237 volume: get_u8(structure, "Volume").unwrap_or(0),
238 volume_variation: get_u8(structure, "VolumeVrtn").unwrap_or(0),
239 pitch_variation: get_f32(structure, "PitchVariation").unwrap_or(0.0),
240 fixed_variance: get_f32(structure, "FixedVariance").unwrap_or(0.0),
241 min_distance: get_f32(structure, "MinDistance").unwrap_or(0.0),
242 max_distance: get_f32(structure, "MaxDistance").unwrap_or(0.0),
243 random_range_x: get_f32(structure, "RandomRangeX").unwrap_or(0.0),
244 random_range_y: get_f32(structure, "RandomRangeY").unwrap_or(0.0),
245
246 x_position: get_f32(structure, "XPosition").unwrap_or(0.0),
247 y_position: get_f32(structure, "YPosition").unwrap_or(0.0),
248 z_position: get_f32(structure, "ZPosition").unwrap_or(0.0),
249 }
250 }
251
252 pub fn to_struct(&self, index: usize) -> GffStruct {
254 let mut s = GffStruct::new(i32::try_from(index).expect("sound index fits i32"));
255
256 upsert_field(&mut s, "Tag", GffValue::String(self.tag.clone()));
257 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id.get()));
258 upsert_field(
259 &mut s,
260 "Commandable",
261 GffValue::UInt8(self.commandable.into()),
262 );
263
264 upsert_field(&mut s, "Active", GffValue::UInt8(self.active.into()));
265 upsert_field(&mut s, "Looping", GffValue::UInt8(self.looping.into()));
266 upsert_field(
267 &mut s,
268 "Continuous",
269 GffValue::UInt8(self.continuous.into()),
270 );
271 upsert_field(&mut s, "Random", GffValue::UInt8(self.random.into()));
272 upsert_field(
273 &mut s,
274 "RandomPosition",
275 GffValue::UInt8(self.random_position.into()),
276 );
277 upsert_field(
278 &mut s,
279 "Positional",
280 GffValue::UInt8(self.positional.into()),
281 );
282
283 upsert_field(
284 &mut s,
285 "Sounds",
286 GffValue::List(
287 self.sounds
288 .iter()
289 .enumerate()
290 .map(|(index, sound)| sound.to_struct(index))
291 .collect(),
292 ),
293 );
294 upsert_field(&mut s, "Times", GffValue::UInt8(self.times));
295 upsert_field(&mut s, "Hours", GffValue::UInt32(self.hours));
296 upsert_field(&mut s, "Interval", GffValue::UInt32(self.interval));
297 upsert_field(
298 &mut s,
299 "IntervalVrtn",
300 GffValue::UInt32(self.interval_variation),
301 );
302 upsert_field(
303 &mut s,
304 "GeneratedType",
305 GffValue::UInt32(self.generated_type),
306 );
307
308 upsert_field(&mut s, "Volume", GffValue::UInt8(self.volume));
309 upsert_field(&mut s, "VolumeVrtn", GffValue::UInt8(self.volume_variation));
310 upsert_field(
311 &mut s,
312 "PitchVariation",
313 GffValue::Single(self.pitch_variation),
314 );
315 upsert_field(
316 &mut s,
317 "FixedVariance",
318 GffValue::Single(self.fixed_variance),
319 );
320 upsert_field(&mut s, "MinDistance", GffValue::Single(self.min_distance));
321 upsert_field(&mut s, "MaxDistance", GffValue::Single(self.max_distance));
322 upsert_field(
323 &mut s,
324 "RandomRangeX",
325 GffValue::Single(self.random_range_x),
326 );
327 upsert_field(
328 &mut s,
329 "RandomRangeY",
330 GffValue::Single(self.random_range_y),
331 );
332
333 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
334 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
335 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
336
337 s
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use rakata_core::ResRef;
345
346 fn sample() -> SavedSound {
347 SavedSound {
348 tag: "amb_wind".to_string(),
349 object_id: ObjectId::new(0x8000_0004),
350 active: true,
351 looping: true,
352 positional: true,
353 sounds: vec![
354 UtsSound {
355 sound: ResRef::new("as_wind_01").expect("valid resref"),
356 },
357 UtsSound {
358 sound: ResRef::new("as_wind_02").expect("valid resref"),
359 },
360 ],
361 interval: 4000,
362 volume: 90,
363 min_distance: 1.0,
364 max_distance: 30.0,
365 x_position: 8.0,
366 ..SavedSound::default()
367 }
368 }
369
370 #[test]
371 fn round_trips_through_a_list_element() {
372 let sound = sample();
373
374 let parsed = SavedSound::from_struct(&sound.to_struct(0));
375
376 assert_eq!(parsed, sound);
377 }
378
379 #[test]
380 fn carries_a_position_but_no_orientation() {
381 let written = sample().to_struct(0);
383
384 assert!(written.field("XPosition").is_some());
385 assert!(written.field("XOrientation").is_none());
386 assert!(written.field("YOrientation").is_none());
387 assert!(written.field("ZOrientation").is_none());
388 }
389}