plasma-prp 0.1.0

Read, write, inspect, and manipulate Plasma engine PRP files used by Myst Online: Uru Live
Documentation
//! plSceneNode — groups scene objects and drawables per page.
//!
//! Each page has one plSceneNode that holds references to all
//! scene objects and drawables in that page.
//!
//! C++ ref: plScene/plSceneNode.h/.cpp

use std::io::Read;

use anyhow::Result;

use crate::resource::prp::PlasmaRead;

use super::uoid::{Uoid, read_key_uoid};

/// Parsed plSceneNode data.
///
/// Note: plSceneNode inherits from hsKeyedObject (NOT plSynchedObject).
#[derive(Debug, Clone)]
pub struct SceneNodeData {
    pub self_key: Option<Uoid>,
    pub scene_objects: Vec<Option<Uoid>>,
    pub pool_objects: Vec<Option<Uoid>>,
}

impl SceneNodeData {
    /// Read a plSceneNode from a stream (after creatable class index).
    pub fn read(reader: &mut impl Read) -> Result<Self> {
        // hsKeyedObject::Read — just the self-key (no plSynchedObject!)
        let self_key = read_key_uoid(reader)?;

        // Scene objects
        let num_scene_objects = reader.read_u32()?;
        let mut scene_objects = Vec::with_capacity(num_scene_objects as usize);
        for _ in 0..num_scene_objects {
            scene_objects.push(read_key_uoid(reader)?);
        }

        // Pool (generic) objects
        let num_pool = reader.read_u32()?;
        let mut pool_objects = Vec::with_capacity(num_pool as usize);
        for _ in 0..num_pool {
            pool_objects.push(read_key_uoid(reader)?);
        }

        Ok(Self {
            self_key,
            scene_objects,
            pool_objects,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::class_index::ClassIndex;
    use crate::resource::prp::PrpPage;
    use std::io::Cursor;
    use std::path::Path;

    #[test]
    fn test_parse_cleft_scene_node() {
        let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Cleft.prp");
        if !path.exists() {
            eprintln!("Skipping test: {:?} not found", path);
            return;
        }

        let page = PrpPage::from_file(path).unwrap();
        let node_keys: Vec<_> = page.keys_of_type(ClassIndex::PL_SCENE_NODE);

        assert!(!node_keys.is_empty(), "Should have at least one scene node");

        for key in &node_keys {
            if let Some(data) = page.object_data(key) {
                let mut cursor = Cursor::new(data);
                let _ = cursor.read_i16().unwrap();

                let node = SceneNodeData::read(&mut cursor).unwrap();
                eprintln!(
                    "SceneNode '{}': {} scene objects, {} pool objects",
                    key.object_name,
                    node.scene_objects.len(),
                    node.pool_objects.len()
                );
                assert!(
                    !node.scene_objects.is_empty(),
                    "Cleft scene node should have scene objects"
                );
            }
        }
    }
}