use std::io::Read;
use anyhow::Result;
use crate::resource::prp::PlasmaRead;
use super::bit_vector::BitVector;
use super::synched_object::SynchedObjectData;
use super::uoid::{Uoid, read_key_uoid};
#[derive(Debug, Clone, Default)]
pub struct ObjInterfaceData {
pub self_key: Option<Uoid>,
pub synched: SynchedObjectData,
pub owner: Option<Uoid>,
pub props: BitVector,
}
impl ObjInterfaceData {
pub fn read(reader: &mut impl Read) -> Result<Self> {
let self_key = read_key_uoid(reader)?;
let synched = SynchedObjectData::read(reader)?;
let owner = read_key_uoid(reader)?;
let props = BitVector::read(reader)?;
Ok(Self {
self_key,
synched,
owner,
props,
})
}
}
#[derive(Debug, Clone)]
pub struct CoordinateInterfaceData {
pub base: ObjInterfaceData,
pub local_to_parent: [f32; 16],
pub parent_to_local: [f32; 16],
pub local_to_world: [f32; 16],
pub world_to_local: [f32; 16],
pub children: Vec<Option<Uoid>>,
}
impl CoordinateInterfaceData {
pub fn read(reader: &mut impl Read) -> Result<Self> {
let base = ObjInterfaceData::read(reader)?;
let local_to_parent = read_matrix44(reader)?;
let parent_to_local = read_matrix44(reader)?;
let local_to_world = read_matrix44(reader)?;
let world_to_local = read_matrix44(reader)?;
let num_children = reader.read_u32()?;
let mut children = Vec::with_capacity(num_children as usize);
for _ in 0..num_children {
children.push(read_key_uoid(reader)?);
}
Ok(Self {
base,
local_to_parent,
parent_to_local,
local_to_world,
world_to_local,
children,
})
}
}
#[derive(Debug, Clone)]
pub struct DrawInterfaceData {
pub base: ObjInterfaceData,
pub drawables: Vec<(u32, Option<Uoid>)>,
pub regions: Vec<Option<Uoid>>,
}
impl DrawInterfaceData {
pub fn read(reader: &mut impl Read) -> Result<Self> {
let base = ObjInterfaceData::read(reader)?;
let num_drawables = reader.read_u32()?;
let mut drawables = Vec::with_capacity(num_drawables as usize);
for _ in 0..num_drawables {
let index = reader.read_u32()?;
let key = read_key_uoid(reader)?;
drawables.push((index, key));
}
let num_regions = reader.read_u32()?;
let mut regions = Vec::with_capacity(num_regions as usize);
for _ in 0..num_regions {
regions.push(read_key_uoid(reader)?);
}
Ok(Self {
base,
drawables,
regions,
})
}
}
#[derive(Debug, Clone, Default)]
pub struct SimulationInterfaceData {
pub base: ObjInterfaceData,
pub physical_key: Option<Uoid>,
}
impl SimulationInterfaceData {
pub fn read(reader: &mut impl Read) -> Result<Self> {
let base = ObjInterfaceData::read(reader)?;
let physical_key = read_key_uoid(reader)?;
Ok(Self {
base,
physical_key,
})
}
}
#[derive(Debug, Clone, Default)]
pub struct AudioInterfaceData {
pub base: ObjInterfaceData,
pub audible_key: Option<Uoid>,
}
impl AudioInterfaceData {
pub fn read(reader: &mut impl Read) -> Result<Self> {
let base = ObjInterfaceData::read(reader)?;
let audible_key = read_key_uoid(reader)?;
Ok(Self {
base,
audible_key,
})
}
}
#[derive(Debug, Clone)]
pub struct SceneObjectData {
pub self_key: Option<Uoid>,
pub synched: SynchedObjectData,
pub draw_interface: Option<Uoid>,
pub sim_interface: Option<Uoid>,
pub coord_interface: Option<Uoid>,
pub audio_interface: Option<Uoid>,
pub generics: Vec<Option<Uoid>>,
pub modifiers: Vec<Option<Uoid>>,
pub scene_node: Option<Uoid>,
}
impl SceneObjectData {
pub fn read(reader: &mut impl Read) -> Result<Self> {
let self_key = read_key_uoid(reader)?;
let synched = SynchedObjectData::read(reader)?;
let draw_interface = read_key_uoid(reader)?;
let sim_interface = read_key_uoid(reader)?;
let coord_interface = read_key_uoid(reader)?;
let audio_interface = read_key_uoid(reader)?;
let num_generics = reader.read_u32()?;
let mut generics = Vec::with_capacity(num_generics as usize);
for _ in 0..num_generics {
generics.push(read_key_uoid(reader)?);
}
let num_modifiers = reader.read_u32()?;
let mut modifiers = Vec::with_capacity(num_modifiers as usize);
for _ in 0..num_modifiers {
modifiers.push(read_key_uoid(reader)?);
}
let scene_node = read_key_uoid(reader)?;
Ok(Self {
self_key,
synched,
draw_interface,
sim_interface,
coord_interface,
audio_interface,
generics,
modifiers,
scene_node,
})
}
}
fn read_matrix44(reader: &mut impl Read) -> Result<[f32; 16]> {
let flag = reader.read_u8()?;
if flag == 0 {
return Ok([
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,
]);
}
let mut m = [0f32; 16];
for val in &mut m {
*val = reader.read_f32()?;
}
Ok(m)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_parse_cleft_scene_objects() {
use crate::resource::prp::{PrpPage, class_types};
use std::path::Path;
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 scene_keys: Vec<_> = page.keys_of_type(class_types::PL_SCENE_OBJECT);
let mut parsed = 0;
let mut with_draw = 0;
let mut with_coord = 0;
let mut with_sim = 0;
let mut with_audio = 0;
let mut total_modifiers = 0;
for key in &scene_keys {
if let Some(data) = page.object_data(key) {
let mut cursor = Cursor::new(data);
let _ = cursor.read_i16().unwrap();
match SceneObjectData::read(&mut cursor) {
Ok(so) => {
parsed += 1;
if so.draw_interface.is_some() {
with_draw += 1;
}
if so.coord_interface.is_some() {
with_coord += 1;
}
if so.sim_interface.is_some() {
with_sim += 1;
}
if so.audio_interface.is_some() {
with_audio += 1;
}
total_modifiers += so.modifiers.len();
if let Some(uoid) = &so.self_key {
assert_eq!(
uoid.object_name, key.object_name,
"Self-key name mismatch for {}",
key.object_name
);
}
}
Err(e) => {
panic!(
"Failed to parse SceneObject '{}': {}",
key.object_name, e
);
}
}
}
}
eprintln!(
"Parsed {} plSceneObjects: {} with draw, {} with coord, {} with sim, {} with audio, {} total modifiers",
parsed, with_draw, with_coord, with_sim, with_audio, total_modifiers
);
assert!(parsed > 0, "Should have parsed at least some scene objects");
assert!(with_draw > 0, "Some objects should have draw interfaces");
assert!(with_coord > 0, "Some objects should have coordinate interfaces");
}
#[test]
fn test_parse_cleft_coord_interfaces() {
use crate::core::class_index::ClassIndex;
use crate::resource::prp::PrpPage;
use std::path::Path;
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 coord_keys: Vec<_> = page.keys_of_type(ClassIndex::PL_COORDINATE_INTERFACE);
let mut parsed = 0;
for key in &coord_keys {
if let Some(data) = page.object_data(key) {
let mut cursor = Cursor::new(data);
let _ = cursor.read_i16().unwrap();
match CoordinateInterfaceData::read(&mut cursor) {
Ok(ci) => {
parsed += 1;
let l2w = ci.local_to_world;
let has_some_nonzero = l2w.iter().any(|&v| v != 0.0);
assert!(
has_some_nonzero,
"L2W matrix for {} is all zeros",
key.object_name
);
}
Err(e) => {
panic!(
"Failed to parse CoordinateInterface '{}': {}",
key.object_name, e
);
}
}
}
}
eprintln!(
"Parsed {} plCoordinateInterfaces from Cleft",
parsed
);
assert!(parsed > 0, "Should have parsed coordinate interfaces");
}
#[test]
fn test_parse_cleft_draw_interfaces() {
use crate::core::class_index::ClassIndex;
use crate::resource::prp::PrpPage;
use std::path::Path;
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 draw_keys: Vec<_> = page.keys_of_type(ClassIndex::PL_DRAW_INTERFACE);
let mut parsed = 0;
let mut total_drawables = 0;
for key in &draw_keys {
if let Some(data) = page.object_data(key) {
let mut cursor = Cursor::new(data);
let _ = cursor.read_i16().unwrap();
match DrawInterfaceData::read(&mut cursor) {
Ok(di) => {
parsed += 1;
total_drawables += di.drawables.len();
for (idx, key_ref) in &di.drawables {
if let Some(uoid) = key_ref {
assert_eq!(
uoid.class_type,
ClassIndex::PL_DRAWABLE_SPANS,
"DrawInterface should reference plDrawableSpans, got 0x{:04X} for {}",
uoid.class_type,
key.object_name
);
}
}
}
Err(e) => {
panic!(
"Failed to parse DrawInterface '{}': {}",
key.object_name, e
);
}
}
}
}
eprintln!(
"Parsed {} plDrawInterfaces ({} total drawable refs) from Cleft",
parsed, total_drawables
);
assert!(parsed > 0, "Should have parsed draw interfaces");
assert!(total_drawables > 0, "Should have drawable references");
}
#[test]
fn find_cleft_spawn_transforms() {
use crate::core::class_index::ClassIndex;
use crate::resource::prp::PrpPage;
use std::path::Path;
let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Desert.prp");
if !path.exists() { return; }
let page = PrpPage::from_file(path).unwrap();
let spawn_names: std::collections::HashSet<String> = page.keys_of_type(ClassIndex::PL_SPAWN_MODIFIER)
.iter().map(|k| k.object_name.clone()).collect();
assert!(!spawn_names.is_empty());
let mut found = 0;
for so_key in page.keys_of_type(ClassIndex::PL_SCENE_OBJECT) {
let has_spawn = if let Some(data) = page.object_data(so_key) {
let mut c = Cursor::new(data);
let _ = c.read_i16().unwrap();
SceneObjectData::read(&mut c).map_or(false, |so| {
so.modifiers.iter().any(|m| m.as_ref().map_or(false, |u| spawn_names.contains(&u.object_name)))
})
} else { false };
if !has_spawn { continue; }
if let Some(data) = page.object_data(so_key) {
let mut cursor = Cursor::new(data);
let _ = cursor.read_i16().unwrap();
if let Ok(so) = SceneObjectData::read(&mut cursor) {
if let Some(ci_uoid) = &so.coord_interface {
for ci_key in page.keys_of_type(ClassIndex::PL_COORDINATE_INTERFACE) {
if ci_key.object_name != ci_uoid.object_name { continue; }
if let Some(ci_data) = page.object_data(ci_key) {
let mut ci_cursor = Cursor::new(ci_data);
let _ = ci_cursor.read_i16().unwrap();
if let Ok(ci) = CoordinateInterfaceData::read(&mut ci_cursor) {
let m = ci.local_to_world;
found += 1;
if so_key.object_name == "LinkInPointDefault" {
assert!((m[3] - -147.78).abs() < 1.0, "X mismatch: {}", m[3]);
assert!((m[7] - -648.55).abs() < 1.0, "Y mismatch: {}", m[7]);
}
}
}
}
}
}
}
}
assert!(found > 0, "Should have found spawn transforms");
}
}