From 80ccd375de7524643c73f514a4356d883a719f62 Mon Sep 17 00:00:00 2001 From: Connor Date: Sat, 10 Jan 2026 10:43:41 +0000 Subject: [PATCH] different commands --- cursebreaker-parser/Cargo.toml | 16 +++ cursebreaker-parser/README.md | 58 +++++++- cursebreaker-parser/src/bin/image-parser.rs | 49 +++++++ cursebreaker-parser/src/bin/scene-parser.rs | 72 ++++++++++ cursebreaker-parser/src/bin/xml-parser.rs | 145 ++++++++++++++++++++ 5 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 cursebreaker-parser/src/bin/image-parser.rs create mode 100644 cursebreaker-parser/src/bin/scene-parser.rs create mode 100644 cursebreaker-parser/src/bin/xml-parser.rs diff --git a/cursebreaker-parser/Cargo.toml b/cursebreaker-parser/Cargo.toml index 1f32964..2013694 100644 --- a/cursebreaker-parser/Cargo.toml +++ b/cursebreaker-parser/Cargo.toml @@ -7,10 +7,26 @@ edition = "2021" name = "cursebreaker_parser" path = "src/lib.rs" +# Main binary - runs all parsers [[bin]] name = "cursebreaker-parser" path = "src/main.rs" +# XML Parser - loads game data from XML files and populates database +[[bin]] +name = "xml-parser" +path = "src/bin/xml-parser.rs" + +# Scene Parser - parses Unity scenes and extracts game objects +[[bin]] +name = "scene-parser" +path = "src/bin/scene-parser.rs" + +# Image Parser - processes minimap tiles +[[bin]] +name = "image-parser" +path = "src/bin/image-parser.rs" + [dependencies] unity-parser = { path = "../unity-parser" } serde_yaml = "0.9" diff --git a/cursebreaker-parser/README.md b/cursebreaker-parser/README.md index 1dfe0dc..3f89418 100644 --- a/cursebreaker-parser/README.md +++ b/cursebreaker-parser/README.md @@ -20,6 +20,53 @@ Cursebreaker Parser is designed to: - **XML Parsing**: Robust XML parsing with error handling - **SQL Export**: Prepare data for SQL database insertion +## Binaries + +The project provides multiple binaries to handle different parsing tasks. This allows you to run only the parts you need, avoiding long load times for unnecessary operations. + +### Available Binaries + +1. **xml-parser** - Loads game data from XML files and populates the SQLite database + - Fast execution + - Run this when XML files change + ```bash + cargo run --bin xml-parser + ``` + +2. **scene-parser** - Parses Unity scenes and extracts game objects + - Slow execution (Unity project initialization) + - Run this when scene files change + ```bash + cargo run --bin scene-parser + ``` + +3. **image-parser** - Processes minimap tiles + - Slow execution (image processing and compression) + - Run this when minimap images change + ```bash + cargo run --bin image-parser + ``` + +4. **cursebreaker-parser** - All-in-one binary (runs all parsers) + - Slowest execution (runs everything) + - Use when you need to regenerate the entire database + ```bash + cargo run --bin cursebreaker-parser + # or simply + cargo run + ``` + +### Building for Production + +Build specific binaries for release: +```bash +cargo build --release --bin xml-parser +cargo build --release --bin scene-parser +cargo build --release --bin image-parser +``` + +The compiled binaries will be in `target/release/`. + ## Usage ### Loading Items from XML @@ -69,19 +116,26 @@ for (id, name, json) in sql_data.iter().take(5) { cursebreaker-parser/ ├── src/ │ ├── lib.rs # Library entry point and public API -│ ├── main.rs # Binary entry point +│ ├── main.rs # Main binary (all-in-one parser) +│ ├── bin/ # Separate parser binaries +│ │ ├── xml-parser.rs # XML parsing only +│ │ ├── scene-parser.rs # Unity scene parsing only +│ │ └── image-parser.rs # Image processing only │ ├── xml_parser.rs # XML parsing utilities +│ ├── image_processor.rs # Image processing utilities │ ├── item_loader.rs # Item loading logic │ ├── databases/ # Database implementations │ │ ├── item_database.rs │ │ ├── npc_database.rs │ │ ├── quest_database.rs │ │ ├── harvestable_database.rs -│ │ └── loot_database.rs +│ │ ├── loot_database.rs +│ │ └── minimap_database.rs │ └── types/ # Type definitions │ ├── cursebreaker/ # Game-specific types (Items, NPCs, Quests, etc.) │ └── monobehaviours/ # Unity MonoBehaviour types ├── examples/ # Example usage +├── migrations/ # Database migrations ├── Cargo.toml # Package configuration └── XML_PARSING.md # XML parsing documentation diff --git a/cursebreaker-parser/src/bin/image-parser.rs b/cursebreaker-parser/src/bin/image-parser.rs new file mode 100644 index 0000000..695f82e --- /dev/null +++ b/cursebreaker-parser/src/bin/image-parser.rs @@ -0,0 +1,49 @@ +//! Image Parser - Processes minimap tiles +//! +//! This binary handles: +//! - Loading minimap tile images +//! - Converting PNG to WebP format +//! - Storing tiles in the SQLite database +//! - Generating statistics about storage and compression + +use cursebreaker_parser::MinimapDatabase; +use log::{info, error, LevelFilter}; +use unity_parser::log::DedupLogger; + +fn main() -> Result<(), Box> { + let logger = DedupLogger::new(); + log::set_boxed_logger(Box::new(logger)) + .map(|()| log::set_max_level(LevelFilter::Trace)) + .unwrap(); + + info!("🎮 Cursebreaker - Image Parser"); + + // Process minimap tiles + info!("\n🗺️ Processing minimap tiles..."); + let minimap_db = MinimapDatabase::new("cursebreaker.db".to_string()); + + let minimap_path = "/home/connor/repos/CBAssets/Data/Textures/MinimapSquares"; + match minimap_db.load_from_directory(minimap_path) { + Ok(count) => { + info!("✅ Processed {} minimap tiles", count); + + if let Ok(stats) = minimap_db.get_storage_stats() { + info!(" Storage Statistics:"); + info!(" • Original PNG total: {} MB", stats.total_original_size / 1_048_576); + info!(" • WebP total: {} MB", stats.total_webp_size() / 1_048_576); + info!(" • Compression ratio: {:.2}%", stats.compression_ratio()); + } + + if let Ok(bounds) = minimap_db.get_map_bounds() { + info!(" Map Bounds:"); + info!(" • Min (x,y): {:?}", bounds.0); + info!(" • Max (x,y): {:?}", bounds.1); + } + } + Err(e) => { + error!("Failed to process minimap tiles: {}", e); + } + } + + Ok(()) +} diff --git a/cursebreaker-parser/src/bin/scene-parser.rs b/cursebreaker-parser/src/bin/scene-parser.rs new file mode 100644 index 0000000..e14dbc4 --- /dev/null +++ b/cursebreaker-parser/src/bin/scene-parser.rs @@ -0,0 +1,72 @@ +//! Scene Parser - Parses Unity scenes and extracts game objects +//! +//! This binary handles: +//! - Initializing the Unity project +//! - Parsing Unity scenes +//! - Extracting Interactable_Resource components +//! - Computing world transforms + +use cursebreaker_parser::InteractableResource; +use unity_parser::UnityProject; +use std::path::Path; +use unity_parser::log::DedupLogger; +use log::{info, error, LevelFilter}; + +fn main() -> Result<(), Box> { + let logger = DedupLogger::new(); + log::set_boxed_logger(Box::new(logger)) + .map(|()| log::set_max_level(LevelFilter::Trace)) + .unwrap(); + + info!("🎮 Cursebreaker - Scene Parser"); + + // Initialize Unity project once - scans entire project for GUID mappings + let project_root = Path::new("/home/connor/repos/CBAssets"); + info!("\n📦 Initializing Unity project from: {}", project_root.display()); + + let project = UnityProject::from_path(project_root)?; + + // Now parse the scene using the pre-built GUID resolvers + let scene_path = "_GameAssets/Scenes/Tiles/10_3.unity"; + info!("📁 Parsing scene: {}", scene_path); + + log::logger().flush(); + + // Parse the scene using the project + match project.parse_scene(scene_path) { + Ok(mut scene) => { + info!("✅ Scene parsed successfully!"); + info!(" Total entities: {}", scene.entity_map.len()); + + // Post-processing: Compute world transforms + info!("🔄 Computing world transforms..."); + unity_parser::compute_world_transforms(&mut scene.world, &scene.entity_map); + info!(" ✓ World transforms computed"); + + // Find all entities that have Interactable_Resource + log::logger().flush(); + + scene.world + .query_all::<(&InteractableResource, &unity_parser::WorldTransform, &unity_parser::GameObject)>() + .for_each(|(resource, transform, object)| { + info!(" 📦 Resource: \"{}\"", object.name); + info!(" • typeId: {}", resource.type_id); + + // Extract world position from WorldTransform + let world_pos = transform.position(); + info!(" • Position: ({:.2}, {:.2}, {:.2})", world_pos.x, world_pos.y, world_pos.z); + log::logger().flush(); + }); + + log::logger().flush(); + } + Err(e) => { + error!("Parse error: {}", e); + return Err(Box::new(e)); + } + } + + log::logger().flush(); + + Ok(()) +} diff --git a/cursebreaker-parser/src/bin/xml-parser.rs b/cursebreaker-parser/src/bin/xml-parser.rs new file mode 100644 index 0000000..d9c04ab --- /dev/null +++ b/cursebreaker-parser/src/bin/xml-parser.rs @@ -0,0 +1,145 @@ +//! XML Parser - Loads game data from XML files and populates the SQLite database +//! +//! This binary handles: +//! - Loading all game data from XML files +//! - Populating the SQLite database with the parsed data +//! - Generating statistics about the loaded data + +use cursebreaker_parser::{ + ItemDatabase, NpcDatabase, QuestDatabase, HarvestableDatabase, LootDatabase, + MapDatabase, FastTravelDatabase, PlayerHouseDatabase, TraitDatabase, ShopDatabase +}; +use log::{info, warn, LevelFilter}; +use unity_parser::log::DedupLogger; +use diesel::prelude::*; +use diesel::sqlite::SqliteConnection; + +fn main() -> Result<(), Box> { + let logger = DedupLogger::new(); + log::set_boxed_logger(Box::new(logger)) + .map(|()| log::set_max_level(LevelFilter::Trace)) + .unwrap(); + + info!("🎮 Cursebreaker - XML Parser"); + info!("📚 Loading game data from XML..."); + + // Load items from XML + let items_path = "/home/connor/repos/CBAssets/Data/XMLs/Items/Items.xml"; + let item_db = ItemDatabase::load_from_xml(items_path)?; + info!("✅ Loaded {} items", item_db.len()); + + let npcs_path = "/home/connor/repos/CBAssets/Data/XMLs/Npcs/NPCInfo.xml"; + let npc_db = NpcDatabase::load_from_xml(npcs_path)?; + info!("✅ Loaded {} NPCs", npc_db.len()); + + let quests_path = "/home/connor/repos/CBAssets/Data/XMLs/Quests/Quests.xml"; + let quest_db = QuestDatabase::load_from_xml(quests_path)?; + info!("✅ Loaded {} quests", quest_db.len()); + + let harvestables_path = "/home/connor/repos/CBAssets/Data/XMLs/Harvestables/HarvestableInfo.xml"; + let harvestable_db = HarvestableDatabase::load_from_xml(harvestables_path)?; + info!("✅ Loaded {} harvestables", harvestable_db.len()); + + let loot_path = "/home/connor/repos/CBAssets/Data/XMLs/Loot/Loot.xml"; + let loot_db = LootDatabase::load_from_xml(loot_path)?; + info!("✅ Loaded {} loot tables", loot_db.len()); + + let maps_path = "/home/connor/repos/CBAssets/Data/XMLs/Maps/Maps.xml"; + let map_db = MapDatabase::load_from_xml(maps_path)?; + info!("✅ Loaded {} maps", map_db.len()); + + let fast_travel_dir = "/home/connor/repos/CBAssets/Data/XMLs"; + let fast_travel_db = FastTravelDatabase::load_from_directory(fast_travel_dir)?; + info!("✅ Loaded {} fast travel locations", fast_travel_db.len()); + + let player_houses_path = "/home/connor/repos/CBAssets/Data/XMLs/PlayerHouses/PlayerHouses.xml"; + let player_house_db = PlayerHouseDatabase::load_from_xml(player_houses_path)?; + info!("✅ Loaded {} player houses", player_house_db.len()); + + let traits_path = "/home/connor/repos/CBAssets/Data/XMLs/Traits/Traits.xml"; + let trait_db = TraitDatabase::load_from_xml(traits_path)?; + info!("✅ Loaded {} traits", trait_db.len()); + + let shops_path = "/home/connor/repos/CBAssets/Data/XMLs/Shops/Shops.xml"; + let shop_db = ShopDatabase::load_from_xml(shops_path)?; + info!("✅ Loaded {} shops", shop_db.len()); + + // Save to SQLite database + info!("\n💾 Saving game data to SQLite database..."); + let mut conn = SqliteConnection::establish("cursebreaker.db")?; + + match item_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} items to database", count), + Err(e) => warn!("⚠️ Failed to save items: {}", e), + } + + match npc_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} NPCs to database", count), + Err(e) => warn!("⚠️ Failed to save NPCs: {}", e), + } + + match quest_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} quests to database", count), + Err(e) => warn!("⚠️ Failed to save quests: {}", e), + } + + match harvestable_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} harvestables to database", count), + Err(e) => warn!("⚠️ Failed to save harvestables: {}", e), + } + + match loot_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} loot tables to database", count), + Err(e) => warn!("⚠️ Failed to save loot tables: {}", e), + } + + match map_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} maps to database", count), + Err(e) => warn!("⚠️ Failed to save maps: {}", e), + } + + match fast_travel_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} fast travel locations to database", count), + Err(e) => warn!("⚠️ Failed to save fast travel locations: {}", e), + } + + match player_house_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} player houses to database", count), + Err(e) => warn!("⚠️ Failed to save player houses: {}", e), + } + + match trait_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} traits to database", count), + Err(e) => warn!("⚠️ Failed to save traits: {}", e), + } + + match shop_db.save_to_db(&mut conn) { + Ok(count) => info!("✅ Saved {} shops to database", count), + Err(e) => warn!("⚠️ Failed to save shops: {}", e), + } + + // Print statistics + info!("\n📊 Game Data Statistics:"); + info!(" Items:"); + info!(" • Weapons: {}", item_db.get_by_slot("weapon").len()); + info!(" • Consumables: {}", item_db.get_by_slot("consumable").len()); + info!(" NPCs:"); + info!(" • Hostile: {}", npc_db.get_hostile().len()); + info!(" • Interactable: {}", npc_db.get_interactable().len()); + info!(" Quests:"); + info!(" • Main quests: {}", quest_db.get_main_quests().len()); + info!(" • Side quests: {}", quest_db.get_side_quests().len()); + info!(" Harvestables:"); + info!(" • Trees: {}", harvestable_db.get_trees().len()); + info!(" • Woodcutting: {}", harvestable_db.get_by_skill("Woodcutting").len()); + info!(" • Mining: {}", harvestable_db.get_by_skill("mining").len()); + info!(" • Fishing: {}", harvestable_db.get_by_skill("Fishing").len()); + info!(" • Alchemy: {}", harvestable_db.get_by_skill("Alchemy").len()); + info!(" Loot:"); + info!(" • Total tables: {}", loot_db.len()); + info!(" • NPCs with loot: {}", loot_db.get_all_npcs_with_loot().len()); + info!(" • Droppable items: {}", loot_db.get_all_droppable_items().len()); + info!(" • Tables with conditional drops: {}", loot_db.get_conditional_tables().len()); + + Ok(()) +}