1use std::io::Read;
16
17use anyhow::Result;
18
19use crate::resource::prp::PlasmaRead;
20
21use super::bit_vector::BitVector;
22use super::synched_object::SynchedObjectData;
23use super::uoid::{Uoid, read_key_uoid};
24
25#[derive(Debug, Clone, Default)]
27pub struct ObjInterfaceData {
28 pub self_key: Option<Uoid>,
30 pub synched: SynchedObjectData,
32 pub owner: Option<Uoid>,
34 pub props: BitVector,
36}
37
38impl ObjInterfaceData {
39 pub fn read(reader: &mut impl Read) -> Result<Self> {
42 let self_key = read_key_uoid(reader)?;
44 let synched = SynchedObjectData::read(reader)?;
45 let owner = read_key_uoid(reader)?;
46 let props = BitVector::read(reader)?;
47
48 Ok(Self {
49 self_key,
50 synched,
51 owner,
52 props,
53 })
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct CoordinateInterfaceData {
60 pub base: ObjInterfaceData,
61 pub local_to_parent: [f32; 16],
62 pub parent_to_local: [f32; 16],
63 pub local_to_world: [f32; 16],
64 pub world_to_local: [f32; 16],
65 pub children: Vec<Option<Uoid>>,
66}
67
68impl CoordinateInterfaceData {
69 pub fn read(reader: &mut impl Read) -> Result<Self> {
72 let base = ObjInterfaceData::read(reader)?;
73
74 let local_to_parent = read_matrix44(reader)?;
75 let parent_to_local = read_matrix44(reader)?;
76 let local_to_world = read_matrix44(reader)?;
77 let world_to_local = read_matrix44(reader)?;
78
79 let num_children = reader.read_u32()?;
80 let mut children = Vec::with_capacity(num_children as usize);
81 for _ in 0..num_children {
82 children.push(read_key_uoid(reader)?);
83 }
84
85 Ok(Self {
86 base,
87 local_to_parent,
88 parent_to_local,
89 local_to_world,
90 world_to_local,
91 children,
92 })
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct DrawInterfaceData {
99 pub base: ObjInterfaceData,
100 pub drawables: Vec<(u32, Option<Uoid>)>,
102 pub regions: Vec<Option<Uoid>>,
104}
105
106impl DrawInterfaceData {
107 pub fn read(reader: &mut impl Read) -> Result<Self> {
110 let base = ObjInterfaceData::read(reader)?;
111
112 let num_drawables = reader.read_u32()?;
113 let mut drawables = Vec::with_capacity(num_drawables as usize);
114 for _ in 0..num_drawables {
115 let index = reader.read_u32()?;
116 let key = read_key_uoid(reader)?;
117 drawables.push((index, key));
118 }
119
120 let num_regions = reader.read_u32()?;
121 let mut regions = Vec::with_capacity(num_regions as usize);
122 for _ in 0..num_regions {
123 regions.push(read_key_uoid(reader)?);
124 }
125
126 Ok(Self {
127 base,
128 drawables,
129 regions,
130 })
131 }
132}
133
134#[derive(Debug, Clone, Default)]
136pub struct SimulationInterfaceData {
137 pub base: ObjInterfaceData,
138 pub physical_key: Option<Uoid>,
139}
140
141impl SimulationInterfaceData {
142 pub fn read(reader: &mut impl Read) -> Result<Self> {
143 let base = ObjInterfaceData::read(reader)?;
144 let physical_key = read_key_uoid(reader)?;
146 Ok(Self {
147 base,
148 physical_key,
149 })
150 }
151}
152
153#[derive(Debug, Clone, Default)]
155pub struct AudioInterfaceData {
156 pub base: ObjInterfaceData,
157 pub audible_key: Option<Uoid>,
158}
159
160impl AudioInterfaceData {
161 pub fn read(reader: &mut impl Read) -> Result<Self> {
162 let base = ObjInterfaceData::read(reader)?;
163 let audible_key = read_key_uoid(reader)?;
165 Ok(Self {
166 base,
167 audible_key,
168 })
169 }
170}
171
172#[derive(Debug, Clone)]
174pub struct SceneObjectData {
175 pub self_key: Option<Uoid>,
177 pub synched: SynchedObjectData,
179 pub draw_interface: Option<Uoid>,
181 pub sim_interface: Option<Uoid>,
183 pub coord_interface: Option<Uoid>,
185 pub audio_interface: Option<Uoid>,
187 pub generics: Vec<Option<Uoid>>,
189 pub modifiers: Vec<Option<Uoid>>,
191 pub scene_node: Option<Uoid>,
193}
194
195impl SceneObjectData {
196 pub fn read(reader: &mut impl Read) -> Result<Self> {
203 let self_key = read_key_uoid(reader)?;
206
207 let synched = SynchedObjectData::read(reader)?;
209
210 let draw_interface = read_key_uoid(reader)?;
212 let sim_interface = read_key_uoid(reader)?;
213 let coord_interface = read_key_uoid(reader)?;
214 let audio_interface = read_key_uoid(reader)?;
215
216 let num_generics = reader.read_u32()?;
218 let mut generics = Vec::with_capacity(num_generics as usize);
219 for _ in 0..num_generics {
220 generics.push(read_key_uoid(reader)?);
221 }
222
223 let num_modifiers = reader.read_u32()?;
225 let mut modifiers = Vec::with_capacity(num_modifiers as usize);
226 for _ in 0..num_modifiers {
227 modifiers.push(read_key_uoid(reader)?);
228 }
229
230 let scene_node = read_key_uoid(reader)?;
232
233 Ok(Self {
234 self_key,
235 synched,
236 draw_interface,
237 sim_interface,
238 coord_interface,
239 audio_interface,
240 generics,
241 modifiers,
242 scene_node,
243 })
244 }
245}
246
247fn read_matrix44(reader: &mut impl Read) -> Result<[f32; 16]> {
250 let flag = reader.read_u8()?;
251 if flag == 0 {
252 return Ok([
253 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
254 ]);
255 }
256 let mut m = [0f32; 16];
257 for val in &mut m {
258 *val = reader.read_f32()?;
259 }
260 Ok(m)
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use std::io::Cursor;
267
268 #[test]
270 fn test_parse_cleft_scene_objects() {
271 use crate::resource::prp::{PrpPage, class_types};
272 use std::path::Path;
273
274 let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Cleft.prp");
275 if !path.exists() {
276 eprintln!("Skipping test: {:?} not found", path);
277 return;
278 }
279
280 let page = PrpPage::from_file(path).unwrap();
281 let scene_keys: Vec<_> = page.keys_of_type(class_types::PL_SCENE_OBJECT);
282
283 let mut parsed = 0;
284 let mut with_draw = 0;
285 let mut with_coord = 0;
286 let mut with_sim = 0;
287 let mut with_audio = 0;
288 let mut total_modifiers = 0;
289
290 for key in &scene_keys {
291 if let Some(data) = page.object_data(key) {
292 let mut cursor = Cursor::new(data);
293
294 let _ = cursor.read_i16().unwrap();
296
297 match SceneObjectData::read(&mut cursor) {
298 Ok(so) => {
299 parsed += 1;
300 if so.draw_interface.is_some() {
301 with_draw += 1;
302 }
303 if so.coord_interface.is_some() {
304 with_coord += 1;
305 }
306 if so.sim_interface.is_some() {
307 with_sim += 1;
308 }
309 if so.audio_interface.is_some() {
310 with_audio += 1;
311 }
312 total_modifiers += so.modifiers.len();
313
314 if let Some(uoid) = &so.self_key {
316 assert_eq!(
317 uoid.object_name, key.object_name,
318 "Self-key name mismatch for {}",
319 key.object_name
320 );
321 }
322 }
323 Err(e) => {
324 panic!(
325 "Failed to parse SceneObject '{}': {}",
326 key.object_name, e
327 );
328 }
329 }
330 }
331 }
332
333 eprintln!(
334 "Parsed {} plSceneObjects: {} with draw, {} with coord, {} with sim, {} with audio, {} total modifiers",
335 parsed, with_draw, with_coord, with_sim, with_audio, total_modifiers
336 );
337 assert!(parsed > 0, "Should have parsed at least some scene objects");
338 assert!(with_draw > 0, "Some objects should have draw interfaces");
339 assert!(with_coord > 0, "Some objects should have coordinate interfaces");
340 }
341
342 #[test]
344 fn test_parse_cleft_coord_interfaces() {
345 use crate::core::class_index::ClassIndex;
346 use crate::resource::prp::PrpPage;
347 use std::path::Path;
348
349 let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Cleft.prp");
350 if !path.exists() {
351 eprintln!("Skipping test: {:?} not found", path);
352 return;
353 }
354
355 let page = PrpPage::from_file(path).unwrap();
356 let coord_keys: Vec<_> = page.keys_of_type(ClassIndex::PL_COORDINATE_INTERFACE);
357
358 let mut parsed = 0;
359 for key in &coord_keys {
360 if let Some(data) = page.object_data(key) {
361 let mut cursor = Cursor::new(data);
362 let _ = cursor.read_i16().unwrap(); match CoordinateInterfaceData::read(&mut cursor) {
365 Ok(ci) => {
366 parsed += 1;
367 let l2w = ci.local_to_world;
370 let has_some_nonzero = l2w.iter().any(|&v| v != 0.0);
371 assert!(
372 has_some_nonzero,
373 "L2W matrix for {} is all zeros",
374 key.object_name
375 );
376 }
377 Err(e) => {
378 panic!(
379 "Failed to parse CoordinateInterface '{}': {}",
380 key.object_name, e
381 );
382 }
383 }
384 }
385 }
386
387 eprintln!(
388 "Parsed {} plCoordinateInterfaces from Cleft",
389 parsed
390 );
391 assert!(parsed > 0, "Should have parsed coordinate interfaces");
392 }
393
394 #[test]
396 fn test_parse_cleft_draw_interfaces() {
397 use crate::core::class_index::ClassIndex;
398 use crate::resource::prp::PrpPage;
399 use std::path::Path;
400
401 let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Cleft.prp");
402 if !path.exists() {
403 eprintln!("Skipping test: {:?} not found", path);
404 return;
405 }
406
407 let page = PrpPage::from_file(path).unwrap();
408 let draw_keys: Vec<_> = page.keys_of_type(ClassIndex::PL_DRAW_INTERFACE);
409
410 let mut parsed = 0;
411 let mut total_drawables = 0;
412 for key in &draw_keys {
413 if let Some(data) = page.object_data(key) {
414 let mut cursor = Cursor::new(data);
415 let _ = cursor.read_i16().unwrap(); match DrawInterfaceData::read(&mut cursor) {
418 Ok(di) => {
419 parsed += 1;
420 total_drawables += di.drawables.len();
421
422 for (idx, key_ref) in &di.drawables {
424 if let Some(uoid) = key_ref {
425 assert_eq!(
426 uoid.class_type,
427 ClassIndex::PL_DRAWABLE_SPANS,
428 "DrawInterface should reference plDrawableSpans, got 0x{:04X} for {}",
429 uoid.class_type,
430 key.object_name
431 );
432 }
433 }
434 }
435 Err(e) => {
436 panic!(
437 "Failed to parse DrawInterface '{}': {}",
438 key.object_name, e
439 );
440 }
441 }
442 }
443 }
444
445 eprintln!(
446 "Parsed {} plDrawInterfaces ({} total drawable refs) from Cleft",
447 parsed, total_drawables
448 );
449 assert!(parsed > 0, "Should have parsed draw interfaces");
450 assert!(total_drawables > 0, "Should have drawable references");
451 }
452
453 #[test]
455 fn find_cleft_spawn_transforms() {
456 use crate::core::class_index::ClassIndex;
457 use crate::resource::prp::PrpPage;
458 use std::path::Path;
459
460 let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Desert.prp");
461 if !path.exists() { return; }
462 let page = PrpPage::from_file(path).unwrap();
463
464 let spawn_names: std::collections::HashSet<String> = page.keys_of_type(ClassIndex::PL_SPAWN_MODIFIER)
465 .iter().map(|k| k.object_name.clone()).collect();
466 assert!(!spawn_names.is_empty());
467
468 let mut found = 0;
469 for so_key in page.keys_of_type(ClassIndex::PL_SCENE_OBJECT) {
470 let has_spawn = if let Some(data) = page.object_data(so_key) {
471 let mut c = Cursor::new(data);
472 let _ = c.read_i16().unwrap();
473 SceneObjectData::read(&mut c).map_or(false, |so| {
474 so.modifiers.iter().any(|m| m.as_ref().map_or(false, |u| spawn_names.contains(&u.object_name)))
475 })
476 } else { false };
477 if !has_spawn { continue; }
478
479 if let Some(data) = page.object_data(so_key) {
480 let mut cursor = Cursor::new(data);
481 let _ = cursor.read_i16().unwrap();
482 if let Ok(so) = SceneObjectData::read(&mut cursor) {
483 if let Some(ci_uoid) = &so.coord_interface {
484 for ci_key in page.keys_of_type(ClassIndex::PL_COORDINATE_INTERFACE) {
485 if ci_key.object_name != ci_uoid.object_name { continue; }
486 if let Some(ci_data) = page.object_data(ci_key) {
487 let mut ci_cursor = Cursor::new(ci_data);
488 let _ = ci_cursor.read_i16().unwrap();
489 if let Ok(ci) = CoordinateInterfaceData::read(&mut ci_cursor) {
490 let m = ci.local_to_world;
491 found += 1;
493 if so_key.object_name == "LinkInPointDefault" {
494 assert!((m[3] - -147.78).abs() < 1.0, "X mismatch: {}", m[3]);
496 assert!((m[7] - -648.55).abs() < 1.0, "Y mismatch: {}", m[7]);
497 }
498 }
499 }
500 }
501 }
502 }
503 }
504 }
505 assert!(found > 0, "Should have found spawn transforms");
506 }
507}