From 6f40cb917734f2cc940acd25caa1d861a29732b9 Mon Sep 17 00:00:00 2001 From: Connor Date: Sat, 3 Jan 2026 04:09:19 +0000 Subject: [PATCH] meta files phase 3 --- cursebreaker-parser/src/ecs/builder.rs | 54 ++++++++++++- cursebreaker-parser/src/parser/mod.rs | 23 +++++- cursebreaker-parser/src/types/component.rs | 2 + .../src/types/prefab_instance.rs | 1 + .../tests/integration_tests.rs | 78 +++++++++++++++++++ 5 files changed, 155 insertions(+), 3 deletions(-) diff --git a/cursebreaker-parser/src/ecs/builder.rs b/cursebreaker-parser/src/ecs/builder.rs index 1c2896a..54b1994 100644 --- a/cursebreaker-parser/src/ecs/builder.rs +++ b/cursebreaker-parser/src/ecs/builder.rs @@ -1,6 +1,7 @@ //! ECS world building from Unity documents use crate::model::RawDocument; +use crate::parser::GuidResolver; use crate::types::{ yaml_helpers, ComponentContext, FileID, GameObject, LinkingContext, PrefabInstanceComponent, RectTransform, Transform, TypeFilter, UnityComponent, @@ -19,11 +20,13 @@ use std::collections::HashMap; /// /// # Arguments /// - `documents`: Parsed Unity documents to build the world from +/// - `guid_resolver`: Optional GUID resolver for resolving MonoBehaviour scripts to class names /// /// # Returns /// A tuple of (World, FileID → Entity mapping) pub fn build_world_from_documents( documents: Vec, + guid_resolver: Option<&GuidResolver>, ) -> Result<(World, HashMap)> { // Create World builder with registered component types let mut builder = World::builder(); @@ -51,7 +54,7 @@ pub fn build_world_from_documents( // PASS 2: Attach components to entities let type_filter = TypeFilter::parse_all(); for doc in documents.iter().filter(|d| d.type_id != 1 && d.class_name != "GameObject") { - attach_component(&mut world, doc, &linking_ctx, &type_filter)?; + attach_component(&mut world, doc, &linking_ctx, &type_filter, guid_resolver)?; } // PASS 3: Execute all deferred linking callbacks @@ -100,7 +103,8 @@ pub fn build_world_from_documents_into( // PASS 2: Attach components to entities let type_filter = TypeFilter::parse_all(); for doc in documents.iter().filter(|d| d.type_id != 1 && d.class_name != "GameObject") { - attach_component(world, doc, &linking_ctx, &type_filter)?; + // Note: Prefab instantiation doesn't need GUID resolution (uses base prefab's components) + attach_component(world, doc, &linking_ctx, &type_filter, None)?; } // PASS 3: Execute all deferred linking callbacks @@ -125,6 +129,7 @@ fn spawn_game_object(world: &mut World, doc: &RawDocument) -> Result { entity: None, linking_ctx: None, yaml, + guid_resolver: None, }; let go = GameObject::parse(yaml, &ctx) @@ -142,6 +147,7 @@ fn attach_component( doc: &RawDocument, linking_ctx: &RefCell, type_filter: &TypeFilter, + guid_resolver: Option<&GuidResolver>, ) -> Result<()> { let yaml = doc .as_mapping() @@ -176,6 +182,7 @@ fn attach_component( entity: Some(entity), linking_ctx: Some(linking_ctx), yaml, + guid_resolver, }; // Check type filter to see if we should parse this component @@ -209,6 +216,49 @@ fn attach_component( linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity); } } + "MonoBehaviour" => { + // Extract m_Script GUID to resolve the actual class name + if let Some(resolver) = guid_resolver { + if let Some(script_ref) = yaml_helpers::get_external_ref(yaml, "m_Script") { + // Resolve GUID to class name + if let Some(class_name) = resolver.resolve_class_name(script_ref.guid.as_str()) { + // Try to find a registered custom component with this class name + let mut found_custom = false; + for reg in inventory::iter:: { + if reg.class_name == class_name { + found_custom = true; + // Parse and insert the component into the ECS world + if (reg.parse_and_insert)(yaml, &ctx, world, entity) { + // Successfully parsed and inserted + linking_ctx.borrow_mut().entity_map_mut().insert(doc.file_id, entity); + } + break; + } + } + + if !found_custom { + // GUID resolved but no registered component found + eprintln!( + "Warning: Skipping MonoBehaviour '{}' (no registered parser)", + class_name + ); + } + } else { + // GUID not found in resolver + eprintln!( + "Warning: Could not resolve MonoBehaviour GUID: {}", + script_ref.guid + ); + } + } else { + // No m_Script reference found + eprintln!("Warning: MonoBehaviour missing m_Script reference"); + } + } else { + // No GUID resolver available + eprintln!("Warning: Skipping MonoBehaviour (no GUID resolver available)"); + } + } _ => { // Check if this is a registered custom component let mut found_custom = false; diff --git a/cursebreaker-parser/src/parser/mod.rs b/cursebreaker-parser/src/parser/mod.rs index 500e258..823935d 100644 --- a/cursebreaker-parser/src/parser/mod.rs +++ b/cursebreaker-parser/src/parser/mod.rs @@ -81,8 +81,29 @@ fn detect_file_type(path: &Path) -> FileType { fn parse_scene(path: &Path, content: &str) -> Result { let raw_documents = parse_raw_documents(content)?; + // Try to find Unity project root and build GUID resolver + let guid_resolver = match find_project_root(path) { + Ok(project_root) => { + // Build GUID resolver from project + match GuidResolver::from_project(&project_root) { + Ok(resolver) => Some(resolver), + Err(e) => { + eprintln!("Warning: Failed to build GUID resolver: {}", e); + None + } + } + } + Err(_) => { + // Not part of a Unity project, or project root not found + None + } + }; + // Build ECS world from documents - let (world, entity_map) = crate::ecs::build_world_from_documents(raw_documents)?; + let (world, entity_map) = crate::ecs::build_world_from_documents( + raw_documents, + guid_resolver.as_ref(), + )?; Ok(UnityFile::Scene(UnityScene::new( path.to_path_buf(), diff --git a/cursebreaker-parser/src/types/component.rs b/cursebreaker-parser/src/types/component.rs index 40746a7..e55eb61 100644 --- a/cursebreaker-parser/src/types/component.rs +++ b/cursebreaker-parser/src/types/component.rs @@ -70,6 +70,8 @@ pub struct ComponentContext<'a> { pub linking_ctx: Option<&'a RefCell>, /// The raw YAML mapping for this component (for extracting FileRefs) pub yaml: &'a Mapping, + /// GUID resolver for resolving MonoBehaviour script GUIDs to class names + pub guid_resolver: Option<&'a crate::parser::GuidResolver>, } /// Trait for Unity components that can be parsed from YAML diff --git a/cursebreaker-parser/src/types/prefab_instance.rs b/cursebreaker-parser/src/types/prefab_instance.rs index ccc73c7..c17a906 100644 --- a/cursebreaker-parser/src/types/prefab_instance.rs +++ b/cursebreaker-parser/src/types/prefab_instance.rs @@ -524,6 +524,7 @@ impl PrefabResolver { entity: None, linking_ctx: None, yaml: mapping, + guid_resolver: None, }; if let Some(component) = PrefabInstanceComponent::parse(mapping, &ctx) { diff --git a/cursebreaker-parser/tests/integration_tests.rs b/cursebreaker-parser/tests/integration_tests.rs index c05628e..b3db41b 100644 --- a/cursebreaker-parser/tests/integration_tests.rs +++ b/cursebreaker-parser/tests/integration_tests.rs @@ -474,3 +474,81 @@ fn test_guid_resolution() { println!("\n{}", "=".repeat(60)); } + +/// Test parsing PlaySFX components from actual scene file +#[test] +fn test_playsfx_parsing() { + use cursebreaker_parser::UnityComponent; + + /// PlaySFX component from VR_Horror_YouCantRun + #[derive(Debug, Clone, UnityComponent)] + #[unity_class("PlaySFX")] + pub struct PlaySFX { + #[unity_field("volume")] + pub volume: f64, + + #[unity_field("startTime")] + pub start_time: f64, + + #[unity_field("endTime")] + pub end_time: f64, + + #[unity_field("isLoop")] + pub is_loop: bool, + } + + let project_path = match clone_test_project(&TestProject::VR_HORROR) { + Ok(path) => path, + Err(e) => { + eprintln!("Failed to clone project: {}", e); + eprintln!("Skipping PlaySFX parsing test (git may not be available)"); + return; + } + }; + + println!("\n{}", "=".repeat(60)); + println!("Testing PlaySFX Component Parsing"); + println!("{}", "=".repeat(60)); + + // Parse the 1F.unity scene that contains PlaySFX components + let scene_path = project_path.join("Assets/Scenes/TEST/Final_1F/1F.unity"); + + if !scene_path.exists() { + eprintln!("Scene file not found: {}", scene_path.display()); + return; + } + + println!("\n Parsing scene: {}", scene_path.display()); + + match cursebreaker_parser::UnityFile::from_path(&scene_path) { + Ok(cursebreaker_parser::UnityFile::Scene(scene)) => { + println!(" ✓ Scene parsed successfully"); + println!(" - Total entities: {}", scene.entity_map.len()); + + // Try to get PlaySFX components + let playsfx_view = scene.world.borrow::(); + let mut found_count = 0; + + for entity in scene.entity_map.values() { + if playsfx_view.get(*entity).is_some() { + found_count += 1; + } + } + + println!(" ✓ Found {} PlaySFX component(s)", found_count); + + assert!( + found_count > 0, + "Should find at least one PlaySFX component in 1F.unity" + ); + } + Ok(_) => { + panic!("File was not parsed as a scene"); + } + Err(e) => { + panic!("Failed to parse scene: {}", e); + } + } + + println!("\n{}", "=".repeat(60)); +}